Generated Constructors¶
[GenerateConstructor] removes hand-written constructor, field-assignment, and guard-clause boilerplate for a partial class. The generator emits one public constructor whose parameters, in declaration order, come from the class's eligible private readonly fields, plus any requested guard clauses -- built-in or custom, always as a direct compile-time call, never through reflection. This is a source-generation-only feature.
Why Not a Primary Constructor?¶
C# primary constructors reduce constructor syntax, but they give you no constructor body to place a guard clause in. Enforcing an invariant with a primary constructor still means writing a field or property initializer that repeats the parameter, the storage member, and the validation expression -- and that repetition grows with every dependency.
Roslyn source generators are additive: they can contribute new members to a partial type, but they cannot rewrite or inject statements into a constructor you already wrote -- primary or otherwise. So Needlr generates a complete constructor from field declarations that remain in your source, instead of trying to splice guard statements into a primary constructor it does not own. See ADR-0005 for the full design rationale.
Prerequisites¶
Generated constructors require Needlr's source-generation setup. Add the source-generation packages described in Getting Started; reflection-only configuration does not run this generator.
Needlr's current packages and examples target net10.0. If NexusLabs.Needlr.Generators is consumed independently by a project targeting an older framework, the generated built-in guard calls impose these minimum runtime API versions:
| Guard | Generated API | Minimum .NET version |
|---|---|---|
NotNull and NonNullableReferences |
ArgumentNullException.ThrowIfNull |
.NET 6 |
NotNullOrEmpty |
ArgumentException.ThrowIfNullOrEmpty |
.NET 7 |
NotNullOrWhiteSpace |
ArgumentException.ThrowIfNullOrWhiteSpace |
.NET 8 |
Bare constructor generation and custom guards depend only on the APIs used by the generated assignments and the application-provided guard method.
Quick Start¶
using NexusLabs.Needlr.Generators;
[GenerateConstructor]
public partial class UserService
{
private readonly IRepository _repository;
}
Generated:
No guard is emitted -- see Default: No Guards below. Every eligible field becomes a parameter in declaration order, and _camelCase field names map to camelCase parameter names.
Authored vs. Generated Code¶
You author the field declarations (and any guard attributes); Needlr generates the constructor:
// You write:
using NexusLabs.Needlr.Generators;
[GenerateConstructor(ConstructorNullGuardMode.NonNullableReferences)]
public partial class TenantService
{
private readonly IRepository _repository;
[ConstructorGuard(ConstructorGuardKind.NotNullOrWhiteSpace)]
private readonly string _tenantName;
public IRepository Repository => _repository;
public string TenantName => _tenantName;
}
// Needlr generates (simplified):
partial class TenantService
{
/// <summary>Initializes a new instance of the <see cref="TenantService"/> class.</summary>
/// <param name="repository">The value used to initialize <c>_repository</c>.</param>
/// <param name="tenantName">The value used to initialize <c>_tenantName</c>.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="repository"/> or <paramref name="tenantName"/> is null.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="tenantName"/> is empty or consists only of white-space characters.
/// </exception>
public TenantService(IRepository repository, string tenantName)
{
global::System.ArgumentNullException.ThrowIfNull(repository);
global::System.ArgumentNullException.ThrowIfNull(tenantName);
global::System.ArgumentException.ThrowIfNullOrWhiteSpace(tenantName);
_repository = repository;
_tenantName = tenantName;
}
}
The generated constructor carries full XML documentation, generated by the generator itself from each field's name -- you do not write it by hand.
Migrating Existing Constructors¶
From a hand-written constructor¶
Replace the constructor parameters, guards, and assignments with the generation attribute while retaining the fields:
// Before
public sealed class UserService
{
private readonly IRepository _repository;
public UserService(IRepository repository)
{
ArgumentNullException.ThrowIfNull(repository);
_repository = repository;
}
}
// After
[GenerateConstructor(ConstructorNullGuardMode.NonNullableReferences)]
public sealed partial class UserService
{
private readonly IRepository _repository;
}
From a guarded primary constructor¶
A guarded primary constructor normally repeats each dependency in a field initializer:
// Before
public sealed class UserService(IRepository repository)
{
private readonly IRepository _repository =
repository ?? throw new ArgumentNullException(nameof(repository));
}
// After
[GenerateConstructor(ConstructorNullGuardMode.NonNullableReferences)]
public sealed partial class UserService
{
private readonly IRepository _repository;
}
The generated form preserves an ordinary public constructor while removing the repeated parameter, guard, and assignment code.
Finding mechanically replaceable constructors¶
NDLRGEN063 reports an Info diagnostic when one public
hand-written or primary constructor has an exact generated-constructor equivalent.
The analyzer preserves the authored parameter names, types, and order, and recognizes
only assignments and built-in guards that Needlr can reproduce without changing
behavior.
The diagnostic deliberately does not propose architectural refactors. Constructors that normalize or derive values, copy collections, transfer ownership, perform side effects, chain to another constructor, pass base-constructor arguments, or expose multiple overloads remain authored code.
Projects that require generated constructors can promote the suggestion:
Default: No Guards¶
[GenerateConstructor] with no argument is equivalent to [GenerateConstructor(ConstructorNullGuardMode.None)]: field assignments are generated as-is, with no automatic runtime guard, even for a non-nullable reference field:
[GenerateConstructor]
public partial class UserService
{
private readonly IRepository _repository;
}
// Generated: no guard.
// public UserService(IRepository repository)
// {
// _repository = repository;
// }
Automatic null guards require an explicit class-level opt-in:
[GenerateConstructor(ConstructorNullGuardMode.NonNullableReferences)]
public partial class UserService
{
private readonly IRepository _repository;
}
// Generated:
// public UserService(IRepository repository)
// {
// ArgumentNullException.ThrowIfNull(repository);
// _repository = repository;
// }
ConstructorNullGuardMode.NonNullableReferences only ever affects non-nullable reference-type fields. Nullable reference fields and every value-type field (including Nullable<T>) are never guarded by the class-level default -- see Nullability.
Field-Triggered Generation¶
A positive field-level guard attribute enables constructor generation even when the class has no [GenerateConstructor] at all:
public partial class TenantService
{
private readonly IRepository _repository;
[ConstructorGuard(ConstructorGuardKind.NotNullOrWhiteSpace)]
private readonly string _tenantName;
}
This behaves exactly as though the class had [GenerateConstructor] at the default ConstructorNullGuardMode.None:
- Every eligible field becomes a constructor parameter -- not just the annotated one.
- Only explicitly annotated fields receive a guard;
_repositoryabove is unguarded and acceptsnull.
// Generated:
// public TenantService(IRepository repository, string tenantName)
// {
// ArgumentException.ThrowIfNullOrWhiteSpace(tenantName);
//
// _repository = repository;
// _tenantName = tenantName;
// }
A positive trigger is a built-in guard kind other than None, a custom guard type, or an alias attribute defined with [ConstructorGuardDefinition]. [ConstructorIgnore] and a bare [ConstructorGuard(ConstructorGuardKind.None)] are exclusion-only modifiers -- applying either one, on its own, with no other trigger present on the class, has no effect and is flagged by NDLRGEN045.
Eligible Fields, Order, and Naming¶
An eligible field is a private, instance, readonly field without a field initializer. Fields are considered in this order:
- Fields are read from the type's declaring syntax in deterministic declaration order (by source file path, then by position within the file), so the parameter list is stable across incremental builds.
[ConstructorIgnore]removes an otherwise-eligible field from the parameter list entirely.- Field names are normalized to parameter names by stripping a single leading underscore and lower-casing the first letter (
_tenantName->tenantName); a name that collides with a C# keyword is escaped with@.
The type itself must satisfy additional constraints:
- Partial. Only a
partial classcan receive a generator-contributed constructor (NDLRGEN039). - Top-level, non-record class. Records and nested classes are unsupported (NDLRGEN040).
- No existing explicit instance constructor. Generation is skipped entirely -- not merged -- when the class already declares its own constructor (NDLRGEN041).
- A base type with an accessible parameterless constructor, including the common case of no explicit base constructors at all (e.g.
BackgroundService), since the generated constructor relies on the implicit: base()call (NDLRGEN042). - At least one eligible field (NDLRGEN043), and no two eligible fields normalizing to the same parameter name (NDLRGEN044).
Generic classes are supported: the generated constructor repeats the class's own type parameter list.
Nullability¶
Only non-nullable reference-type fields are eligible for the class-level NonNullableReferences automatic guard. Every other field shape is left alone by the class-level default:
[GenerateConstructor(ConstructorNullGuardMode.NonNullableReferences)]
public partial class Widget
{
private readonly IRepository _repository; // guarded automatically (non-nullable reference)
private readonly IRepository? _optionalRepository; // never guarded (nullable reference: null is explicitly valid)
private readonly int _retryCount; // never guarded (non-nullable value type: null is impossible)
private readonly int? _timeoutSeconds; // never guarded by the default, but CAN take an explicit guard below
}
ConstructorGuardKind.NotNull can still be requested explicitly on a Nullable<T> value-type field, because a runtime null is possible for int? even though it is impossible for a non-nullable int:
[GenerateConstructor(ConstructorNullGuardMode.NonNullableReferences)]
public partial class Widget
{
[ConstructorGuard(ConstructorGuardKind.NotNull)]
private readonly int? _timeoutSeconds;
}
Requesting NotNull on a non-nullable value type (e.g. plain int) is rejected at compile time by NDLRGEN048, because the guard could never fail.
Built-In Guards¶
| Kind | Applies to | Generated call | Failure |
|---|---|---|---|
None |
any eligible field | (none) | -- |
NotNull |
any reference type, Nullable<T>, or type parameter not constrained to a value type |
ArgumentNullException.ThrowIfNull(value) |
ArgumentNullException when null |
NotNullOrEmpty |
string |
ArgumentException.ThrowIfNullOrEmpty(value) |
ArgumentNullException when null; ArgumentException when "" |
NotNullOrWhiteSpace |
string |
ArgumentException.ThrowIfNullOrWhiteSpace(value) |
ArgumentNullException when null; ArgumentException when empty or white space |
Every built-in guard emits the exact same standard .NET guard-clause call a hand-written constructor would use, so the reported exception type and ParamName are identical to what you would get by hand -- ParamName is always the generated constructor's parameter name.
The generated constructor's XML documentation also describes the exceptions implied by its effective built-in guards. Repeated guards and parameters are coalesced deterministically into one ArgumentNullException element and, when applicable, one ArgumentException element. None, a suppressed class-level default, and custom guards do not produce exception claims.
NotNullOrEmpty and NotNullOrWhiteSpace are rejected on a non-string field by NDLRGEN048. A guard kind not defined on ConstructorGuardKind (e.g. produced by casting an out-of-range integer) is rejected by NDLRGEN047.
NotEmpty is deferred
A generic, collection-agnostic NotEmpty guard (covering strings, collections, and arbitrary IEnumerable<T> uniformly) is deferred until its semantics are well-defined across those shapes. Use a direct custom guard type such as CollectionNotEmptyGuard shown below in the meantime.
Guard Composition and Suppression¶
A field can carry more than one [ConstructorGuard] -- guards are additive, applied in a deterministic order:
- The class-level automatic default (when applicable) is composed first.
- Every explicit
[ConstructorGuard]on the field follows, in source declaration order. ConstructorGuardKind.Nonesuppresses the class-level default for that field only -- it does not suppress any other explicit guard on the same field.- Identical effective guard calls (the same built-in kind, or the same resolved custom guard type and method) are only emitted once, even if requested more than once.
[GenerateConstructor(ConstructorNullGuardMode.NonNullableReferences)]
public partial class OrderService
{
// Suppresses the automatic NotNull default for this field only.
[ConstructorGuard(ConstructorGuardKind.None)]
private readonly IRepository _repository;
// Class default (NotNull) applies first, then this explicit guard.
[ConstructorGuard(ConstructorGuardKind.NotNullOrWhiteSpace)]
private readonly string _orderId;
}
// Generated:
// public OrderService(IRepository repository, string orderId)
// {
// ArgumentNullException.ThrowIfNull(orderId);
// ArgumentException.ThrowIfNullOrWhiteSpace(orderId);
//
// _repository = repository;
// _orderId = orderId;
// }
Custom Guards¶
Custom guards use compile-time-resolved direct calls. Needlr never invokes a guard through reflection, and never splices an arbitrary string expression as source.
Direct Guard Types¶
Point a field at a custom guard type. The generator resolves and calls its conventional static void Validate(T value, string parameterName) method directly:
public static class CollectionNotEmptyGuard
{
public static void Validate<T>(IReadOnlyCollection<T>? value, string parameterName)
{
ArgumentNullException.ThrowIfNull(value, parameterName);
if (value.Count == 0)
{
throw new ArgumentException("Collection must not be empty.", parameterName);
}
}
}
public partial class OrderService
{
[ConstructorGuard(typeof(CollectionNotEmptyGuard))]
private readonly IReadOnlyCollection<Order> _orders;
}
// Generated call: CollectionNotEmptyGuard.Validate(orders, nameof(orders));
Explicit Method Selectors¶
Select an explicit method name -- instead of relying on the conventional Validate name -- with nameof:
public static class NumberGuards
{
public static void ValidatePositive(int value, string parameterName)
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(parameterName, value, "Value must be positive.");
}
}
}
public partial class RetryPolicy
{
[ConstructorGuard(typeof(NumberGuards), nameof(NumberGuards.ValidatePositive))]
private readonly int _retryCount;
}
// Generated call: NumberGuards.ValidatePositive(retryCount, nameof(retryCount));
A custom guard method must be accessible, static, return void, and take exactly two parameters: a value compatible with the field's type (directly, or via generic type-parameter inference) and a trailing string parameter name -- no ref/out/in parameters. The analyzer resolves and validates the exact method at compile time; see NDLRGEN049 through NDLRGEN052 for every way this resolution can fail.
Custom Alias / Meta-Attributes¶
Wrap a custom guard behind your own domain-specific attribute by decorating it with [ConstructorGuardDefinition]:
[ConstructorGuardDefinition(typeof(CollectionNotEmptyGuard))]
[AttributeUsage(AttributeTargets.Field)]
public sealed class CollectionNotEmptyAttribute : Attribute
{
}
public partial class OrderService
{
[CollectionNotEmpty]
private readonly IReadOnlyCollection<Order> _orders;
}
// Generated call: CollectionNotEmptyGuard.Validate(orders, nameof(orders));
[CollectionNotEmpty] and [ConstructorGuard(typeof(CollectionNotEmptyGuard))] normalize to the exact same internal guard model and generated call. This works whether the alias attribute is declared in the current project or in a referenced assembly -- Roslyn can inspect the meta-attribute either way. An alias attribute is itself validated once, at its own declaration, by NDLRGEN053 and NDLRGEN054.
Parameterized Alias Arguments¶
A parameterized alias such as [MinCount(3)] automatically forwards every one of its own positional constructor arguments, in declared order, onto the resolved guard method call -- spliced between the guarded value and the trailing nameof parameter name:
public static class MinCountGuard
{
public static void Validate<T>(IReadOnlyCollection<T>? value, int minimum, string parameterName)
{
ArgumentNullException.ThrowIfNull(value, parameterName);
if (value.Count < minimum)
{
throw new ArgumentException($"Must contain at least {minimum} element(s).", parameterName);
}
}
}
[ConstructorGuardDefinition(typeof(MinCountGuard))]
[AttributeUsage(AttributeTargets.Field)]
public sealed class MinCountAttribute : Attribute
{
public MinCountAttribute(int minimum) => Minimum = minimum;
public int Minimum { get; }
}
public partial class BulkOrderRequest
{
[MinCount(3)]
private readonly IReadOnlyCollection<string> _lineItems;
}
// Generated call: MinCountGuard.Validate(lineItems, 3, nameof(lineItems));
The resolved guard method must declare exactly one middle parameter per forwarded argument -- value, then one parameter per alias constructor argument in order, then the trailing string parameterName -- and each middle parameter's type must be compatible with its corresponding forwarded argument's type (directly, or via generic method type-parameter unification with the field's type, as MinCountGuard.Validate<T> demonstrates above). A bare alias with no positional constructor arguments (e.g. [CollectionNotEmpty]) forwards zero arguments, so its generated call remains byte-for-byte identical to the two-argument shape the feature has always emitted.
Supported forwarded argument shapes¶
Every positional alias constructor argument is a compiler-validated attribute constant (a Roslyn TypedConstant), never raw source text or a runtime value. Only the following constant shapes can be forwarded:
| Shape | Example alias usage | Rendered literal |
|---|---|---|
null |
[Defaultable(null)] |
null |
bool |
[Flagged(true)] |
true |
Signed/unsigned integral (sbyte, byte, short, ushort, int, uint, long, ulong) |
[Bounds(7u, 123456789012L, 9999999999UL)] |
7u, 123456789012L, 9999999999UL (the C# suffix required for the literal to round-trip to the same runtime type) |
char |
[Label("prefix", ':')] |
':' |
string |
[Label("prefix", ':')] |
"prefix" |
enum member |
[Risk(RiskLevel.High)] |
global::TestApp.RiskLevel.High (a fully qualified cast expression for a combined [Flags] value with no single matching member) |
System.Type, including open generics |
[OfType(typeof(List<>))] |
typeof(global::System.Collections.Generic.List<>) |
Optional alias constructor parameters¶
An omitted optional alias constructor argument still forwards -- Roslyn's AttributeData.ConstructorArguments always includes one entry per declared constructor parameter, filling in the parameter's effective default value for any argument the usage omits:
[ConstructorGuardDefinition(typeof(RetryGuard))]
[AttributeUsage(AttributeTargets.Field)]
public sealed class RetryAttribute : Attribute
{
public RetryAttribute(int maxAttempts = 5) { }
}
[Retry]
private readonly int _value;
// Generated call: RetryGuard.Validate(value, 5, nameof(value));
Unsupported in this version¶
The following are diagnosed by NDLRGEN055 rather than silently dropped or approximated:
- Arrays and
paramsarguments -- forwarding an array as a single positional literal is inherently ambiguous. floatanddoublearguments -- floating-point literals can silently lose round-trip precision.- Named attribute arguments and properties -- only positional constructor arguments are ever forwarded, regardless of shape.
When an alias usage's forwarded argument is a supported shape but is incompatible with the resolved guard method's corresponding middle parameter -- either because the method's effective arity does not match the number of forwarded arguments, or because a middle parameter's type is incompatible with its forwarded argument -- NDLRGEN056 is reported instead. NDLRGEN051 and NDLRGEN052 retain their existing general invalid/ambiguous guard-method behavior for every other resolution failure.
Factory, DI, and AOT Integration¶
Generated-constructor generation shares one constructor-parameter model with Needlr's type registry, [GenerateFactory], and its Native AOT factory emission -- a generated constructor is never invisible to Needlr's own registration and never requires [DeferToContainer].
Automatic DI Registration¶
A type is automatically registered when every generated-constructor parameter is itself a container-resolvable service type:
[GenerateConstructor(ConstructorNullGuardMode.NonNullableReferences)]
public partial class UserService
{
private readonly IRepository _repository;
}
var serviceProvider = new Syringe()
.UsingSourceGen()
.BuildServiceProvider();
var service = serviceProvider.GetRequiredService<UserService>(); // resolves the generated constructor's dependency
[GenerateFactory] Interoperability¶
A class with a mix of injectable and runtime (value type, string, delegate) fields is excluded from automatic registration -- the same rule applied to a hand-written constructor -- but works with [GenerateFactory] exactly as it would with a hand-written constructor:
[GenerateFactory]
[GenerateConstructor]
public partial class ReportBuilder
{
private readonly IRepository _repository; // injectable
private readonly string _templateName; // runtime
}
var factory = serviceProvider.GetRequiredService<IReportBuilderFactory>();
var report = factory.Create("acme-template"); // _repository resolved from DI, templateName forwarded
The factory call site binds arguments by parameter name, not by the field declaration order relative to injectable/runtime grouping -- interleaving a runtime field before an injectable field still binds correctly. See Factory Delegates for the full factory story.
Native AOT¶
Needlr's type registry, factory emission, and generated constructor share one model, so the generated activation path adds no runtime reflection and uses direct, statically-known constructor calls. Custom guard implementations must independently remain compatible with their application's Native AOT and trimming requirements.
Primary-Constructor Limitation¶
Needlr's generator cannot add guard statements to a primary constructor you already declared, because Roslyn source generators can only add members to a partial type -- they cannot rewrite or inject statements into an existing member. This is why the feature generates an entirely new constructor from field declarations instead of trying to augment a primary constructor:
// NOT supported: a source generator cannot inject a guard into this constructor body.
public partial class UserService(IRepository repository)
{
}
Use [GenerateConstructor] with private readonly fields (not primary-constructor parameters) whenever you need generated guard clauses.
Unsupported and Deferred Scenarios¶
The following are intentionally out of scope for the initial implementation and are diagnosed rather than silently approximated:
- Records and nested types (NDLRGEN040).
- Classes with an existing explicit instance constructor (NDLRGEN041).
- Base types that require constructor arguments (NDLRGEN042).
- Keyed fields or properties -- generated-constructor generation only ever considers plain private
readonlyfields; there is no support for producing a[FromKeyedServices]-annotated generated-constructor parameter from a field or property annotation. - Properties as a source of this class constructor's parameters -- only fields are considered. Positional records can add explicitly marked properties through Generated Record Constructor Overloads.
NotEmptyas a built-in guard kind (see Built-In Guards above).- Array/
paramsand floating-point (float/double) parameterized alias arguments, and every named attribute argument or property -- diagnosed by NDLRGEN055 (see Parameterized Alias Arguments above); only the scalar constant shapes in that section's table are forwarded. IHubRegistrationPluginimplementations -- SignalR hub-registration plugins require parameterless activation and cannot use generated-constructor generation at all; see NDLRSIG003.- Raw expressions, cross-field validation, or reflection supplied as attribute arguments -- every guard is a typed guard kind or a compile-time-resolved direct method reference (with, at most, a fixed number of compiler-validated positional constants forwarded to it), never an arbitrary spliced expression or a runtime-reflected call.
Attribute Reference¶
| Attribute / Type | Target | Description |
|---|---|---|
[GenerateConstructor] |
Class | Enables constructor generation. The parameterless form is equivalent to [GenerateConstructor(ConstructorNullGuardMode.None)]. |
[GenerateConstructor(ConstructorNullGuardMode)] |
Class | Enables constructor generation with an explicit null-guard mode. |
ConstructorNullGuardMode.None |
-- | No automatic null guards (default). |
ConstructorNullGuardMode.NonNullableReferences |
-- | Automatically guards every eligible non-nullable reference-type field with NotNull. |
[ConstructorGuard(ConstructorGuardKind)] |
Field | Requests a built-in guard kind for the field. A positive kind also triggers generation on its own. |
[ConstructorGuard(Type)] |
Field | Requests a custom guard type using its conventional static void Validate(T, string) method. Also a positive trigger. |
[ConstructorGuard(Type, string)] |
Field | Requests a custom guard type and an explicit method name (typically via nameof). Also a positive trigger. |
ConstructorGuardKind.None |
-- | No guard; explicitly suppresses an applicable class-level default for this field only. |
ConstructorGuardKind.NotNull |
-- | Throws ArgumentNullException when null. |
ConstructorGuardKind.NotNullOrEmpty |
-- | Throws ArgumentNullException/ArgumentException for null/empty strings. |
ConstructorGuardKind.NotNullOrWhiteSpace |
-- | Throws ArgumentNullException/ArgumentException for null/empty/white-space-only strings. |
[ConstructorIgnore] |
Field | Excludes an otherwise-eligible field from generated-constructor parameters. Exclusion-only; has no effect without a generation trigger elsewhere on the class. |
[ConstructorGuardDefinition(Type)] |
Attribute type | Declares an application-defined field or property attribute as an alias for a direct custom guard, using its conventional Validate method. Property usages require [RecordConstructorOverloadParameter]. |
[ConstructorGuardDefinition(Type, string)] |
Attribute type | Declares an alias attribute for a custom guard with an explicit method name. |
[ConstructorGuard] allows multiple applications on the same field (AllowMultiple = true); the other attributes each allow exactly one application.
Analyzers¶
| Diagnostic | Severity | Description |
|---|---|---|
| NDLRGEN039 | Error | Generated-constructor type must be partial |
| NDLRGEN040 | Error | Generated-constructor type shape is unsupported (record or nested type) |
| NDLRGEN041 | Error | Generated-constructor conflicts with an explicit constructor |
| NDLRGEN042 | Error | Generated-constructor base type requires a parameterless constructor |
| NDLRGEN043 | Error | No eligible field for generated-constructor generation |
| NDLRGEN044 | Error | Generated-constructor parameter names collide |
| NDLRGEN045 | Warning | Constructor guard attribute has no effect |
| NDLRGEN046 | Error | Constructor guard attribute applied to an ineligible field |
| NDLRGEN047 | Error | Invalid constructor guard enum value |
| NDLRGEN048 | Error | Constructor guard incompatible with field type |
| NDLRGEN049 | Error | Custom constructor guard type is invalid |
| NDLRGEN050 | Error | Custom constructor guard method name is invalid |
| NDLRGEN051 | Error | Custom constructor guard method is invalid |
| NDLRGEN052 | Error | Custom constructor guard method is ambiguous |
| NDLRGEN053 | Error | [ConstructorGuardDefinition] target is invalid |
| NDLRGEN054 | Error | [ConstructorGuardDefinition] guard contract is unresolved |
| NDLRGEN055 | Error | Constructor guard alias usage argument is unsupported |
| NDLRGEN056 | Error | Custom constructor guard method is incompatible with forwarded alias arguments |
| NDLRSIG003 | Error | IHubRegistrationPlugin implementation cannot use generated-constructor generation |
Namespace Import¶
[GenerateConstructor], [ConstructorGuard], [ConstructorIgnore], [ConstructorGuardDefinition], ConstructorGuardKind, and ConstructorNullGuardMode are all in the NexusLabs.Needlr.Generators namespace:
This is intentional -- it signals that the feature is source-generation-only.
Full Runnable Example¶
src/Examples/SourceGen/GeneratedConstructorExample is a runnable console application exercising every scenario described on this page: bare generation, the NonNullableReferences mode resolved through a real Syringe, field-triggered implicit generation, both built-in string guards, nullable/value-type behavior, a direct custom guard type, an explicit method selector, an application-defined alias attribute, a parameterized alias attribute ([MinCount(3)]) that forwards its own positional argument onto the resolved guard call, and [GenerateFactory] interoperability.