学 .NET 的时候一直在用依赖注入,但对 IoC 容器到底干了什么一直是模模糊糊的,只知道注册一下然后构造函数里就能拿到实例了,具体怎么实现的完全不清楚。所以就试着自己写了一个最简单的 IoC 容器,写完之后确实理解了不少。


先搞清楚 IoC 到底在干嘛

其实就三件事:

  1. 你告诉容器:ILogger 对应 ConsoleLogger,这叫注册
  2. 你问容器要一个 IUserService,容器发现它的构造函数需要 IUserRepositoryILogger,就自己去递归把这些依赖全部创建好,组装完了再给你,这叫解析
  3. 有些东西你想全局用同一个实例(比如日志),有些你想每次都 new 一个新的,这叫生命周期管理

搞懂这三个事情,IoC 就没什么神秘的了。

容器代码

直接贴了,就一个类:

using System;
using System.Collections.Generic;
using System.Linq;

public class MyContainer
{
    enum Lifetime { Transient, Singleton }

    Dictionary<Type, Type> _typeMap = new Dictionary<Type, Type>();
    Dictionary<Type, Lifetime> _lifetimeMap = new Dictionary<Type, Lifetime>();
    Dictionary<Type, object> _singletonMap = new Dictionary<Type, object>();

    public void AddTransient<TService, TImpl>() where TImpl : TService
    {
        _typeMap[typeof(TService)] = typeof(TImpl);
        _lifetimeMap[typeof(TService)] = Lifetime.Transient;
    }

    public void AddSingleton<TService, TImpl>() where TImpl : TService
    {
        _typeMap[typeof(TService)] = typeof(TImpl);
        _lifetimeMap[typeof(TService)] = Lifetime.Singleton;
    }

    public T Resolve<T>()
    {
        return (T)Resolve(typeof(T));
    }

    object Resolve(Type type)
    {
        if (!_typeMap.ContainsKey(type))
            throw new Exception("没有注册类型: " + type.Name);

        var implType = _typeMap[type];
        var lifetime = _lifetimeMap[type];

        // 单例的话看看有没有已经创建好的
        if (lifetime == Lifetime.Singleton)
        {
            if (_singletonMap.ContainsKey(type))
                return _singletonMap[type];
        }

        // 找构造函数,参数最多的那个
        var ctor = implType.GetConstructors()
            .OrderByDescending(c => c.GetParameters().Length)
            .First();

        // 把构造函数的每个参数都递归解析出来
        var paramInfos = ctor.GetParameters();
        var paramValues = new object[paramInfos.Length];
        for (int i = 0; i < paramInfos.Length; i++)
        {
            paramValues[i] = Resolve(paramInfos[i].ParameterType);
        }

        // 创建实例
        var obj = Activator.CreateInstance(implType, paramValues);

        if (lifetime == Lifetime.Singleton)
            _singletonMap[type] = obj;

        return obj;
    }
}

写得有点乱,三个 Dictionary 分开存的,其实可以合成一个 Tuple 或者弄个内部类,但当时没想那么多,能跑就没改了。

测试用的接口和实现类

弄了三层依赖关系:UserService 依赖 UserRepositoryILoggerUserRepository 又依赖 ILogger

public interface ILogger
{
    void Log(string msg);
}

public class ConsoleLogger : ILogger
{
    public void Log(string msg)
    {
        Console.WriteLine("[LOG] " + msg);
    }
}

public interface IUserRepository
{
    string GetUser(int id);
}

public class UserRepository : IUserRepository
{
    ILogger _logger;

    public UserRepository(ILogger logger)
    {
        _logger = logger;
    }

    public string GetUser(int id)
    {
        _logger.Log("查询用户 " + id);
        return "User_" + id;
    }
}

public interface IUserService
{
    void PrintUser(int id);
}

public class UserService : IUserService
{
    IUserRepository _repo;
    ILogger _logger;

    public UserService(IUserRepository repo, ILogger logger)
    {
        _repo = repo;
        _logger = logger;
    }

    public void PrintUser(int id)
    {
        var user = _repo.GetUser(id);
        _logger.Log("找到了: " + user);
    }
}

跑起来

static void Main(string[] args)
{
    var container = new MyContainer();

    container.AddSingleton<ILogger, ConsoleLogger>();
    container.AddTransient<IUserRepository, UserRepository>();
    container.AddTransient<IUserService, UserService>();

    var service = container.Resolve<IUserService>();
    service.PrintUser(1);

    // 验证单例
    var logger1 = container.Resolve<ILogger>();
    var logger2 = container.Resolve<ILogger>();
    Console.WriteLine("是同一个实例吗: " + ReferenceEquals(logger1, logger2));

    Console.ReadLine();
}

输出:

[LOG] 查询用户 1
[LOG] 找到了: User_1
是同一个实例吗: True

你只 Resolve 了一个 IUserService,容器自己把 UserRepositoryConsoleLogger 全创建好了,构造函数的参数自动填上了。单例也生效了,两次 Resolve<ILogger>() 拿到的是同一个对象。

它到底干了什么

Resolve<IUserService>() 的时候,容器内部走了这么一圈:

  1. IUserService → 找到映射 UserService
  2. UserService 的构造函数,需要 IUserRepositoryILogger
  3. 先解析 IUserRepository → 映射到 UserRepository → 它的构造函数需要 ILogger
  4. 解析 ILogger → 映射到 ConsoleLogger → 无参构造函数,直接 Activator.CreateInstance 创建
  5. ConsoleLogger 实例去创建 UserRepository
  6. 再解析一次 ILogger,因为是单例,直接返回刚才那个 ConsoleLogger
  7. UserRepositoryConsoleLogger 去创建 UserService

就是一个递归,一直递归到没有依赖的叶子节点,然后一层层往回构建。Resolve 方法里那个 for 循环递归调用自己就是整个 IoC 的核心,真的就这么点东西。

这个 demo 缺了什么

真正的 IoC 框架比如 Autofac、Microsoft.Extensions.DependencyInjection,在这个基础上还做了不少事情:

  • Scoped 生命周期:比如一次 HTTP 请求内共享同一个实例,请求结束就销毁,我这里只有 Transient 和 Singleton
  • 线程安全:我这几个 Dictionary 完全没加锁,多线程同时 Resolve 会出问题
  • 循环依赖检测:A 依赖 B,B 又依赖 A,我这代码会直接栈溢出,正经框架会报一个明确的错误
  • IDisposable 管理:容器释放的时候应该把它创建的 IDisposable 对象也一起释放掉

不过作为理解原理的 demo 够用了,骨架就是这 50 多行代码。