在 C# 中理解与实践依赖注入(DI)

什么是 DI?

依赖注入(Dependency Injection,简称 DI),是一种通过将对象所依赖的组件(或服务)从外部传入,而不是在内部直接创建,从而实现控制反转(IoC)的设计模式。

  • 控制反转(IoC):将对象的创建、配置、生命周期管理等责任,从类内部“反转”到外部容器或调用者。
  • 依赖注入(DI):IoC 的具体实现方式之一,通过构造函数、属性或方法将依赖传入。

为什么需要 DI?

  1. 降低耦合

    • 传统写法里,类内部直接 new 出依赖对象,两个类强绑定在一起。
    • DI 让类只依赖接口或抽象,不关心具体实现,方便后期扩展或替换。
  2. 提高可测试性

    • 通过接口编程,可以在单元测试时注入“假实现”(Mock),无需依赖真实数据库、网络等。
  3. 集中管理依赖

    • 使用容器在启动时统一注册所有服务,后续只需从容器获取,无需手动 new

常见的注入方式

  1. 构造函数注入(Constructor Injection)
    最常用:依赖通过构造函数参数传入,确保依赖在对象创建时就可用。

  2. 属性注入(Property Injection)
    通过 public 属性设置依赖,适合可选依赖或循环依赖场景,但不如构造注入直观。

  3. 方法注入(Method Injection)
    在调用某个方法时,将依赖作为参数传入,使用场景较少。


手动实现构造函数注入示例

// 定义消息服务接口
public interface IMessageService
{
    void Send(string to, string message);
}

// 邮件发送服务
public class EmailService : IMessageService
{
    public void Send(string to, string message)
    {
        // 模拟发送邮件
        Console.WriteLine($"[Email] 发给 {to}:{message}");
    }
}

// 短信发送服务
public class SmsService : IMessageService
{
    public void Send(string to, string message)
    {
        // 模拟发送短信
        Console.WriteLine($"[SMS] 发给 {to}:{message}");
    }
}

// 通知管理器,依赖 IMessageService
public class NotificationManager
{
    private readonly IMessageService _messageService;

    // 构造函数注入
    public NotificationManager(IMessageService messageService)
    {
        _messageService = messageService;
    }

    public void Notify(string user, string content)
    {
        // 调用接口,无需关心具体实现
        _messageService.Send(user, content);
    }
}

// 程序入口:手动组装依赖
class Program
{
    static void Main(string[] args)
    {
        // 选择具体实现:更换为 SmsService 时,只需改这一行
        IMessageService service = new EmailService();
        var manager = new NotificationManager(service);
        manager.Notify("alice@example.com", "你好,Alice!");
    }
}

使用 .NET 内置容器

从 .NET Core / .NET 6 开始,微软提供了轻量级的 DI 容器,位于 Microsoft.Extensions.DependencyInjection 包中:

using Microsoft.Extensions.DependencyInjection;

class Program
{
    static void Main(string[] args)
    {
        // 1. 创建 ServiceCollection,用于注册服务
        var services = new ServiceCollection();

        // 2. 注册服务:接口 -> 实现
        services.AddTransient<IMessageService, EmailService>();   // 每次请求创建新实例
        services.AddTransient<NotificationManager>();            // 注册 NotificationManager,自身依赖会被自动注入

        // 3. 构建 ServiceProvider
        var provider = services.BuildServiceProvider();

        // 4. 从容器中获取 NotificationManager 并使用
        var manager = provider.GetService<NotificationManager>();
        manager.Notify("bob@example.com", "你好,Bob!");
    }
}
  • AddTransient<TInterface, TImplementation>()每次请求都创建新的实例
  • AddSingleton<TInterface, TImplementation>()全局单例,第一次创建后复用同一实例
  • AddScoped<TInterface, TImplementation>()每个作用域(如 ASP.NET Core 的一次 HTTP 请求)内单例

在 WPF 中使用 DI

在 WPF 应用中,一般在 App.xaml.cs 中配置容器,然后通过构造函数注入到窗口或 ViewModel:

// App.xaml.cs
public partial class App : Application
{
    private readonly ServiceProvider _serviceProvider;

    public App()
    {
        // 1. 创建 ServiceCollection
        var services = new ServiceCollection();

        // 2. 注册服务与窗口
        services.AddSingleton<IMessageService, EmailService>();  // 单例消息服务
        services.AddTransient<MainWindow>();                     // 主窗口注册为瞬时服务

        // 3. 构建 ServiceProvider
        _serviceProvider = services.BuildServiceProvider();
    }

    protected override void OnStartup(StartupEventArgs e)
    {
        // 从容器中获取 MainWindow 并显示
        var mainWindow = _serviceProvider.GetService<MainWindow>();
        mainWindow.Show();
        base.OnStartup(e);
    }
}
// MainWindow.xaml.cs
public partial class MainWindow : Window
{
    private readonly IMessageService _messageService;

    // 构造函数注入 IMessageService
    public MainWindow(IMessageService messageService)
    {
        InitializeComponent();
        _messageService = messageService;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        // 使用注入的服务发送消息
        _messageService.Send("user@example.com", "来自 WPF 的消息");
    }
}

小结

  • 依赖注入(DI):将组件的创建和管理交给外部容器,实现控制反转(IoC)。
  • 主要优点
    • 降低模块间耦合
    • 提高单元测试可 mock 性
    • 集中管理依赖、统一生命周期
  • 常见注入方式
    1. 构造函数注入(Constructor Injection)
    2. 属性注入(Property Injection)
    3. 方法注入(Method Injection)
  • .NET 内置容器
    • 使用 ServiceCollection 注册服务(Transient / Scoped / Singleton)
    • 调用 BuildServiceProvider() 构建容器并自动注入
  • WPF 集成要点
    • App.xaml.cs 中初始化容器
    • 将窗口和 ViewModel 注册到容器
    • 在窗口或 ViewModel 构造函数中直接接收依赖

在 Prism 中使用依赖注入

Prism 是一个经典的 WPF/Xamarin MVVM 框架,自带对 Unity、DryIoc、Autofac 等容器的支持。以下示例基于 Prism.DryIoc:

// App.xaml.cs
public partial class App : PrismApplication
{
    protected override Window CreateShell()
    {
        // 从容器中解析 MainWindow 并返回
        return Container.Resolve<MainWindow>();
    }

    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        // 注册服务:接口 -> 实现
        containerRegistry.Register<IMessageService, EmailService>();
        // 注册视图与 ViewModel 自动关联
        containerRegistry.RegisterForNavigation<MainView, MainViewModel>();
    }
}

// MainViewModel.cs
public class MainViewModel : BindableBase
{
    private readonly IMessageService _messageService;
    public DelegateCommand SendCommand { get; }

    // 构造函数注入 IMessageService
    public MainViewModel(IMessageService messageService)
    {
        _messageService = messageService;
        SendCommand = new DelegateCommand(OnSend);
    }

    private void OnSend()
    {
        _messageService.Send("prism@example.com", "来自 Prism 的消息");
    }
}
  • PrismApplication会在启动时自动构建容器
  • RegisterTypes方法中使用IContainerRegistry注册所有依赖
  • Container.Resolve<T>()或构造函数自动注入

小贴士

  • 选择容器:Prism 推荐使用 DryIoc、Unity 等成熟容器;简单项目可直接用 .NET 内置容器。
  • 模块化:Prism 支持模块化加载,IModule 接口中也可注册服务。
  • 性能考虑:大型项目下,DryIoc 性能优;Autofac 功能丰富但相对较重。

在 MVVM Toolkit 中使用 DI

// App.xaml.cs
public partial class App : Application
{
    private IServiceProvider _provider;

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        // 配置 ServiceCollection
        var services = new ServiceCollection();
        services.AddSingleton<IMessageService, EmailService>();         // 单例服务
        services.AddTransient<MainViewModel>();                        // 注册 ViewModel
        services.AddTransient<MainWindow>(sp =>                        // 注册窗口并在构造时注入 VM
        {
            return new MainWindow
            {
                DataContext = sp.GetRequiredService<MainViewModel>()
            };
        });

        _provider = services.BuildServiceProvider();

        // 从容器中获取窗口并显示
        var window = _provider.GetRequiredService<MainWindow>();
        window.Show();
    }
}

// MainViewModel.cs
public partial class MainViewModel : ObservableObject
{
    private readonly IMessageService _messageService;

    // 构造函数注入
    public MainViewModel(IMessageService messageService)
    {
        _messageService = messageService;
    }

    [ICommand] // CommunityToolkit.Mvvm 源码生成命令
    private void Send()
    {
        _messageService.Send("toolkit@example.com", "来自 MVVM Toolkit 的消息");
    }
}
  • 使用 Microsoft.Extensions.DependencyInjection 管理生命周期

  • ObservableObject + [ICommand] 简化属性和命令的定义

  • 在窗口创建时,将 MainViewModel 注入到 DataContext

posted @ 2025-04-21 16:46  吃饺子不沾醋  阅读(217)  评论(0)    收藏  举报