Featured image of post C#对动态加载的DLL依赖注入

C#对动态加载的DLL依赖注入

动态加载DLL并实现依赖注入的方法,包括模块注册、服务配置和容器构建流程。

什么是依赖注入(Dependency Injection)

📌 概念

依赖注入(Dependency Injection,简称 DI)是一种常见的软件设计模式,主要用于解耦组件之间的依赖关系,提高代码的可维护性和可测试性。

在 C# 开发中,如果不使用依赖注入,类 A 可能会在内部直接实例化类 B,导致两个类之间形成强耦合。而通过依赖注入的方式,可以将类 B 的实例从外部传递给类 A(通常通过构造函数、属性或方法参数),从而实现松耦合的设计。

✅ 主要作用

  • 提升可维护性
    当系统规模变大时,如果各个组件之间紧密耦合,修改一个组件可能会引发连锁反应。通过依赖注入,可以使依赖关系更加清晰明了,便于后期维护。

  • 简化单元测试
    在编写单元测试时,可以轻松地模拟(Mock)依赖对象,而不是依赖真实的复杂对象,使测试更简单、更精准。


🛠 常用依赖注入框架及示例

1. Microsoft.Extensions.DependencyInjection

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using Microsoft.Extensions.DependencyInjection;

class MyClassA
{
    private readonly MyClassB _dependency;
    public MyClassA(MyClassB dependency) => _dependency = dependency;

    public void DoSomething() => _dependency.SomeMethod();
}

class MyClassB
{
    public void SomeMethod() => Console.WriteLine("MyClassB's method is called.");
}

class Program
{
    static void Main()
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddTransient<MyClassB>();
        serviceCollection.AddTransient<MyClassA>();

        var serviceProvider = serviceCollection.BuildServiceProvider();
        var a = serviceProvider.GetService<MyClassA>();
        a.DoSomething();
    }
}

2. Autofac

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using Autofac;

class MyClassA
{
    private readonly MyClassB _dependency;
    public MyClassA(MyClassB dependency) => _dependency = dependency;

    public void DoSomething() => _dependency.SomeMethod();
}

class MyClassB
{
    public void SomeMethod() => Console.WriteLine("MyClassB's method is called.");
}

class Program
{
    static void Main()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<MyClassB>().AsSelf();
        builder.RegisterType<MyClassA>().AsSelf();

        var container = builder.Build();
        var a = container.Resolve<MyClassA>();
        a.DoSomething();
    }
}

什么是动态加载程序集(Dynamic Assembly Loading)

📌 定义

在 C# 中,动态加载程序集允许程序在运行时加载并使用 DLL 文件,而不是在编译时静态引用它们。这种方式提供了更高的灵活性,例如:

  • 根据不同的条件或用户需求加载不同的功能模块。
  • 在程序运行过程中更新某些功能模块,而无需重新编译整个项目。

🧪 示例:动态加载 DLL

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
using System.Reflection;

class Program
{
    static void Main()
    {
        // 加载程序集
        Assembly assembly = Assembly.Load("xxx.dll");

        // 获取类型
        Type type = assembly.GetType("MyNamespace.MyClass");

        // 创建实例
        object instance = Activator.CreateInstance(type);

        // 调用方法
        MethodInfo method = type.GetMethod("DoSomething");
        method.Invoke(instance, null);
    }
}

⚠️ 注意:使用 Assembly.Load("xxx.dll") 加载的程序集会一直占用该 DLL 文件。若需要卸载或更新 DLL,建议使用如下方式:

1
2
byte[] rawAssembly = File.ReadAllBytes(dllPath);
Assembly assembly = Assembly.Load(rawAssembly);

🔄 将动态加载的 DLL 注入到依赖容器中(以 Autofac 为例)

我们以 Autofac 作为依赖注入容器,演示如何对动态加载的 DLL 进行服务注册与解析。


步骤 1:加载程序集

1
2
byte[] rawAssembly = File.ReadAllBytes(dllPath);
Assembly assembly = Assembly.Load(rawAssembly);

步骤 2:定义注入特性(Attribute)

为了筛选需要注册的服务类型,我们可以自定义一个 [Inject] 特性:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class InjectAttribute(string name, string category) : Attribute
{
    public string Name { get; } = name;
    public string Category { get; } = category;
}

public interface ITool
{
    void DoWork();
    void ShowDialog();
}

// 示例类
[Inject("工具2", "类别1")]
public class MainFrame : ITool
{
    public void DoWork() { /* 实现逻辑 */ }
    public void ShowDialog() { /* 实现逻辑 */ }
}

步骤 3:注册服务到 Autofac 容器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
var builder = new ContainerBuilder();

var types = assembly.DefinedTypes.ToList().Where(IsToolType).ToList();

foreach (var type in types)
{
    var attr = type.CustomAttributes.First(a => a.AttributeType == typeof(InjectAttribute));
    var name = attr.ConstructorArguments[0].Value?.ToString();
    var category = attr.ConstructorArguments[1].Value?.ToString();
    string key = $"{category}_{name}";

    builder.RegisterType(type).Named<ITool>(key);
}

步骤 4:构建容器并解析服务

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var container = builder.Build();

var tool1 = container.ResolveNamed<ITool>("类别1_工具1");
tool1.DoWork();

var tool2 = container.ResolveNamed<ITool>("类别1_工具2");
tool2.ShowDialog();

var tool3 = container.ResolveNamed<ITool>("类别2_工具1");
tool3.ShowDialog();

🧩 依赖自动解析能力

当被注入的类型存在其他依赖项时,如日志 (ILogger) 或数据库 (IDatabase) 接口,依赖注入容器会自动完成这些依赖的解析。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public interface ILogger
{
    void Log(string message);
}

public interface IDatabase
{
    List<string> Select();
    void Insert<T>(T entity);
}

示例:单例注入

1
2
builder.RegisterType<ConsoleLogger>().SingleInstance().As<ILogger>();
builder.RegisterType<MysqlMocker>().SingleInstance().As<IDatabase>();

使用示例类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[Inject("工具1", "类别1")]
public class Tool1 : ITool
{
    private readonly ILogger _logger;
    private readonly IDatabase _database;

    public Tool1(ILogger logger, IDatabase database)
    {
        _logger = logger;
        _database = database;
    }

    public void DoWork()
    {
        _logger.Log("This is a log message from Tool1");
        _logger.Log("Doing work in MainFrame");

        var data = _database.Select();
        var str = JsonConvert.SerializeObject(data);
        _logger.Log($"Data from database: {str}");
    }

    public void ShowDialog() { }
}

✅ 总结

功能 描述
依赖注入 解耦组件,提升可维护性和可测试性
动态加载 灵活加载 DLL,支持热插拔和运行时扩展
结合使用 可以将动态加载的 DLL 类型注册为服务,并由 DI 容器管理其生命周期和依赖关系

通过上述方式,你可以实现一个高度模块化、易于扩展和维护的 C# 应用架构,适用于插件式系统、微服务架构等场景。


Built with Hugo
Theme Stack designed by Jimmy