DI 容器是复杂的库。建造它们需要数年时间并维护它们数十年。但是为了演示它们的工作原理,您可以用几行代码编写一个简单的实现。
在其核心,DI Container 通常会包装一个字典,System.Type
并将其作为键,值将是一些允许您创建该类型的新实例的对象。当您编写一个简单的实现时System.Func<object>
就可以了。这是一个包含多个Register
方法的示例,包括泛型和非泛型GetInstance
方法,并允许Auto-Wiring:
public class Container
{
private readonly Dictionary<Type, Func<object>> regs = new();
public void Register<TService, TImpl>() where TImpl : TService =>
regs.Add(typeof(TService), () => this.GetInstance(typeof(TImpl)));
public void Register<TService>(Func<TService> factory) =>
regs.Add(typeof(TService), () => factory());
public void RegisterInstance<TService>(TService instance) =>
regs.Add(typeof(TService), () => instance);
public void RegisterSingleton<TService>(Func<TService> factory)
{
var lazy = new Lazy<TService>(factory);
Register(() => lazy.Value);
}
public object GetInstance(Type type)
{
if (regs.TryGetValue(type, out Func<object> fac)) return fac();
else if (!type.IsAbstract) return this.CreateInstance(type);
throw new InvalidOperationException("No registration for " + type);
}
private object CreateInstance(Type implementationType)
{
var ctor = implementationType.GetConstructors().Single();
var paramTypes = ctor.GetParameters().Select(p => p.ParameterType);
var dependencies = paramTypes.Select(GetInstance).ToArray();
return Activator.CreateInstance(implementationType, dependencies);
}
}
您可以按如下方式使用它:
var container = new Container();
container.RegisterInstance<ILogger>(new FileLogger("c:\\logs\\log.txt"));
// SqlUserRepository depends on ILogger
container.Register<IUserRepository, SqlUserRepository>();
// HomeController depends on IUserRepository
// Concrete instances don't need to be resolved
container.GetInstance(typeof(HomeController));
警告:你不应该使用上面给出的这种幼稚和简单的实现。它缺乏成熟的 DI 库为您提供的许多重要功能,但与使用纯 DI(即手动连接对象图)相比没有任何优势。你失去了编译时支持,没有得到任何回报。
当您的应用程序很小时,您应该从 Pure DI 开始,一旦您的应用程序和 DI 配置增长到维护您的Composition Root变得很麻烦,您可以考虑切换到已建立的 DI 库之一。
以下是与已建立的库相比,这种天真的实现缺乏的一些特性:
- 自动注册:通过在单行中注册一组类型来应用约定优于配置的能力,而不必手动注册每种类型。
- 拦截:为一系列类型应用装饰器或拦截器的能力
- 泛型:将开放泛型抽象映射到开放泛型实现
- 集成:将库与常用应用平台(如 ASP.NET MVC、Web API、.NET Core 等)结合使用
- 生命周期管理:使用自定义生活方式注册类型的能力(例如,Scoped 或 Per Request)。
- 错误处理:检测错误配置,例如循环依赖。这种简单的实现会引发堆栈溢出异常。
- 验证:用于验证配置正确性(以补偿编译时支持的损失)和诊断常见配置错误的功能或工具。
- 性能:使用这种简单的实现构建大型对象图会很慢(例如,由于产生的垃圾量很大,会导致很大的 GC 压力)。
这些特性和能力允许您在使用 DI 容器时保持 DI 配置的可维护性。