我们日常使用中会经常使用到依赖注入的情况。将服务类或者接口注入到DI容器中。并且在WPF Prism框架中会经常使用到Prism的依赖注入。Prism的依赖注入与.Net的原生注入是一致的,Prism的依赖注入相当于原生的超集。Prism的依赖注入在原生注入的情况下还增加了ViewModel的依赖注入。
除了普通的依赖注入外,我们比较多常见的差异在于单例注入和单例设计模式的差异。
首先先介绍一下原生.Net的依赖注入的作用和用法。

一、.NET 原生依赖注入

1. 原生 DI 的核心组件

.NET 原生 DI 体系封装在Microsoft.Extensions.DependencyInjection命名空间下,主要组件包括:
IServiceCollection:服务注册容器,声明 “接口 - 实现类” 映射及生命周期
IServiceProvider:服务解析容器,创建 / 获取服务实例(核心:容器接管实例生命周期)
ServiceLifetime:生命周期枚举:Singleton(单例)、Scoped(作用域)、Transient(瞬时)

2. 构造函数注入

在构造函数注入中,构造函数注入的传参从哪里来的问题,这个很多人都不了解前后因果关系。我们都知道依赖注入的参数一定是来自与DI容器的。比如我们有一个单例注入的对象,将对象注册到DI容器中,然后在其他地方进行访问。那么我现在的传参类对象到底是不是从DI容器来的,构造函数是怎么从DI容器中拿到单例类对象的。
这里就涉及到,我们怎么从DI容器获取类对象的事情。首先我们先将单例注册到容器中,然后将使用类也注册到容器中,在我们注册的操作中,就相当于容器接管了我们的类对象。下面做一个代码参考
参考Github地址:https://github.com/2825077535/DIABInstance

namespace InstanceTest
{
    public interface ILogService
    {
        // 标记单例实例的唯一ID,验证全局唯一性
        Guid InstanceId { get; }
        void Log(string message);
    }
}
namespace InstanceTest
{
    public class LogService : ILogService
    {
        // 单例实例唯一标识
        public Guid InstanceId { get; } = Guid.NewGuid();

        public void Log(string message)
        {
            Console.WriteLine($"[B项目单例 {InstanceId}] {message}");
        }
    }
}
namespace InstanceTest
{
    public class OrderService
    {
        private readonly ILogService _logService;

        // 核心:构造函数依赖注入B项目的ILogService单例
        public OrderService(ILogService logService=null)
        {
            // 若注入失效,此处会抛空引用异常
            _logService = logService ?? throw new ArgumentNullException(
                nameof(logService), "ILogService注入失败!容器未接管实例");
        }

        // 业务方法:使用注入的单例服务
        public void ProcessOrder(int orderId)
        {
            _logService.Log($"处理订单 {orderId}");
        }
    }
}
using InstanceTest;
using Microsoft.Extensions.DependencyInjection;
using System.ComponentModel;
using System.Xml.Linq;

var services = new ServiceCollection();
services.AddSingleton<ILogService, LogService>(); // 注册单例
services.AddScoped<OrderService>();
var provider = services.BuildServiceProvider();
try
{
    var order=new OrderService();

    var orderService1 = provider.GetRequiredService<OrderService>();
    orderService1.ProcessOrder(1001);

}
catch (Exception ex)
{
    Console.WriteLine($"注入失效原因:{ex.Message}");

}
if (true)
{
    var orderService1 = provider.GetRequiredService<OrderService>();
    orderService1.ProcessOrder(1002);
}

上述代码中,如果我们直接实例new一个OrderService对象,那么我们构造函数的传参就为空值。如果我们通过容器来创建OrderService的对象时,就自动将容器对应的单例类对象注入其中了。

3.单例注入和单例设计模式

单例设计模式与单例注入,主要差异来自于项目管理上的问题。
首先单例设计模式在项目上不方便进行黑盒测试。黑盒测试的2大前提,无状态依赖和可独立执行。在单例设计模式中,每一次的执行都会将当前执行的数据保存到下一次执行中。而依赖注入的单例模式是依靠于容器实现的。那么不同的容器可以关联不同的单例类。我们可以在每个黑盒测试中都创建新的容器用于管理单例类。
其次,在相当多的C#项目情况下都会用到依赖注入的形式,比如WPF的Prism框架,可以运行在ViewModel中进行单例传参,便于项目的管理。

二、Prism框架的依赖注入

我们可以通过放些Prism框架的容器来了解Prism框架的依赖注入。Prism框架的依赖注入是在原生的依赖注入上实现的。Prism框架的容器与我们平时容器不一样就是,Prism使用全局容器进行管理,在整个框架的使用上会更加友好,当然逻辑并不复杂。我们可以在自己的项目上定义全局容器来管理我们的类对象即可。
github地址:https://github.com/2825077535/PrismTest.git

namespace DI.Core.Interfaces
{
    // 模拟 Prism 的 IContainerProvider(解析服务)
    public interface IContainerProvider
    {
        // 解析服务(对应 Prism 的 Resolve)
        T Resolve<T>() where T : class;
    }
}
namespace DI.Core.Interfaces
{
    // 模拟 Prism 的 IContainerRegistry(注册服务)
    public interface IContainerRegistry
    {
        // 注册单例(对应 Prism 的 RegisterSingleton)
        void RegisterSingleton<T>() where T : class;
        // 注册瞬时
        void Register<T>() where T : class;
    }
}
namespace DI.Core
{
    // 全局容器管理类(单例,模拟 PrismApplication 的 Container)
    public static class ContainerLocator
    {
        private static IServiceProvider _serviceProvider;
        private static IServiceCollection _services;

        // 初始化容器
        public static IContainerRegistry CreateContainer()
        {
            _services = new ServiceCollection();
            return new ContainerRegistry(_services);
        }

        // 构建容器(注册完成后调用)
        public static IContainerProvider Build()
        {
            _serviceProvider = _services.BuildServiceProvider();
            return new ContainerProvider(_serviceProvider);
        }

        // 全局访问容器(对应 Prism 的 Container 属性)
        public static IContainerProvider Current => new ContainerProvider(_serviceProvider);

        // 内部实现:注册逻辑
        private class ContainerRegistry : IContainerRegistry
        {
            private readonly IServiceCollection _services;
            public ContainerRegistry(IServiceCollection services) => _services = services;

            // DI 注册单例
            public void RegisterSingleton<T>() where T : class
            {
                _services.AddSingleton<T>();
            }

            public void Register<T>() where T : class
            {
                _services.AddTransient<T>();
            }
        }

        // 内部实现:解析逻辑
        private class ContainerProvider : IContainerProvider
        {
            private readonly IServiceProvider _provider;
            public ContainerProvider(IServiceProvider provider) => _provider = provider;

            //DI 解析服务
            public T Resolve<T>() where T : class
            {
                return _provider.GetRequiredService<T>();
            }
        }
    }
}
namespace PrismUse
{
    public class MySingletonService
    {
        private readonly string _instanceId;

        //自动调用构造函数
        public MySingletonService()
        {
            _instanceId = Guid.NewGuid().ToString("N").Substring(0, 8);
            Console.WriteLine($"[PrismUse 项目] MySingletonService 单例创建,ID:{_instanceId}");
        }

        // 业务方法
        public void Execute()
        {
            Console.WriteLine($"[PrismUse 项目] 执行单例逻辑,ID:{_instanceId}");
        }

        // 验证单例唯一性
        public string GetInstanceId() => _instanceId;
    }
}
namespace PrismUse
{
    public class SingletonUser
    {
        private readonly MySingletonService _singletonService;

        // 方式 1:构造函数注入
        public SingletonUser(MySingletonService singletonService)
        {
            _singletonService = singletonService;
            Console.WriteLine("[PrismUse 项目] SingletonUser 构造注入单例成功");
        }

        // 调用单例方法
        public void UseSingleton()
        {
            _singletonService.Execute();
        }

        // 方式 2:全局容器解析
        public static void UseSingletonManually()
        {
            // 模拟 Prism 全局容器访问
            var singleton = ContainerLocator.Current.Resolve<MySingletonService>();
            Console.WriteLine($"[PrismUse 项目] 手动解析单例,ID:{singleton.GetInstanceId()}");
        }
    }
}
using DI.Core;
using DI.Core.Interfaces;
using PrismUse;

IContainerRegistry registry = ContainerLocator.CreateContainer();

// ===== 步骤 2:注册 PrismUse 项目的单例=====
// 对应 Prism 的 RegisterSingleton
registry.RegisterSingleton<MySingletonService>();
// 可选:注册 PrismUse 项目的使用类
registry.Register<SingletonUser>();

// ===== 步骤 3:构建容器=====
IContainerProvider provider = ContainerLocator.Build();

// ===== 验证 1:PrismTest 项目直接解析 PrismUse 项目单例 =====
var singleton1 = provider.Resolve<MySingletonService>();
var singleton2 = provider.Resolve<MySingletonService>();
singleton1.Execute();
// 验证单例唯一性
Console.WriteLine($"[PrismTest 项目] 单例是否唯一:{ReferenceEquals(singleton1, singleton2)}"); // True

// ===== 验证 2:PrismTest 项目解析 PrismUse 项目的使用类(自动注入)=====
var user = provider.Resolve<SingletonUser>();
user.UseSingleton();

// ===== 验证 3:PrismUse 项目内部手动解析全局容器 =====
SingletonUser.UseSingletonManually();

Console.ReadKey();

三、常见错误

1.没有注册类

我们在使用类的构造函数注入时,需要将类在容器内标记为接管模式。

//例如
services.AddScoped<OrderService>();
//将OrderService注册到services的容器中,将OrderService在容器中标记为接管模式

如果没有标记为接管模式时,容器将不能创建这个对象会直接报错。

2.直接new一个对象而不是用容器来创建

//直接new一个新的对象,容器不能正常注入
var order=new OrderService();
//使用容器来创建类对象,容器可以正常进行构造函数注入
var orderService1 = provider.GetRequiredService<OrderService>();

3.对单例类进行依赖注入导致类对象生命周期不终止

将IServiceScopeFactory 注入到单例类容器中,在使用scope.ServiceProvider.GetRequiredService();创建一个只有在作用域内的新的注入类对象

//假设OrderService为容器的单例类
public class OrderService
{
    private readonly IServiceScopeFactory _scopeFactory;
    public OrderService(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public void ProcessOrder(int orderId)
    {
        using var scope = _scopeFactory.CreateScope();
        var logService = scope.ServiceProvider.GetRequiredService<ILogService>();
        logService.Log($"处理订单 {orderId}");
    }
}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐