什么是依赖注入(Dependency Injection)
📌 概念
依赖注入(Dependency Injection,简称 DI)是一种常见的软件设计模式,主要用于解耦组件之间的依赖关系,提高代码的可维护性和可测试性。
在 C# 开发中,如果不使用依赖注入,类 A 可能会在内部直接实例化类 B,导致两个类之间形成强耦合。而通过依赖注入的方式,可以将类 B 的实例从外部传递给类 A(通常通过构造函数、属性或方法参数),从而实现松耦合的设计。
✅ 主要作用
🛠 常用依赖注入框架及示例
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# 应用架构,适用于插件式系统、微服务架构等场景。