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");