31

如何在不使用任何内置或库(如 Autofac、Ninject 等)的情况下创建简单的 Dependency Resolver?

这是我的面试问题。

我写了这个简单的代码,他们说它看起来不太好。它就像非常硬编码的想法。

public interface IRepository { }
interface IDataProvider
{
    List<string> GetData();
}
public class SQLDataProvider : IDataProvider
{
    private readonly IRepository _repository { get; set; }
    public SQLDataProvider(IRepository repository)
    {
        _repository = repository;
    }
    public List<string> GetData()
    {
        return new List<string> { "" };
    }
}
public class MockDataProvider : IDataProvider
{
    public List<string> GetData()
    {
        return new List<string> { "" };
    }
}
class Program
{
 static void Main(string[] args)
 {
    string targetClass = "SQLDataProvider";
    //Here i need to supply IRepository instance too 
   IDataProvider dataProvider = 
   (IDataProvider)Activator.CreateInstance(typeof(IDataProvider), targetClass);

  }
}

我做了什么更好的代码并为构造函数参数提供其他对象实例?

4

2 回答 2

46

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 配置的可维护性。

于 2013-03-30T09:36:33.873 回答
6

已经有几年的历史了,但是 Ayende 曾经写过一篇关于这个的博客文章:
Building an IoC container in 15 lines of code

但这只是最简单的可能实现。
Ayende 本人在他的下一篇文章中表示,现有的 IoC 容器可以做的事情远不止返回类实例——这就是它变得复杂的地方。
正如“相信我——我是一名医生”在他的评论中已经说过的那样:实现一个完整的IoC 容器绝非易事。

于 2013-03-30T09:23:58.020 回答