C#基礎之反射,依賴注入

1 反射

1.1 簡介

1.1.1 定義

反射指程序可以 訪問、檢測和修改 它本身狀態(tài)或行為的一種能力。
程序集包含模塊,而模塊包含類型,類型又包含成員。反射則提供了封裝程序集、模塊和類型的對象。
使用反射動態(tài)地創(chuàng)建類型的實例,將類型綁定到現(xiàn)有對象,或從現(xiàn)有對象中獲取類型。然后,可以調用類型的方法或訪問其字段和屬性。

1.1.2 優(yōu)缺點

優(yōu)點:

  • 反射提高了程序的靈活性和擴展性。
  • 降低耦合性,提高自適應能力。
  • 它允許程序創(chuàng)建和控制任何類的對象,無需提前硬編碼目標類。

缺點:

  • 性能問題:使用反射基本上是一種解釋操作,用于字段和方法接入時要遠慢于直接代碼。因此反射機制主要應用在對靈活性拓展性要求很高的系統(tǒng)框架上,普通程序不建議使用。
  • 使用反射會模糊程序內部邏輯;程序員希望在源代碼中看到程序的邏輯,反射卻繞過了源代碼的技術,因而會帶來維護的問題,反射代碼比相應的直接代碼更復雜。

1.1.3 使用場景&注意事項

使用場景:

  • 序列化與反序列化
    可以使用反射來動態(tài)讀取對象的屬性,并將其轉化為 JSON 或 XML 格式。
  • 動態(tài)插件系統(tǒng)
    反射可以動態(tài)加載程序集并調用其中的類型或方法。例如:
  • 插件架構
    動態(tài)加載第三方庫
  • 測試框架
    很多測試框架(如 NUnit、xUnit)使用反射來發(fā)現(xiàn)和執(zhí)行標記為測試的方法。
  • 代碼生成
    反射結合 System.Reflection.Emit 可以動態(tài)生成代碼。

反射的注意事項:

  • 性能問題
    反射的動態(tài)調用性能較低,不適合頻繁調用。
    在性能敏感的場景下,可以通過緩存反射結果優(yōu)化。
  • 安全問題
    反射允許訪問私有成員,可能導致意外的安全漏洞。
  • 類型檢查
    使用反射時,請確保類型和方法存在,否則可能會引發(fā)運行時錯誤。

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 動態(tài)調用方法

反射可以動態(tài)調用類型的成員,例如方法和屬性。

Type type = typeof(Math);
獲取靜態(tài)方法信息
MethodInfo method = type.GetMethod("Pow");
調用方法
object result = method.Invoke(null, new object[] { 2, 3 });
Console.WriteLine($"Result: {result}"); // 輸出 2^3 = 8

注意method.Invoke的第一個參數用于指定調用該方法的實例對象,之后的參數是調用方法要接收的參數。
如果方法是 實例方法,那么第一個參數需要傳入一個具體的實例對象;如果方法是 靜態(tài)方法,則第一個參數傳入 null

1.3.4 動態(tài)創(chuàng)建對象

反射可以動態(tài)創(chuàng)建類型實例

Type type = typeof(StringBuilder);

動態(tài)創(chuàng)建對象
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 動態(tài)調用方法與屬性

1.4.1.1 動態(tài)設置屬性值

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 動態(tài)調用方法

class Calculator
{
    public int Add(int x, int y) => x + y;
}

Calculator calculator = new Calculator();
Type type = calculator.GetType();

// 動態(tài)調用 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)是一種設計模式,用于實現(xiàn)控制反轉(Inversion of Control, IoC)。它通過將依賴對象的創(chuàng)建和管理交給框架,而不是由代碼直接負責,這樣可以提高代碼的可維護性和測試性。

依賴注入的核心概念:

  • 依賴
    依賴是一個類在運行時所需要的其他類。例如,OrderService 依賴于 PaymentService 來完成支付操作。
  • 注入
    注入是指將這些依賴傳遞到需要它們的類中,而不是讓類自己創(chuàng)建依賴。
  • 控制反轉
    將對象的創(chuàng)建和管理從代碼中轉移到外部(如依賴注入容器)。

使用依賴注入的好處:

  • 低耦合:類之間的依賴被抽象,方便替換和擴展。
  • 更容易測試:可以輕松注入 Mock 對象。
  • 模塊化設計:服務的注冊和使用可以解耦。
  • 靈活性:可以動態(tài)配置服務實現(xiàn)。

2.2 為什么需要依賴注入

沒有依賴注入的代碼:

public class OrderService
{
    private PaymentService _paymentService;

    public OrderService()
    {
        _paymentService = new PaymentService(); // 直接創(chuàng)建依賴
    }

    public void ProcessOrder()
    {
        _paymentService.ProcessPayment();
    }
}

問題:
代碼緊密耦合,難以測試(例如,無法輕松替換 PaymentService)。
不靈活,如果要更換 PaymentService 的實現(xiàn),需要修改 OrderService。
使用依賴注入:

public class OrderService
{
    private readonly IPaymentService _paymentService;

    public OrderService(IPaymentService paymentService)
    {
        _paymentService = paymentService; // 依賴通過構造函數注入
    }

    public void ProcessOrder()
    {
        _paymentService.ProcessPayment();
    }
}

優(yōu)勢:

  • 解耦合:OrderService 不需要直接創(chuàng)建 PaymentService。
  • 易于測試:可以傳入 MockPaymentService 進行單元測試。
  • 靈活性:可以輕松替換實現(xiàn)。

2.3 Microsoft 提供的依賴注入支持

C# 中,依賴注入通常通過 Microsoft.Extensions.DependencyInjection 提供支持。這個包是 .NET Core.NET 6/7 中的默認 DI 容器實現(xiàn)。

注意:如果使用 ASP.NET Core,DI 是默認內置的,不需要手動構建服務容器。
Microsoft.Extensions.DependencyInjection 是輕量級 DI 容器,但功能有限,復雜場景下可以考慮更強大的容器,如 Autofac。

2.3.1 安裝依賴

首先,確保安裝了依賴注入的 NuGet 包:

dotnet add package Microsoft.Extensions.DependencyInjection

2.3.2 基礎用法

創(chuàng)建一個簡單的 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)
    {
        // 創(chuàng)建服務集合
        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:每次請求都會創(chuàng)建一個新的對象。
  • Scoped:每個作用域創(chuàng)建一個對象(如每個 HTTP 請求)。
  • Singleton:應用程序生命周期內只創(chuàng)建一個實例。
services.AddTransient<IMyService, MyService>(); // 每次獲取服務都會創(chuàng)建一個新實例
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(每次解析創(chuàng)建新實例)。
    • 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)
    {
        // 創(chuàng)建容器構建器
        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();  // 每次解析創(chuàng)建一個新實例

builder.RegisterType<EmailService>()
       .As<IMessageService>()
       .SingleInstance();        // 單例,整個應用生命周期只創(chuàng)建一次

2.4.4.3 使用 Lambda 注冊

可以使用 Lambda 表達式動態(tài)注冊依賴:

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 條件注冊

根據條件動態(tài)注冊服務:

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");
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容