C#教程

关注公众号 jb51net

关闭
首页 > 软件编程 > C#教程 > C# 控制反转和依赖注入

C#中控制反转和依赖注入原理及实现

作者:YuanlongWang

本文深入探讨了IoC(控制反转)和DI(依赖注入)的概念,及在面向对象编程中的应用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

控制反转(IoC)和依赖注入(DI)是面向对象编程中解决对象依赖关系的核心设计思想,目的是降低代码耦合度,提高可维护性和可测试性。下面从概念、关系、实现原理到代码示例逐步说明。

一、控制反转(IoC:Inversion of Control)

概念

控制反转是一种设计原则,核心是 “反转对象的创建和依赖管理的控制权”:

举个生活例子

二、依赖注入(DI:Dependency Injection)

概念

依赖注入是实现 IoC 原则的具体方式,核心是 “将对象的依赖通过外部传递(注入)给它,而非对象自己创建”。简单说:依赖注入 = 谁依赖谁 + 谁注入谁 + 注入什么

IoC 与 DI 的关系

三、实现原理

IoC/DI 的核心是IoC 容器(如 C# 中的 Autofac、Unity 等),其工作流程可分为 3 步:

四、C# 代码示例

下面通过 “用户服务(UserService)依赖日志组件(ILogger)” 的场景,对比传统方式和 DI 方式的区别,并模拟一个简单的 IoC 容器。

1. 传统方式(无 IoC/DI):紧耦合

// 日志接口
public interface ILogger
{
    void Log(string message);
}

// 日志实现(文件日志)
public class FileLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine($"[FileLogger] {message}");
    }
}

// 用户服务(依赖ILogger)
public class UserService
{
    // 传统方式:自己创建依赖(紧耦合!如果要换数据库日志,必须修改这里)
    private ILogger _logger = new FileLogger(); 

    public void AddUser(string username)
    {
        // 业务逻辑:添加用户
        _logger.Log($"用户 {username} 已添加"); // 使用日志
    }
}

// 调用
class Program
{
    static void Main()
    {
        UserService userService = new UserService();
        userService.AddUser("张三"); 
        // 输出:[FileLogger] 用户 张三 已添加
    }
}

问题

2. 依赖注入(DI)方式:松耦合

通过构造函数注入(最常用的注入方式),将依赖从外部传递给UserService

// 新增一个数据库日志实现
public class DatabaseLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine($"[DatabaseLogger] {message}");
    }
}

// 修改UserService:通过构造函数接收依赖(依赖抽象,不依赖具体实现)
public class UserService
{
    private ILogger _logger;

    // 构造函数注入:依赖由外部传入,而非自己创建
    public UserService(ILogger logger)
    {
        _logger = logger; // 接收注入的日志对象
    }

    public void AddUser(string username)
    {
        _logger.Log($"用户 {username} 已添加");
    }
}

// 调用:手动注入依赖(简单场景下可手动管理,复杂场景用容器)
class Program
{
    static void Main()
    {
        // 1. 创建依赖对象(可替换为DatabaseLogger)
        ILogger logger = new FileLogger(); 
        // ILogger logger = new DatabaseLogger(); 

        // 2. 注入到UserService
        UserService userService = new UserService(logger); 
        userService.AddUser("张三"); 
        // 输出:[FileLogger] 用户 张三 已添加(换DatabaseLogger则输出对应内容)
    }
}

改进

3. 模拟 IoC 容器:自动管理依赖

当项目复杂(依赖层级深,如 A 依赖 B,B 依赖 C),手动注入会非常繁琐。此时需要 IoC 容器自动处理依赖。下面模拟一个极简的 IoC 容器:

using System;
using System.Collections.Generic;
using System.Reflection;

// 模拟IoC容器
public class SimpleIocContainer
{
    // 存储注册信息:key=接口类型,value=实现类型
    private Dictionary<Type, Type> _registry = new Dictionary<Type, Type>();

    // 注册:接口 -> 实现
    public void Register<TInterface, TImplementation>() 
        where TImplementation : TInterface
    {
        _registry[typeof(TInterface)] = typeof(TImplementation);
    }

    // 解析:根据接口类型创建对象,并自动注入依赖
    public T Resolve<T>()
    {
        return (T)Resolve(typeof(T));
    }

    private object Resolve(Type type)
    {
        // 1. 如果是具体类型,直接创建(递归终止条件)
        if (!type.IsInterface)
        {
            return CreateInstance(type);
        }

        // 2. 如果是接口,查找注册的实现类型
        if (!_registry.TryGetValue(type, out Type implementationType))
        {
            throw new Exception($"未注册接口 {type.Name} 的实现");
        }

        // 3. 创建实现类的实例(并递归注入其依赖)
        return CreateInstance(implementationType);
    }

    // 通过反射创建对象,并注入构造函数依赖
    private object CreateInstance(Type type)
    {
        // 获取类型的构造函数(简化处理:取第一个构造函数)
        ConstructorInfo ctor = type.GetConstructors()[0];
        // 获取构造函数的参数(依赖)
        ParameterInfo[] parameters = ctor.GetParameters();

        // 递归解析每个参数的依赖(如UserService的构造函数参数是ILogger,解析ILogger)
        object[] parameterInstances = new object[parameters.Length];
        for (int i = 0; i < parameters.Length; i++)
        {
            parameterInstances[i] = Resolve(parameters[i].ParameterType);
        }

        // 创建对象(传入解析好的依赖)
        return ctor.Invoke(parameterInstances);
    }
}

// 使用模拟容器
class Program
{
    static void Main()
    {
        // 1. 创建容器并注册依赖
        SimpleIocContainer container = new SimpleIocContainer();
        container.Register<ILogger, FileLogger>(); // 注册ILogger -> FileLogger
        // container.Register<ILogger, DatabaseLogger>(); // 可随时切换实现

        // 2. 从容器解析UserService(容器自动注入ILogger)
        UserService userService = container.Resolve<UserService>();

        // 3. 调用方法
        userService.AddUser("李四"); 
        // 输出:[FileLogger] 用户 李四 已添加
    }
}

容器工作流程

  1. 注册:Register<ILogger, FileLogger>() 告诉容器 “ILoggerFileLogger实现”。
  2. 解析:调用Resolve<UserService>()时,容器通过反射分析UserService的构造函数,发现依赖ILogger
  3. 注入:容器先解析ILogger(根据注册找到FileLogger),创建FileLogger实例,再注入到UserService的构造函数中,最终返回UserService实例。

五、总结

在实际开发中,通常使用成熟的 IoC 容器(如 Autofac、Microsoft.Extensions.DependencyInjection),而非手动实现,但理解其原理有助于更好地使用这些工具。

到此这篇关于C#中控制反转和依赖注入原理及实现的文章就介绍到这了,更多相关C# 控制反转和依赖注入内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文