C#基础之反射,依赖注入
1 反射
1.1 简介
1.1.1 定义
反射指程序可以 访问、检测和修改
它本身状态或行为的一种能力。
程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。
使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。
1.1.2 优缺点
优点:
- 反射提高了程序的灵活性和扩展性。
- 降低耦合性,提高自适应能力。
- 它允许程序创建和控制任何类的对象,无需提前硬编码目标类。
缺点:
- 性能问题:使用反射基本上是一种
解释操作
,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性
和拓展性
要求很高的系统框架上,普通程序不建议使用。 - 使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。
1.1.3 使用场景&注意事项
使用场景:
- 序列化与反序列化
可以使用反射来动态读取对象的属性,并将其转化为 JSON 或 XML 格式。 - 动态插件系统
反射可以动态加载程序集并调用其中的类型或方法。例如: - 插件架构
动态加载第三方库 - 测试框架
很多测试框架(如 NUnit、xUnit)使用反射来发现和执行标记为测试的方法。 - 代码生成
反射结合 System.Reflection.Emit 可以动态生成代码。
反射的注意事项:
- 性能问题
反射的动态调用性能较低,不适合频繁调用。
在性能敏感的场景下,可以通过缓存反射结果优化。 - 安全问题
反射允许访问私有成员,可能导致意外的安全漏洞。 - 类型检查
使用反射时,请确保类型和方法存在,否则可能会引发运行时错误。
1.2 核心组件
常见的反射类和接口:
Type
:表示类型(类、接口、数组等)的抽象描述,是反射的核心。Assembly
:表示程序集,提供对包含多个类型的程序集的访问。MethodInfo、PropertyInfo、FieldInfo
:表示类的成员信息(方法、属性、字段等)。
核心命名空间:
using System;
using System.Reflection;
1.3 反射方法使用
1.3.1 获取类型信息
使用 typeof 获取类型:
Type type = typeof(string); // 获取 string 类型
使用对象的 GetType 方法:
string str = "hello";
Type type = str.GetType();
通过程序集加载类型:
Assembly assembly = Assembly.Load("mscorlib"); // 加载程序集
Type type = assembly.GetType("System.String");
1.3.2 检查类型的元数据
可以通过 Type
类来查看类型的元数据信息,例如类名、方法、属性等。
Type type = typeof(DateTime);
获取类名
Console.WriteLine($"Class Name: {type.Name}");
获取命名空间
Console.WriteLine($"Namespace: {type.Namespace}");
获取所有方法
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
{
Console.WriteLine($"Method: {method.Name}");
}
获取所有属性
PropertyInfo[] properties = type.GetProperties();
foreach (var property in properties)
{
Console.WriteLine($"Property: {property.Name}");
}
1.3.3 动态调用方法
反射可以动态调用类型的成员,例如方法和属性。
Type type = typeof(Math);
获取静态方法信息
MethodInfo method = type.GetMethod("Pow");
调用方法
object result = method.Invoke(null, new object[] { 2, 3 });
Console.WriteLine($"Result: {result}"); // 输出 2^3 = 8
注意
:method.Invoke
的第一个参数用于指定调用该方法的实例对象
,之后的参数是调用方法要接收的参数。
如果方法是 实例方法
,那么第一个参数
需要传入一个具体的实例对象
;如果方法是 静态方法
,则第一个参数
传入 null
1.3.4 动态创建对象
反射可以动态创建类型实例
Type type = typeof(StringBuilder);
动态创建对象
object instance = Activator.CreateInstance(type);
调用方法
MethodInfo method = type.GetMethod("Append", new[] { typeof(string) });
method.Invoke(instance, new object[] { "Hello, Reflection!" });
Console.WriteLine(instance.ToString()); // 输出 "Hello, Reflection!"
1.3.5 访问私有成员
反射支持访问私有字段、属性或方法,但需要加上 BindingFlags
class Sample
{
private string secret = "This is a secret";
}
Sample sample = new Sample();
Type type = sample.GetType();
获取私有字段
FieldInfo field = type.GetField("secret", BindingFlags.NonPublic | BindingFlags.Instance);
获取字段值
string secretValue = (string)field.GetValue(sample);
Console.WriteLine($"Secret Value: {secretValue}"); // 输出 "This is a secret"
1.4 示例
1.4.1 动态调用方法与属性
1.4.1.1 动态设置属性值
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Person person = new Person();
Type type = person.GetType();
// 设置 Name 属性值
PropertyInfo property = type.GetProperty("Name");
property.SetValue(person, "John Doe");
// 设置 Age 属性值
PropertyInfo ageProperty = type.GetProperty("Age");
ageProperty.SetValue(person, 30);
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}"); // 输出 "Name: John Doe, Age: 30"
1.4.1.2 动态调用方法
class Calculator
{
public int Add(int x, int y) => x + y;
}
Calculator calculator = new Calculator();
Type type = calculator.GetType();
// 动态调用 Add 方法
MethodInfo method = type.GetMethod("Add");
object result = method.Invoke(calculator, new object[] { 5, 7 });
Console.WriteLine($"Result: {result}"); // 输出 12
2 依赖注入
2.1 简介
C#
中的 依赖注入(Dependency Injection, DI
)是一种设计模式
,用于实现控制反转(Inversion of Control, IoC
)。它通过将依赖对象的创建和管理交给框架,而不是由代码直接负责,这样可以提高代码的可维护性和测试性。
依赖注入的核心概念:
- 依赖
依赖是一个类在运行时所需要的其他类。例如,OrderService 依赖于 PaymentService 来完成支付操作。 - 注入
注入是指将这些依赖传递到需要它们的类中,而不是让类自己创建依赖。 - 控制反转
将对象的创建和管理从代码中转移到外部(如依赖注入容器)。
使用依赖注入的好处:
- 低耦合:类之间的依赖被抽象,方便替换和扩展。
- 更容易测试:可以轻松注入
Mock
对象。 - 模块化设计:服务的注册和使用可以解耦。
- 灵活性:可以动态配置服务实现。
2.2 为什么需要依赖注入
没有依赖注入的代码:
public class OrderService
{
private PaymentService _paymentService;
public OrderService()
{
_paymentService = new PaymentService(); // 直接创建依赖
}
public void ProcessOrder()
{
_paymentService.ProcessPayment();
}
}
问题:
代码紧密耦合,难以测试(例如,无法轻松替换 PaymentService)。
不灵活,如果要更换 PaymentService 的实现,需要修改 OrderService。
使用依赖注入:
public class OrderService
{
private readonly IPaymentService _paymentService;
public OrderService(IPaymentService paymentService)
{
_paymentService = paymentService; // 依赖通过构造函数注入
}
public void ProcessOrder()
{
_paymentService.ProcessPayment();
}
}
优势:
- 解耦合:OrderService 不需要直接创建 PaymentService。
- 易于测试:可以传入 MockPaymentService 进行单元测试。
- 灵活性:可以轻松替换实现。
2.3 Microsoft 提供的依赖注入支持
在 C#
中,依赖注入通常通过 Microsoft.Extensions.DependencyInjection
提供支持。这个包是 .NET Core
和 .NET 6/7
中的默认 DI 容器实现。
注意
:如果使用 ASP.NET Core
,DI
是默认内置的,不需要手动构建服务容器。
Microsoft.Extensions.DependencyInjection
是轻量级 DI 容器,但功能有限,复杂场景下可以考虑更强大的容器,如 Autofac
。
2.3.1 安装依赖
首先,确保安装了依赖注入的 NuGet 包:
dotnet add package Microsoft.Extensions.DependencyInjection
2.3.2 基础用法
创建一个简单的 DI 容器:
using Microsoft.Extensions.DependencyInjection;
public interface IPaymentService
{
void ProcessPayment();
}
public class PaymentService : IPaymentService
{
public void ProcessPayment()
{
Console.WriteLine("Payment processed!");
}
}
public class OrderService
{
private readonly IPaymentService _paymentService;
public OrderService(IPaymentService paymentService)
{
_paymentService = paymentService;
}
public void ProcessOrder()
{
_paymentService.ProcessPayment();
}
}
class Program
{
static void Main(string[] args)
{
// 创建服务集合
var services = new ServiceCollection();
// 注册服务
services.AddTransient<IPaymentService, PaymentService>();
services.AddTransient<OrderService>();
// 构建服务提供者
var serviceProvider = services.BuildServiceProvider();
// 获取服务
var orderService = serviceProvider.GetService<OrderService>();
orderService.ProcessOrder();
}
}
2.3.3 DI 的三种生命周期
在 Microsoft.Extensions.DependencyInjection
中,可以通过以下三种方式注册依赖:
Transient
:每次请求都会创建一个新的对象。Scoped
:每个作用域创建一个对象(如每个 HTTP 请求)。Singleton
:应用程序生命周期内只创建一个实例。
services.AddTransient<IMyService, MyService>(); // 每次获取服务都会创建一个新实例
services.AddScoped<IMyService, MyService>(); // 每个请求共享一个实例
services.AddSingleton<IMyService, MyService>(); // 全局共享一个实例
2.4 Autofac
2.4.1 简介
Autofac
是一个功能强大的 依赖注入(Dependency Injection, DI)
容器,用于 .NET
应用程序中管理依赖关系。它比默认的 .NET DI
容器(Microsoft.Extensions.DependencyInjection
)更灵活,支持更多高级功能,非常适合复杂的应用场景。
尽管 .NET
默认提供了内置的 DI 容器
,但它有以下局限(Autofac
通过扩展功能和简洁的配置方式克服了这些不足,使其成为复杂项目中 DI 的理想选择):
功能有限
:不支持某些高级特性,例如条件依赖注入或模块化配置。灵活性不足
:对复杂的生命周期管理、依赖关系链等的支持较弱。
注意
:Autofac
的功能强大,但默认的 DI 容器
对于大多数简单场景已经足够。
如果项目对性能有严格要求,Autofac
可能略慢于默认 DI 容器
。
在 ASP.NET Core
项目中,尽量避免使用非生命周期范围的 Resolve
,以遵循 DI 的最佳实
2.4.2 核心概念
- 容器(
Container
)
Autofac
的核心是容器,它负责管理服务的注册和解析。 - 模块(
Module
)
模块用于组织和分组服务注册逻辑,方便管理。 - 生命周期(
Lifetime Scope
)
Autofac
支持多种生命周期,包括:Instance Per Dependency
(每次解析创建新实例)。Single Instance
(单例)。Instance Per Lifetime Scope
(每个作用域一个实例)。
2.4.3 Autofac 的安装
安装 Autofac 核心库:
dotnet add package Autofac
如果是 ASP.NET Core
项目,还需要安装集成包:
dotnet add package Autofac.Extensions.DependencyInjection
2.4.4 基本用法
2.4.4.1 服务注册与解析
using System;
using Autofac;
public interface IMessageService
{
void SendMessage(string message);
}
public class EmailService : IMessageService
{
public void SendMessage(string message)
{
Console.WriteLine($"Email sent: {message}");
}
}
class Program
{
static void Main(string[] args)
{
// 创建容器构建器
var builder = new ContainerBuilder();
// 注册服务
builder.RegisterType<EmailService>().As<IMessageService>();
// 构建容器
var container = builder.Build();
// 解析服务
var messageService = container.Resolve<IMessageService>();
messageService.SendMessage("Hello Autofac!");
}
}
2.4.4.2 生命周期管理
builder.RegisterType<EmailService>()
.As<IMessageService>()
.InstancePerDependency(); // 每次解析创建一个新实例
builder.RegisterType<EmailService>()
.As<IMessageService>()
.SingleInstance(); // 单例,整个应用生命周期只创建一次
2.4.4.3 使用 Lambda 注册
可以使用 Lambda 表达式动态注册依赖:
builder.Register(c => new EmailService()).As<IMessageService>();
2.4.4.4 注册实例
var config = new AppConfig { Setting = "Value" };
builder.RegisterInstance(config).As<AppConfig>();
2.4.5 高级特性
2.4.5.1 条件注册
根据条件动态注册服务:
builder.RegisterType<SmsService>()
.As<IMessageService>()
.IfNotRegistered(typeof(IMessageService));
2.4.5.2 模块化配置
将服务注册逻辑分组到模块中:
public class MyModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<EmailService>().As<IMessageService>();
}
}
// 注册模块
builder.RegisterModule<MyModule>();
2.4.5.3 属性注入
Autofac 支持属性注入:
builder.RegisterType<MyClass>()
.PropertiesAutowired();
2.4.5.4 参数传递
在构造函数中注入特定参数:
builder.RegisterType<EmailService>()
.As<IMessageService>()
.WithParameter("smtpServer", "smtp.example.com");