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 CoreDI 是默认内置的,不需要手动构建服务容器。
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");
posted @ 2024-12-01 17:20  上善若泪  阅读(39)  评论(0编辑  收藏  举报