如何通过Autofac自动将构造函数参数赋值给成员变量?
解决Autofac构造注入样板代码的两种方案
好问题!我之前也被这种重复的构造注入样板代码折腾过,Autofac虽然没有直接内置“命名约定自动赋值成员”的功能,但我们可以通过自定义扩展来实现,同时也支持属性注入的方式来减少冗余。下面给你详细说明两种方案:
一、通过自定义模块实现构造参数到成员变量的自动赋值(命名约定)
Autofac允许我们通过Module扩展容器的行为,我们可以编写一个自定义模块,在组件激活时自动将构造函数参数赋值给对应的私有/受保护成员变量(比如参数名foo对应字段_foo)。
实现代码示例:
public class AutoAssignConstructorParametersModule : Module { protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration) { registration.Activating += (sender, e) => { // 获取当前组件的构造函数(这里取参数最多的那个,符合Autofac默认构造函数选择逻辑) var constructorParams = e.Component.Activator.LimitType.GetConstructors() .OrderByDescending(c => c.GetParameters().Length) .First() .GetParameters(); foreach (var param in constructorParams) { // 按照命名约定查找对应的字段(参数名foo对应_foo) var field = e.Instance.GetType().GetField($"_{param.Name}", BindingFlags.Instance | BindingFlags.NonPublic); if (field != null) { // 从激活参数中找到对应类型的值并赋值给字段 var paramValue = e.Parameters.First(p => p.ParameterType == param.ParameterType).Value; field.SetValue(e.Instance, paramValue); } } }; } }
使用方式:
在构建容器时注册这个模块:
var builder = new ContainerBuilder(); builder.RegisterModule<AutoAssignConstructorParametersModule>(); // 注册你的服务 builder.RegisterType<Foo>().As<IFoo>(); builder.RegisterType<Bar>().As<IBar>(); builder.RegisterType<Baz>().As<IBaz>(); builder.RegisterType<MyClass>(); var container = builder.Build();
这样你的MyClass就可以省略手动赋值的代码了:
public class MyClass { private IFoo _foo; private IBar _bar; private IBaz _baz; // 只需要声明构造函数,不需要手动赋值 public MyClass(IFoo foo, IBar bar, IBaz baz) { // 空构造函数体即可,模块会自动帮你完成赋值 } }
二、使用属性注入(自定义标记属性)
如果你更倾向于直接标记需要注入的成员变量,Autofac支持属性注入,我们可以自定义一个属性来标记哪些成员需要被注入,然后配置容器自动处理这些属性。
步骤1:定义自定义注入属性
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class DependencyInjectedAttribute : Attribute { }
步骤2:编写模块扫描并注入标记的成员
public class AttributeBasedInjectionModule : Module { protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration) { registration.Activating += (sender, e) => { var type = e.Instance.GetType(); // 处理标记了[DependencyInjected]的字段 var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) .Where(f => f.GetCustomAttribute<DependencyInjectedAttribute>() != null); foreach (var field in fields) { var service = e.Context.Resolve(field.FieldType); field.SetValue(e.Instance, service); } // 处理标记了[DependencyInjected]的属性 var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) .Where(p => p.GetCustomAttribute<DependencyInjectedAttribute>() != null && p.CanWrite); foreach (var prop in properties) { var service = e.Context.Resolve(prop.PropertyType); prop.SetValue(e.Instance, service); } }; } }
使用方式:
注册模块后,直接在成员上标记属性即可,甚至可以不需要构造函数:
public class MyClass { [DependencyInjected] private IFoo _foo; [DependencyInjected] private IBar _bar; [DependencyInjected] private IBaz _baz; // 不需要构造函数也能完成依赖注入 }
注意事项
- 构造注入仍然是Autofac官方推荐的依赖注入方式,因为它能明确组件的依赖关系,且保证组件创建时依赖已就绪。上面的自动赋值方案只是减少样板代码,本质还是基于构造注入的扩展。
- 属性注入适合一些可选依赖或者框架级别的场景,但要注意过度使用会导致依赖关系不清晰,不利于单元测试。
- 自定义模块依赖反射操作,如果你有大量组件需要处理,可能会有轻微的性能影响,但大部分业务应用场景下这个影响可以忽略。
内容的提问来源于stack exchange,提问作者Jez
相关产品推荐
相关产品推荐

