Skip to main content

๐Ÿท๏ธ Attribute Reference

InjectableAttributeโ€‹

Marks a concrete class for generation-based registration.

[Injectable(ServiceLifetime.Transient)]
public sealed class ConcreteService { }

โš™๏ธ Key membersโ€‹

  • โฑ๏ธ Lifetime: DI lifetime (Singleton, Scoped, Transient)
  • ๐Ÿ“Š Group: primary ordering key (default int.MaxValue)
  • ๐Ÿ”ข Order: secondary ordering key inside group (default int.MaxValue)
  • ๐Ÿ”‘ Key: optional keyed-service identifier (default null)

ServiceType in non-generic form is always null.

InjectableAttribute<TService>โ€‹

Use when you want an explicit contract through a closed generic argument.

[Injectable<IMyService>(ServiceLifetime.Singleton)]
public sealed class MyService : IMyService { }

ServiceType is automatically derived as typeof(TService).

ServiceInjectionAttributeโ€‹

Marks interfaces or abstract types that should be discoverable as injectable contracts while GenDI traverses implementations and inheritance.

[ServiceInjection]
public interface IMyService { }

InjectAttributeโ€‹

๐ŸŒŸ [Inject] is the heart of GenDI's property injection model. Mark any required init-only property with it and GenDI generates the resolution code at compile time โ€” no constructor, no private field, no assignment boilerplate.

๐Ÿค” Why property injection?โ€‹

Every constructor parameter forces three lines of code: the parameter itself, a private backing field, and an assignment. As services grow, constructors become the loudest part of a file. Property injection collapses all three into one declarative line:

// โŒ Without property injection โ€” 3 lines of ceremony per dependency
private readonly IOrderRepository _repo;
public MyService(IOrderRepository repo) { _repo = repo; }

// โœ… With [Inject] โ€” 1 declarative line, zero private fields
[Inject] public required IOrderRepository Repo { get; init; }

The required modifier is enforced by the C# compiler: GenDI cannot generate an instance without providing every [Inject] property. This gives you the same compile-time guarantee as constructor injection โ€” you cannot accidentally skip a dependency in the generated initializer. Note: if a service is not registered in the DI container, GetRequiredService<T>() will still throw at runtime, just like standard DI.

๐Ÿ”ง Basic usageโ€‹

[Inject]
public required IOtherService OtherService { get; init; }

๐Ÿ”‘ Keyed property injectionโ€‹

[Inject(Key = "primary")]
public required IOtherService OtherService { get; init; }

Constructor parameters can use native DI keyed resolution:

public Consumer([FromKeyedServices("primary")] IOtherService otherService) { }

๐Ÿ† Benefits at a glanceโ€‹

ScenarioConstructor injection[Inject] property injection
โž• Add dependencyEdit ctor + field + assignmentAdd one property
๐Ÿ‘€ Read class structureScan ctor + private fieldsProperties visible immediately
๐Ÿงช Unit test partial wiringMust satisfy full ctorAssign only needed properties
๐Ÿ”€ Avoid parameter order bugsPosition-sensitiveNamed โ€” no ordering risk

โœ… Requirementsโ€‹

  • Property must be get; init;
  • Property must be public or internal

GenDICoverationAttributeโ€‹

Assembly-level toggle for generated extension coverage behavior.

[assembly: GenDI.GenDICoveration(false)]
  • โœ… true => include generated extension in coverage
  • โ›” false => append [ExcludeFromCodeCoverage] to generated extension