Field, Property, and Method Injection
SourceGen.Ioc generates factory method registration when [IocInject] attribute is used on fields, properties, methods, or constructor parameters.
NOTE
FieldInject is not enabled in the default SourceGenIocFeatures value. The default is Register,Container,PropertyInject,MethodInject. To use [IocInject] on fields, add FieldInject in your project configuration (see MSBuild Configuration):
<PropertyGroup>
<SourceGenIocFeatures>Register,Container,PropertyInject,FieldInject,MethodInject</SourceGenIocFeatures>
</PropertyGroup>Property & Field Injection
Use [IocInject] to inject dependencies into properties or fields:
[IocRegister<IMyService>]
internal class MyService : IMyService
{
[IocInject]
public ILogger Logger { get; init; } = null!;
[IocInject]
internal IConfiguration configuration = null!;
}Generated Code
// <auto-generated/>
services.AddSingleton<global::MyNamespace.MyService>((global::System.IServiceProvider sp) =>
{
var s0_p0 = sp.GetRequiredService<global::MyNamespace.ILogger>();
var s0_p1 = sp.GetRequiredService<global::MyNamespace.IConfiguration>();
var s0 = new global::MyNamespace.MyService() { Logger = s0_p0, configuration = s0_p1 };
return s0;
});
services.AddSingleton<global::MyNamespace.IMyService>((global::System.IServiceProvider sp) => sp.GetRequiredService<global::MyNamespace.MyService>());Method Injection
Use [IocInject] on a method to call it after object creation:
[IocRegister<IMyService>]
internal class MyService : IMyService
{
private ILogger logger = null!;
private IConfiguration config = null!;
// Must be void return type
[IocInject]
public void Initialize(ILogger logger, IConfiguration config)
{
this.logger = logger;
this.config = config;
}
}Generated Code
// <auto-generated/>
services.AddSingleton<global::MyNamespace.MyService>((global::System.IServiceProvider sp) =>
{
var s0_m0 = sp.GetRequiredService<global::MyNamespace.ILogger>();
var s0_m1 = sp.GetRequiredService<global::MyNamespace.IConfiguration>();
var s0 = new global::MyNamespace.MyService();
s0.Initialize(s0_m0, s0_m1);
return s0;
});
services.AddSingleton<global::MyNamespace.IMyService>((global::System.IServiceProvider sp) => sp.GetRequiredService<global::MyNamespace.MyService>());Constructor Selection
Use [IocInject] on a constructor to specify which constructor to use for DI:
[IocRegister<IMyService>]
internal class MyService(IDependency1 dep1, IDependency2 dep2) : IMyService
{
private readonly IDependency1 dep1 = dep1;
private readonly IDependency2 dep2 = dep2;
// Use this constructor instead of the primary constructor
[IocInject]
internal MyService(IDependency1 dep1)
: this(dep1, new DefaultDependency2())
{
}
}Generated Code
// <auto-generated/>
services.AddSingleton<global::MyNamespace.MyService>((global::System.IServiceProvider sp) =>
{
var p0 = sp.GetRequiredService<global::MyNamespace.IDependency1>();
var s0 = new global::MyNamespace.MyService(p0);
return s0;
});
services.AddSingleton<global::MyNamespace.IMyService>((global::System.IServiceProvider sp) => sp.GetRequiredService<global::MyNamespace.MyService>());NOTE
If [IocInject] does not exist on any constructor, there are 2 situations:
- If no need to generate factory method (no field/property/method injection or decorator), will let
IServiceProviderselect constructor. - If factory method generation is needed (due to field/property/method injection or decorator), will use primary constructor, then the constructor with the most parameters.
Async Method Injection
Use [IocInject] on a method that returns Task to perform async initialization after construction. The method is awaited after all synchronous injection steps.
WARNING
AsyncMethodInject is not enabled by default. Add it to SourceGenIocFeatures in your project file:
<PropertyGroup>
<SourceGenIocFeatures>Register,Container,PropertyInject,MethodInject,AsyncMethodInject</SourceGenIocFeatures>
</PropertyGroup>AsyncMethodInject requires MethodInject to be enabled. If AsyncMethodInject is enabled without MethodInject, the analyzer reports SGIOC026.
Classification Rules
| Return Type | Classification |
|---|---|
void | Synchronous method injection (InjectionMemberType.Method) |
Task (non-generic) | Async method injection (InjectionMemberType.AsyncMethod) |
Task<T> | Not supported — not a valid injection method return type |
ValueTask / ValueTask<T> | Not supported — not a valid injection method return type |
Injection Stage Order
The generator emits member injection in a fixed stage order. Source declaration order applies within each stage:
| Stage | Members |
|---|---|
| 1 | Properties |
| 2 | Fields |
| 3 | Synchronous methods (void) |
| 4 | Async methods (Task) — awaited last |
Example
using System.Threading.Tasks;
public interface IMyService;
public interface IDependency1;
public interface IDependency2;
public interface IDependency3;
[IocRegister<IMyService>(ServiceLifetime.Singleton)]
internal class MyService : IMyService
{
[IocInject]
public IDependency1 Dep1 { get; set; } = default!;
[IocInject]
public void SyncInit(IDependency2 dep2)
{
}
[IocInject]
public async Task AsyncInit(IDependency3 dep3)
{
await Task.CompletedTask;
}
}Generated Code
// <auto-generated/>
services.AddSingleton<global::System.Threading.Tasks.Task<global::MyNamespace.MyService>>((global::System.IServiceProvider sp) =>
{
async global::System.Threading.Tasks.Task<global::MyNamespace.MyService> Init()
{
var s0_p0 = sp.GetRequiredService<global::MyNamespace.IDependency1>(); // Stage 1: properties
var s0_m1 = sp.GetRequiredService<global::MyNamespace.IDependency2>();
var s0_m2 = sp.GetRequiredService<global::MyNamespace.IDependency3>();
var s0 = new global::MyNamespace.MyService() { Dep1 = s0_p0 };
s0.SyncInit(s0_m1); // Stage 3: sync methods
await s0.AsyncInit(s0_m2); // Stage 4: async methods
return s0;
}
return Init();
});
// Forwarding registration: Task<IMyService> → Task<MyService>
services.AddSingleton<global::System.Threading.Tasks.Task<global::MyNamespace.IMyService>>(async (global::System.IServiceProvider sp) => await sp.GetRequiredService<global::System.Threading.Tasks.Task<global::MyNamespace.MyService>>());NOTE
When a service has async inject methods, the registration type changes from T to Task<T>. Consumers that depend on this service should inject Task<T> and await the result. See Wrapper Types — Task<T> for consumer-side usage.
Diagnostics
| ID | Severity | Description |
|---|---|---|
| SGIOC007 | Error | Invalid [IocInject] usage. The attribute cannot be applied to static members, non-accessible members (private, protected, private protected — but protected internal is accepted), properties without a setter or with an inaccessible setter, readonly fields, generic methods, non-ordinary methods (e.g., constructors, operators), or methods with unsupported return types (only void and non-generic Task when AsyncMethodInject is enabled are accepted). |
| SGIOC022 | Warning | [IocInject] is ignored when the corresponding feature (PropertyInject, FieldInject, MethodInject, or AsyncMethodInject) is disabled in SourceGenIocFeatures. |
| SGIOC026 | Error | AsyncMethodInject feature requires MethodInject to be enabled. |
| SGIOC023 | Error | An element in the InjectMembers array is not in a recognized format. Each element must be nameof(member) or new object[] { nameof(member), key [, KeyType] }. |
| SGIOC024 | Error | A member specified via InjectMembers is not injectable (e.g., static, non-accessible members (private, protected, private protected — but protected internal is accepted), no setter or inaccessible setter, readonly field, generic method, non-ordinary method, or method with unsupported return type). |
InjectMembers: Attribute-Level Injection Without [IocInject]
When you cannot add [IocInject] directly to a type's members (e.g., a third-party type), use the InjectMembers property on [IocRegisterFor] to specify injection points from the registration site:
// Register ThirdPartyService without modifying it
[IocRegisterFor(typeof(ThirdPartyService),
InjectMembers = [nameof(ThirdPartyService.Logger)])]
public static class ThirdPartyModule { }Each element is one of:
| Format | Description |
|---|---|
nameof(T.Member) | Inject without a key (resolves T from the container) |
new object[] { nameof(T.Member), "key" } | Inject a keyed service |
new object[] { nameof(T.Member), nameof(SomeKey), KeyType.Csharp } | Inject using a C# expression key |
NOTE
When the same member is specified in both InjectMembers and via [IocInject] on the member itself, [IocInject] takes priority.