深入理解 C# 反射 的使用

總目錄


前言

反射是.NET框架中一個強大的特性,允許程序在運行時檢查和操作類型信息。通過反射,開發者可以動態地創建對象、調用方法、訪問屬性等,為程序提供了極大的靈活性。本文將詳細講解C#反射的使用方法及其應用場景。


一、什么是反射?

1. 定義

  • 反射(Reflection) 是指程序在運行時能夠檢查和操作其自身的類型信息。通過反射,可以獲取類型的成員(如方法、屬性、事件等)并動態地調用它們。
  • 在.NET框架中,反射的主要實現位于System.Reflection命名空間中。在程序運行時通過 System.Reflection 命名空間提供的 API,可完成如下操作:
    • 獲取類型信息:如類名、命名空間、繼承關系、實現的接口等。
    • 動態創建對象:無需顯式實例化類型。
    • 訪問成員:包括私有字段、方法、屬性等。
    • 執行方法:通過方法名或參數動態調用。

反射,簡單來說,就是程序在運行時能夠自我檢查,獲取類型、方法、字段等元數據信息的能力。

2. 作用

  • 動態類型操作:在運行時獲取類型信息(元數據),并根據這些信息執行相應的操作(動態創建對象實例或執行方法)。
  • 實現插件系統:通過反射加載外部程序集,實現可擴展的插件架構。
  • 序列化/反序列化:獲取對象的字段和屬性,實現數據的序列化和反序列化。
  • 自定義特性處理:讀取類型或成員上的自定義特性,根據特性進行特定處理。
  • 代碼分析工具:開發調試器或分析工具時,反射可以幫助你獲取程序內部的狀態。

3. 反射相關類與命名空間

反射的核心功能依賴于 System.Reflection 命名空間中的類型,其中包含 TypeAssemblyMethodInfo 等關鍵類。

1)核心類與命名空間

  • System.Reflection :反射的核心功能
    • Assembly:表示程序集,用于加載和操作程序集中的模塊、類型等信息。
    • MethodInfo/PropertyInfo/FieldInfo:分別對應方法、屬性、字段的元數據,支持動態調用和訪問。
  • System.Type:代表任意類型(如類、接口、枚舉等),可通過typeof獲取類型信息。

2)相關類與命名空間

基本列舉反射實際應用時所涉及的所有類與命名空間

System.Reflection(命名空間)
|
├── Assembly          	// 程序集操作
├── Module            	// 模塊信息
├── ConstructorInfo   	// 構造函數
├── ParameterInfo 		// 參數信息
├── MethodInfo        	// 方法信息
├── PropertyInfo      	// 屬性信息
├── MemberInfo			// 成員信息
├── FieldInfo			// 字段信息
├── TypeInfo			// 類型信息
└── MethodBase         	// 方法基類信息
|
System(命名空間)
|
├── Type              	// 類型元數據
├── Activator           // 實例創建
└── AppDomain           // 應用程序域管理

反射常用類與方法速查表

類/方法用途示例代碼
Type獲取類型元數據typeof(MyClass)
Assembly加載和操作程序集Assembly.Load("MyAssembly")
MethodInfo獲取和調用方法method.Invoke(obj, args)
PropertyInfo訪問屬性值property.GetValue(obj)
BindingFlags控制成員可見性(如私有)BindingFlags.NonPublic
Activator.CreateInstance動態創建對象實例Activator.CreateInstance(type)

4. 簡單示例

動態創建對象并調用方法

// 獲取類型
Type type = typeof(MyClass);
// 動態創建實例
object instance = Activator.CreateInstance(type);
// 獲取方法并調用
MethodInfo method = type.GetMethod("SayHello");
method.Invoke(instance, null); // 輸出 "Hello, World!"
Type type = typeof(string); // 獲取類型信息
object obj = Activator.CreateInstance(type); // 動態創建實例

二、如何使用反射

0. 反射操作流程概覽

獲取類型信息
動態創建對象
動態的訪問和操作成員
動態的調用方法
動態的訪問字段
動態的訪問屬性
動態的訪問其他成員

1. 獲取類型信息

Type對象是反射的基礎,提供了關于類型的詳細信息。

1)獲取Type對象

要使用反射,首先需要獲取類型的Type對象。在C#中,可以通過多種方式獲取類型信息。

// 方式1:typeof運算符(編譯時已知類型)
Type type1 = typeof(StringBuilder);// 方式2:GetType()(運行時對象實例)
object obj = new List<int>();
Type type2 = obj.GetType();// 方式3:Type.GetType()(通過類型名稱)
Type type3 = Type.GetType("System.String");

關于獲取Type對象的方式,詳細信息可見:C# 獲取Type對象的方式。

2)獲取 Type 類中的基本屬性

? 獲取類型的基本信息
  • Name: 類型的簡單名稱。
  • FullName: 類型的完全限定名稱(包含命名空間)。
  • Namespace: 類型所在命名空間的名稱。
  • AssemblyQualifiedName: 獲取類型的程序集限定名,包含類型及其所在程序集的詳細信息,適用于通過反射加載類型。
namespace ReflectionDemo
{public class User { }internal class Program{static void Main(string[] args){var type = typeof(User);Console.WriteLine($"{"Name".PadRight(24)}{type.Name}");Console.WriteLine($"{"FullName".PadRight(24)}{type.FullName}");Console.WriteLine($"{"Namespace".PadRight(24)}{type.Namespace}");Console.WriteLine($"{"AssemblyQualifiedName".PadRight(24)}{type.AssemblyQualifiedName}");}}
}

運行結果:

Name                    :User
FullName                :ReflectionDemo.User
Namespace               :ReflectionDemo
AssemblyQualifiedName   :ReflectionDemo.User, ReflectionDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

關于以上屬性,詳情內容可見:C# Type類中Name、FullName、Namespace、AssemblyQualifiedName的區別。

? Assembly 屬性

通過Type對象的Assembly 屬性 獲取 類型所在的程序集。

using System.Reflection;namespace ReflectionDemo
{public class User { }internal class Program{static void Main(string[] args){var type = typeof(User);Assembly assembly = type.Assembly;Console.WriteLine($"Assembly FullName:{assembly.FullName}");Console.WriteLine($"Assembly Name:{type.Name}");}}
}

輸出結果:

Assembly FullName:ReflectionDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Assembly Name:User

在此我們可知:


【類型的程序集限定名】= 【類型的完全限定名FullName】+【程序集的完全限定名FullName】

當我們通過Type對象的Assembly 屬性 獲取 類型所在的程序集之后,我們可以對程序集進行相關的操作,具體關于Assembly的相關內容將在下文進行詳細介紹。

? 其他基礎屬性
namespace ReflectionDemo
{public class User { }internal class Program{static void Main(string[] args){var type = typeof(User);Console.WriteLine("基類:" + type.BaseType);      		// 輸出:System.ObjectConsole.WriteLine($"IsAbstract:{type.IsAbstract}");	// 是否是抽象Console.WriteLine($"IsAbstract:{type.IsInterface}");	// 是否是接口Console.WriteLine($"IsAbstract:{type.IsClass}");		// 是否是類Console.WriteLine($"IsAbstract:{type.IsEnum}");		// 是否是枚舉類型Console.WriteLine($"IsAbstract:{type.IsGenericType}");	// 是否是泛型Console.WriteLine($"IsAbstract:{type.IsPublic}");		// 是否PublicConsole.WriteLine($"IsAbstract:{type.IsSealed}");		// 是否SealedConsole.WriteLine($"IsAbstract:{type.IsValueType}");	// 是否值類型}}
}

3)獲取類型的成員信息

反射不僅可以獲取類型信息,還可以獲取類型的成員信息,如字段、屬性、方法等。

class Person
{public string Name { get; set; }private int age;
}
? 獲取字段信息
class Program
{static void Main(){Type personType = typeof(Person);FieldInfo[] fields = personType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);foreach (var field in fields){Console.WriteLine($"Field: {field.Name}, Type: {field.FieldType}");}}
}
? 獲取屬性信息
class Program
{static void Main(){Type personType = typeof(Person);PropertyInfo[] properties = personType.GetProperties();foreach (var property in properties){Console.WriteLine($"Property: {property.Name}, Type: {property.PropertyType}");}}
}
? 獲取方法信息
class Calculator
{public int Add(int a, int b) => a + b;private int Subtract(int a, int b) => a - b;
}class Program
{static void Main(){Type calculatorType = typeof(Calculator);MethodInfo[] methods = calculatorType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);foreach (var method in methods){Console.WriteLine($"Method: {method.Name}, Return Type: {method.ReturnType}");}}
}
  • 更多關于Type類 的詳情介紹,可見:C# Type 類使用詳解。
  • 更多關于BindingFlags 的詳細內容,可見:C# BindingFlags 使用詳解

2. 動態創建對象

反射不僅限于獲取類型和成員信息,還可以用于動態創建對象。

Person類 為例:

class Person
{public int Age { get; set; }public string Name { get; set; }public Person(){Console.WriteLine("無參構造函數被執行!");}public Person(string name){Name = name;Console.WriteLine($"有參構造函數被執行,Name = {Name}");}public Person(string name, int age){Name = name;Age = age;Console.WriteLine($"有參構造函數被執行,Name = {Name}、Age = {Age}");}public void Show(){Console.WriteLine("Person");}
}

1)使用 Activator.CreateInstance 創建對象

? 無參構造
internal class Program
{static void Main(string[] args){Type personType = typeof(Person);object personInstance = Activator.CreateInstance(personType);var person = (Person)personInstance;person.Show();}
}

運行結果:

無參構造函數被執行!
Person
? 帶參數構造
  • 單參數構造函數
internal class Program
{static void Main(string[] args){Type personType = typeof(Person);object[] parameters = { "Bob" };object personInstance = Activator.CreateInstance(personType, parameters);var person = personInstance as Person;person?.Show();}
}

運行結果:

有參構造函數被執行,Name = Bob
Person
  • 多參數構造函數
internal class Program
{static void Main(string[] args){Type personType = typeof(Person);object[] parameters = { "Bob", 12 };object personInstance = Activator.CreateInstance(personType, parameters);var person = (Person)personInstance;person.Show();}
}

運行結果:

有參構造函數被執行,Name = Bob、Age = 12
Person
? 動態創建泛型實例
Type openType = typeof(List<>);
Type closedType = openType.MakeGenericType(typeof(int));
object list = Activator.CreateInstance(closedType);
Type openType = typeof(Dictionary<,>);
Type closedType = openType.MakeGenericType(typeof(int), typeof(string));
object dict = Activator.CreateInstance(closedType);
? 注意事項
  • 使用Activator.CreateInstance創建具有參數化構造函數對象實例的時候,需要保持傳入的參數一致
  • public Person(string name) 構造函數,需要的參數是 object[] parameters = { "Bob" };
  • public Person(string name, int age) 構造函數,需要的參數是 object[] parameters = { "Bob", 12 };

2)使用 Assembly 中的CreateInstance 創建對象

? 無參構造
internal class Program
{static void Main(string[] args){Assembly assembly = Assembly.GetExecutingAssembly();object personInstance = assembly.CreateInstance("ReflectionDemo.Person");var person = (Person)personInstance;person.Show();}
}

運行結果:

無參構造函數被執行!
Person
? 帶參構造
internal class Program
{static void Main(string[] args){Assembly assembly = Assembly.GetExecutingAssembly();// 傳遞參數調用構造函數object personInstance = assembly.CreateInstance("ReflectionDemo.Person",ignoreCase: false,bindingAttr: BindingFlags.Public | BindingFlags.Instance, // 指定公共實例構造函數binder: null,//默認null 即可args: new object[] { "Bob", 12 },culture: null,//默認null 即可activationAttributes: null//默認null 即可)!;var person = (Person)personInstance;person.Show();}
}

運行結果:

有參構造函數被執行,Name = Bob、Age = 12
Person
? 注意事項
  • args 數組的類型和順序必須與目標構造函數的參數完全匹配。
  • Assembly.CreateInstance 相對低效,建議優先使用 Activator.CreateInstance 或工廠模式。
  • Assembly.CreateInstance 最終調用 Activator.CreateInstance,因此兩者功能相似,但 Activator 更直接。

3)使用 Invoke執行構造函數 創建對象

? 無參構造
internal class Program
{static void Main(string[] args){Type personType = typeof(Person);// 配置構造函數參數列表var types = Type.EmptyTypes; //new Type[0];ConstructorInfo constructorInfo = personType.GetConstructor(types);// 使用Invoke 執行構造函數,并傳入對應的參數 數組object personInstance = constructorInfo.Invoke(null);//(new object[0]);var person = (Person)personInstance;person.Show();}
}

運行結果:

無參構造函數被執行!
Person
? 帶參構造
internal class Program
{static void Main(string[] args){Type personType = typeof(Person);// 配置構造函數參數列表var types = new Type[] { typeof(string), typeof(int) };ConstructorInfo constructorInfo = personType.GetConstructor(types);// 使用Invoke 執行構造函數,并傳入對應的參數 數組object personInstance = constructorInfo.Invoke(new object[] { "Bob", 12 });var person = (Person)personInstance;person.Show();}
}

運行結果:

有參構造函數被執行,Name = Bob、Age = 12
Person
? 注意事項
  • 參數數組的類型和順序必須與目標構造函數的參數完全匹配。
  • new Type[] { typeof(string), typeof(int) };new object[] { "Bob", 12 } 保持一致

3. 動態訪問和操作成員

反射不僅限于獲取類型和成員信息,還可以用于動態調用方法和訪問字段或屬性。

1)動態調用方法

使用Type對象的GetMethod方法獲取MethodInfo對象,然后調用Invoke方法執行方法。

using System.Reflection;namespace ReflectionDemo
{class Calculator{public int Add(int a, int b) => a + b;public void Show() => Console.WriteLine("Calculator");}internal class Program{static void Main(string[] args){Type calculatorType = typeof(Calculator);object calculatorInstance = Activator.CreateInstance(calculatorType);// 獲取指定名稱的方法信息MethodInfo showMethod = calculatorType.GetMethod("Show");MethodInfo addMethod = calculatorType.GetMethod("Add");// 執行無參方法showMethod.Invoke(calculatorInstance, null);// 執行帶參方法int result = (int)addMethod.Invoke(calculatorInstance, new object[] { 5, 3 });Console.WriteLine($"Result of Add(5, 3): {result}");}}
}

運行結果:

Calculator
Result of Add(5, 3): 8

2)訪問字段和屬性

? 訪問字段

使用Type對象的GetField方法獲取FieldInfo 對象,然后使用GetValueSetValue方法訪問或設置屬性值。

using System;
using System.Reflection;class Person
{public string Name { get; set; }private int age;
}class Program
{static void Main(){Type personType = typeof(Person);object personInstance = Activator.CreateInstance(personType);// 獲取 FieldInfo 對象:私有字段FieldInfo ageField = personType.GetField("age", BindingFlags.NonPublic | BindingFlags.Instance);// 使用 SetValue 設置字段值ageField.SetValue(personInstance, 30);// 使用 GetValue 獲取字段值int ageValue = (int)ageField.GetValue(personInstance);Console.WriteLine($"Person's Age: {ageValue}");}
}
? 訪問屬性

使用Type對象的GetProperty方法獲取PropertyInfo對象,然后使用GetValueSetValue方法訪問或設置屬性值。

using System;
using System.Reflection;class Person
{public string Name { get; set; }
}class Program
{static void Main(){Type personType = typeof(Person);object personInstance = Activator.CreateInstance(personType);// 獲取 PropertyInfo 對象PropertyInfo nameProperty = personType.GetProperty("Name");// 使用 SetValue 設置屬性值nameProperty.SetValue(personInstance, "Bob");// 使用 GetValue 獲取屬性值string nameValue = (string)nameProperty.GetValue(personInstance);Console.WriteLine($"Person's Name: {nameValue}");}
}

三、高階實戰技巧

1. 動態加載程序集

1)獲取程序集信息

反射可解析程序集的元數據,例如:

using System.Reflection;namespace ReflectionDemo
{internal class Program{static void Main(string[] args){// 獲取當前程序集Assembly currentAssembly = Assembly.GetExecutingAssembly();// 獲取當前程序集完全限定名稱Console.WriteLine("程序集名稱:" + currentAssembly.FullName);// 輸出:程序集名稱:ReflectionDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nullConsole.WriteLine("程序集名稱:" + currentAssembly.GetName().FullName);// 輸出:程序集名稱:ReflectionDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nullConsole.WriteLine("程序集名稱:" + currentAssembly.GetName().Name);// 輸出:程序集名稱:ReflectionDemoConsole.WriteLine("程序集版本:" + currentAssembly.GetName().Version);// 輸出:程序集版本:1.0.0.0}}
}

2)獲取類型信息

從程序集中獲取特定的類型(類、結構等)信息。

// 獲取當前程序集
Assembly currentAssembly = Assembly.GetExecutingAssembly();// 獲取程序集中的所有類型
Type[] types = currentAssembly.GetTypes();// 獲取指定名稱的類型
Type myType = currentAssembly.GetType("Namespace.ClassName");

3)動態加載程序集

? 使用Assembly.Load 加載
// 通過程序集全名加載
string assemblyName = "MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
Assembly assembly = Assembly.Load(assemblyName);// 加載后自動加載依賴項(如 MyDependency.dll)
? 使用Assembly.LoadFrom 加載
// 通過路徑加載,并自動加載依賴項
string path = @"C:\MyAssembly.dll";
Assembly assembly = Assembly.LoadFrom(path);// 如果 MyAssembly.dll 依賴 MyDependency.dll,會自動加載
? 使用Assembly.LoadFile 加載
// 通過路徑加載,但不加載依賴項
string path = @"C:\MyAssembly.dll";
Assembly assembly = Assembly.LoadFile(path);// 手動加載依賴項(如 MyDependency.dll)
Assembly.LoadFile(@"C:\MyDependency.dll");

程序集加載的三種方式,可以在項目中添加該程序集的引用后使用,也可在未添加該程序集的情況下使用(某些情況下),這樣就極大的豐富的項目的靈活性和擴展性

  • 有關程序集的強名稱 或弱名稱 相關信息,可見:C# Type類中Name、FullName、Namespace、AssemblyQualifiedName的區別 文中所涉及的程序集的強弱名稱內容。
  • 有關這三種加載程序集方式的詳細信息,可見:C# 動態加載程序集的三種方式

4)實現插件化架構的關鍵步驟

// 加載 DLL
Assembly pluginAssembly = Assembly.LoadFrom("Plugin.dll");// 獲取類型
Type pluginType = pluginAssembly.GetType("Plugin.MainClass");// 創建實例
object plugin = Activator.CreateInstance(pluginType);// 調用插件方法
MethodInfo executeMethod = pluginType.GetMethod("Execute");
executeMethod.Invoke(plugin, null);

2. 訪問私有成員

通過 BindingFlags 組合實現私有成員的訪問:

// 訪問私有字段
FieldInfo privateField = type.GetField("_privateField", BindingFlags.NonPublic | BindingFlags.Instance);
privateField.SetValue(instance, "secret");// 調用私有方法
MethodInfo privateMethod = type.GetMethod("InternalProcess",BindingFlags.NonPublic | BindingFlags.Instance);
privateMethod.Invoke(instance, null);

3. 泛型方法調用

使用MakeGenericMethod動態創建泛型方法實例

? 調用單類型參數的泛型方法
using System.Reflection;
using System.Xml.Linq;namespace ReflectionDemo
{public class GenericHelper{// 定義一個泛型方法,接受類型參數 T,并打印類型名稱public static void PrintGenericType<T>(){Console.WriteLine($"泛型類型參數 T 的類型是:{typeof(T).Name}");}}internal class Program{static void Main(string[] args){// 1. 獲取目標類的 Type 對象Type targetType = typeof(GenericHelper);// 2. 通過反射獲取泛型方法的 MethodInfo(注意方法名和參數列表)// 這里方法 PrintGenericType<T> 沒有參數,所以參數類型數組為空MethodInfo genericMethod = targetType.GetMethod("PrintGenericType");if (genericMethod == null){Console.WriteLine("未找到泛型方法!");return;}// 3. 使用 MakeGenericMethod 指定類型參數(例如 int 和 string)// 創建一個類型參數數組,這里指定 T 為 intType[] typeArgs = { typeof(int) };MethodInfo closedMethod = genericMethod.MakeGenericMethod(typeArgs);// 4. 調用閉合后的泛型方法closedMethod.Invoke(null, null); // 靜態方法無需實例,參數數組為空// 再次調用,指定類型參數為 stringType[] typeArgs2 = { typeof(string) };MethodInfo closedMethod2 = genericMethod.MakeGenericMethod(typeArgs2);closedMethod2.Invoke(null, null);}}
}
? 調用多類型參數的泛型方法
public class GenericHelper
{public static void PrintTwoTypes<T1, T2>(){Console.WriteLine($"T1: {typeof(T1).Name}, T2: {typeof(T2).Name}");}
}// 通過反射調用:
MethodInfo genericMethod = targetType.GetMethod("PrintTwoTypes");
MethodInfo closedMethod = genericMethod.MakeGenericMethod(typeof(int), typeof(string));
closedMethod.Invoke(null, null); // 輸出:T1: Int32, T2: String

4.代碼示例

為了更直觀地理解反射的使用,下面是一個完整的代碼示例。

using System;
using System.Reflection;// 定義Person類
public class Person
{public string Name { get; set; }public int Age { get; set; }public Person(string name, int age){Name = name;Age = age;}public void Introduce(){Console.WriteLine($"My name is {Name}, and I am {Age} years old.");}public string GetGreeting(){return $"Hello, my name is {Name}.";}
}// 反射示例
public class ReflectionExample
{public static void Main(){// 獲取Person類型的Type對象Type personType = typeof(Person);// 使用Activator.CreateInstance方法創建Person對象object person = Activator.CreateInstance(personType, "Alice", 30);Person alice = (Person)person;// 調用Introduce方法alice.Introduce();// 使用反射調用GetGreeting方法MethodInfo greetingMethod = personType.GetMethod("GetGreeting");string greeting = (string)greetingMethod.Invoke(alice, null);Console.WriteLine(greeting);// 使用反射訪問和設置Name屬性PropertyInfo nameProperty = personType.GetProperty("Name");string name = (string)nameProperty.GetValue(alice);Console.WriteLine($"Name: {name}");nameProperty.SetValue(alice, "Bob");name = (string)nameProperty.GetValue(alice);Console.WriteLine($"Updated Name: {name}");}
}

在這個示例中,我們通過反射獲取了 Person 類的類型信息,動態創建了對象實例,并設置了字段和屬性的值,最后調用了方法。

四、反射的實際應用場景

1. 應用場景

  • 插件系統開發:動態加載DLL實現功能擴展
  • ORM框架:實現對象關系映射
  • 序列化工具:動態解析對象結構
  • DI容器:依賴注入實現

2. 應用場景示例

  • 有關反射的 應用場景示例,可見:C# 反射的實際應用場景。

五、使用須知

1. 反射的優缺點

優點

  • 靈活性高:在運行時動態操作對象,適用于需要靈活處理的場景。
  • 功能強大:可以訪問和操作程序的元數據,實現復雜的功能。適用于序列化場景。
  • 強擴展性:可以動態的加載外部的程序集,增強程序的擴展性,適用于插件系統場景。

缺點

  • 性能開銷:反射操作通常比直接操作慢,因為它需要額外的查找和驗證。
  • 安全性問題:反射可以訪問私有成員,可能帶來安全隱患。
  • 可讀性差:代碼可讀性較差,維護難度增加。

2. 性能優化

1)性能優化策略

雖然反射提供了極大的靈活性,但其性能開銷相對較高(反射涉及動態類型解析,比直接調用慢10-100倍。)。頻繁使用反射可能會影響應用程序的性能,特別是在需要高效率的場景下。為了優化性能,可以考慮以下幾點:

  • 緩存反射結果:如果多次調用相同的反射操作,可以將結果緩存起來,避免重復查找。
    • 對重復使用的Type對象進行緩存
    • 預獲取MethodInfoPropertyInfo并緩存。
  • 使用表達式樹:對于某些復雜的反射操作,可以使用表達式樹來生成高效的IL代碼。
    • 預編譯表達式:使用System.Linq.Expressions生成動態方法
    • 使用 Delegate 或 Expression:將反射調用轉為委托提高性能
  • 減少反射調用次數:盡量減少不必要的反射調用,尤其是在循環中。
  • BindingFlags優化:精確指定成員搜索范圍

2)基準測試對比

操作類型直接調用反射調用委托緩存Emit IL
簡單方法調用(100萬次)5ms6500ms15ms8ms
屬性訪問(100萬次)2ms3200ms10ms6ms

以上數據,僅供參考。

3)優化方案實現

? 方案1:緩存反射結果

緩存反射結果可以顯著提高應用程序的性能,尤其是在頻繁使用反射獲取類型信息、方法調用等場景下。下面我將給出一個簡單的C#示例,展示如何緩存反射的結果。

using System;
using System.Collections.Generic;
using System.Reflection;public class ReflectionCacheExample
{private static Dictionary<string, MethodInfo> _methodInfoCache = new Dictionary<string, MethodInfo>();public void ExecuteMethod(string methodName){var methodInfo = GetMethodInfo(methodName);if (methodInfo != null){// 調用方法methodInfo.Invoke(this, null);}else{Console.WriteLine($"未找到名為 {methodName} 的方法");}}private MethodInfo GetMethodInfo(string methodName){string key = $"{this.GetType().FullName}.{methodName}";// 檢查緩存中是否已有該方法的信息if (!_methodInfoCache.TryGetValue(key, out MethodInfo cachedMethod)){// 如果緩存中沒有,則通過反射查找并添加到緩存MethodInfo method = this.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);if (method != null){_methodInfoCache[key] = method;return method;}return null;}return cachedMethod;}// 示例方法private void PrintHello(){Console.WriteLine("Hello, World!");}
}class Program
{static void Main(string[] args){ReflectionCacheExample example = new ReflectionCacheExample();example.ExecuteMethod("PrintHello");  // 第一次調用,會進行反射查找example.ExecuteMethod("PrintHello");  // 第二次調用,直接從緩存讀取}
}

代碼說明

  • 在這個例子中,我們創建了一個ReflectionCacheExample類,它包含一個用于緩存方法信息的靜態字典_methodInfoCache
  • GetMethodInfo方法首先嘗試從緩存中檢索方法信息。如果找不到,則通過反射獲取方法信息,并將其存儲在緩存中以便將來使用。
  • ExecuteMethod方法演示了如何利用緩存來執行方法。第一次調用時,由于緩存為空,所以需要通過反射查找方法;第二次調用時,直接從緩存中獲取方法信息,提高了效率。

緩存屬性信息

private static Dictionary<Type, PropertyInfo[]> _propertyCache = new();public static PropertyInfo[] GetCachedProperties(Type type)
{if (!_propertyCache.TryGetValue(type, out var props)){props = type.GetProperties();_propertyCache[type] = props;}return props;
}
? 方案2:委托緩存(推薦)

下面是一個完整的C#示例,展示了如何使用表達式樹創建一個屬性訪問器(getter),并將其應用于具體的類實例中。我們將以Person類為例,并展示如何獲取其Name屬性的值。

using System;
using System.Linq.Expressions;
using System.Reflection;public class Person
{public string Name { get; set; }public int Age { get; set; }public Person(string name, int age){Name = name;Age = age;}
}public static class PropertyGetterFactory
{public static Func<object, object> CreatePropertyGetter(PropertyInfo prop){var objParam = Expression.Parameter(typeof(object), "obj");var castExpr = Expression.Convert(objParam, prop.DeclaringType);var propAccess = Expression.Property(castExpr, prop);var castResult = Expression.Convert(propAccess, typeof(object));return Expression.Lambda<Func<object, object>>(castResult, objParam).Compile();}
}class Program
{static void Main(string[] args){// 創建一個Person對象var personObj = new Person("Alice", 30);// 獲取"Name"屬性的PropertyInfovar propertyInfo = typeof(Person).GetProperty("Name");if (propertyInfo == null){Console.WriteLine("未找到指定的屬性");return;}// 創建屬性訪問器var getter = PropertyGetterFactory.CreatePropertyGetter(propertyInfo);// 使用屬性訪問器獲取屬性值string name = (string)getter(personObj);// 輸出結果Console.WriteLine($"Name: {name}");}
}

代碼說明

  1. Person 類:

    • 包含兩個屬性:NameAge
    • 提供了一個構造函數用于初始化這兩個屬性。
  2. PropertyGetterFactory 類:

    • CreatePropertyGetter 方法接收一個 PropertyInfo 對象作為參數。
    • 使用表達式樹構建一個從對象到其屬性值的委托(Func<object, object>)。
    • 這個委托可以直接調用,傳入目標對象,返回該對象對應屬性的值。
  3. Main 方法:

    • 創建一個 Person 實例并初始化其屬性。
    • 獲取 Person 類的 Name 屬性的 PropertyInfo
    • 調用 CreatePropertyGetter 方法生成屬性訪問器。
    • 使用生成的屬性訪問器獲取 personObjName 屬性值,并輸出結果。

輸出結果

Name: Alice

這種方式通過表達式樹動態生成屬性訪問器,可以顯著提高反射操作的性能,特別是在需要頻繁訪問同一屬性的情況下。

// 表達式樹優化
var param = Expression.Parameter(typeof(MyClass));
var propAccess = Expression.Property(param, "Name");
var lambda = Expression.Lambda<Func<MyClass, string>>(propAccess, param);
Func<MyClass, string> compiled = lambda.Compile();
string value = compiled(instance);
? 方案3:Emit動態生成

動態方法和IL生成是一種高級技術,通常用于性能優化或在運行時動態生成代碼的場景。使用這些技術時需要小心,確保生成的IL代碼是正確的并且符合預期的行為。

下面是一個完整的C#示例,展示了如何使用動態方法(DynamicMethod)和IL生成器(ILGenerator)來創建一個無參構造函數的委托(ObjectActivator)。我們將以一個簡單的類Person為例,并展示如何使用這個委托實例化對象。

using System;
using System.Reflection;
using System.Reflection.Emit;public class Person
{public string Name { get; set; }public int Age { get; set; }public Person(){Name = "Unknown";Age = 0;}public override string ToString(){return $"Name: {Name}, Age: {Age}";}
}public delegate object ObjectActivator();public static class ObjectActivatorFactory
{public static ObjectActivator CreateParameterlessConstructor(Type type){// 確保類型有一個無參構造函數ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);if (constructor == null){throw new ArgumentException("類型必須包含一個無參構造函數。", nameof(type));}// 創建一個新的動態方法var dynamicMethod = new DynamicMethod(name: "CreateInstance",returnType: typeof(object),parameterTypes: null,owner: type);ILGenerator il = dynamicMethod.GetILGenerator();il.Emit(OpCodes.Newobj, constructor); // 調用無參構造函數il.Emit(OpCodes.Ret); // 返回新創建的對象return (ObjectActivator)dynamicMethod.CreateDelegate(typeof(ObjectActivator));}
}class Program
{static void Main(string[] args){try{// 創建一個用于實例化Person對象的委托ObjectActivator activator = ObjectActivatorFactory.CreateParameterlessConstructor(typeof(Person));// 使用委托創建Person對象object personObj = activator();// 輸出結果Console.WriteLine(personObj.ToString());}catch (Exception ex){Console.WriteLine($"發生錯誤: {ex.Message}");}}
}

代碼說明

  1. Person 類:

    • 包含兩個屬性:NameAge
    • 提供了一個無參構造函數,初始化Name為"Unknown",Age為0。
    • 重寫了ToString方法,方便輸出對象信息。
  2. ObjectActivatorFactory 類:

    • CreateParameterlessConstructor 方法接收一個 Type 對象作為參數。
    • 首先檢查該類型是否包含無參構造函數,如果沒有,則拋出異常。
    • 使用 DynamicMethod 創建一個新的動態方法,返回類型為 object,沒有參數。
    • 使用 ILGenerator 生成中間語言(IL)代碼,調用指定類型的無參構造函數并返回新創建的對象。
    • 最后,將動態方法編譯為委托并返回。
  3. Main 方法:

    • 嘗試創建一個用于實例化 Person 對象的委托。
    • 使用該委托創建一個 Person 對象。
    • 輸出創建的對象信息。

輸出結果

Name: Unknown, Age: 0
using System;
using System.Linq.Expressions;
using System.Reflection.Emit;public class Program
{public static void Main(){// 動態生成一個方法DynamicMethod dynamicMethod = new DynamicMethod("Add", typeof(int), new[] { typeof(int), typeof(int) }, typeof(Program).Module);ILGenerator il = dynamicMethod.GetILGenerator();il.Emit(OpCodes.Ldarg_0);il.Emit(OpCodes.Ldarg_1);il.Emit(OpCodes.Add);il.Emit(OpCodes.Ret);// 調用動態生成的方法Func<int, int, int> add = (Func<int, int, int>)dynamicMethod.CreateDelegate(typeof(Func<int, int, int>));Console.WriteLine(add(2, 3)); // 輸出: 5}
}

4)反射替代方案

場景反射方案替代方案
高性能方法調用MethodInfo.Invoke表達式樹編譯委托
對象創建Activator.CreateInstance預編譯工廠類
類型檢查IsAssignableFrom泛型約束/模式匹配
元數據分析GetCustomAttributes源代碼生成

3. 安全注意事項

  • 訪問私有成員破壞封裝性,可能導致代碼脆弱。
  • 反射可繞過權限檢查,需謹慎處理敏感操作。
  • 避免反射未信任的程序集,防止安全漏洞。

4. 反射開發原則

  • 最小化原則:只在必要時使用反射(僅在動態擴展、框架開發等必要場景使用反射。 )
  • 緩存原則:避免重復反射操作
  • 安全原則:嚴格校驗輸入參數
  • 性能原則:優先使用編譯時方案
  • 封裝原則:封裝反射邏輯,將反射操作封裝在工具類中,降低業務代碼復雜度。

六、反射與dynamic 關鍵字

1. 替代方案

  • 對于簡單場景,優先使用 dynamic 關鍵字:
    dynamic obj = new Person();
    obj.Name = "李四"; // 動態綁定
    

2. 反射與 dynamic 的區別

  • 反射:通過 Type 對象顯式操作類型成員,靈活性高但性能低。
  • dynamic:編譯時靜態類型,運行時動態綁定,語法簡潔但功能受限。

3. 反射與dynamic

internal class Program
{static void Main(string[] args){Type type = typeof(User);object o_user = Activator.CreateInstance(type);//o_user.Show() //不可能通過o_class1 調用Showdynamic d_user = Activator.CreateInstance(type);d_user.Show("sss");//可以通過d_user 調用方法Show//其實o_user 和 d_user得到結果都是一樣的,// 但是因為 object 時編譯時類型,object本身沒有Show方法,因此調用會報錯// 而dynamic 是運行時類型,編譯狀態下會繞過編譯器的檢查,直到真正運行后才確定其數據類型Console.ReadLine();}
}

結語

回到目錄頁:C#/.NET 知識匯總
希望以上內容可以幫助到大家,如文中有不對之處,還請批評指正。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/72839.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/72839.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/72839.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

YOLO+OpenCV強強聯手:高精度跌倒檢測技術實戰解析

目錄 關于摔倒檢測 摔倒檢測核心邏輯 摔倒檢測:聯合多種邏輯判斷 原理詳細解釋 1. 導入必要的庫 2. 定義函數和關鍵點連接關系 3. 篩選有效關鍵點并計算邊界框 4. 計算人體上下半身中心點和角度 5. 繪制關鍵點和連接線 6. 繪制角度標注和檢測跌倒 7. 返回處理后的圖…

AI入門7:python三種API方式調用本地Ollama+DeepSeek

回顧 書接上篇&#xff1a;各種方式搭建了本地知識庫&#xff1a; AI入門&#xff1a;AI模型管家婆ollama的安裝和使用-CSDN博客 AI入門2&#xff1a;本地AI部署&#xff0c;用ollama部署deepseek&#xff08;私有化部署&#xff09;-CSDN博客 AI入門3&#xff1a;給本地d…

內網安全-橫向移動Kerberos 攻擊SPN 掃描WinRMWinRSRDP

1.WinRM&WinRS 條件&#xff1a; 雙方開啟winrm winrs服務 2008版本以上默認開啟&#xff0c;win 7默認關閉 檢測使用cs內置端口掃描5985開放情況 進行連接 winrs -r:http://192.168.93.30:5985 -u:administrator -p:Whoami2021 whoami 2.內網-spn shell setspn -T …

LoRA中黑塞矩陣、Fisher信息矩陣是什么

LoRA中黑塞矩陣、Fisher信息矩陣是什么 1. 三者的核心概念 黑塞矩陣(Hessian) 二階導數矩陣,用于優化問題中判斷函數的凸性(如牛頓法),或計算參數更新方向(如擬牛頓法)。 Fisher信息矩陣(Fisher Information Matrix, FIM) 統計學中衡量參數估計的不確定性,反映數據…

高級java每日一道面試題-2025年3月04日-微服務篇[Eureka篇]-Eureka是什么?

如果有遺漏,評論區告訴我進行補充 面試官: Eureka是什么&#xff1f; 我回答: 在Java高級面試中&#xff0c;關于Eureka的討論通常會涵蓋其基本概念、組件與架構、工作原理、高級特性以及與其他服務發現工具的比較等多個方面。以下是結合提供的內容對Eureka進行的詳細解析和…

YZi Labs 談對 Plume 的投資:利用區塊鏈創造現實價值的典范項目

3 月 17 日&#xff0c;YZi Labs 宣布投資 RWAfi 賽道項目 Plume&#xff0c;引發市場廣泛關注。本輪融資是 Plume 在 去年 5 月和 12 月 連續兩輪融資后的第三輪融資&#xff0c;代表著市場資本市場對于 Plume RWAfi 敘事以及其發展潛力的高度認可。 本次融資不僅提升了市場對…

互功率譜 cpsd

互功率譜(Cross-Power Spectral Density, CPSD)是信號處理中用于描述兩個信號在頻域中相關性的工具。它表示兩個信號在不同頻率下的功率分布及其相位關系,廣泛應用于模態分析、系統辨識和信號匹配等領域。 matlab 實現 MATLAB 提供了 cpsd 函數來計算互功率譜。以下是使用 …

RocketMQ 架構

一、RocketMQ 核心架構概述 ?1. 主要組件 ?Name Server&#xff1a; 集群的「中樞神經」&#xff0c;負責 Topic 元數據管理&#xff08;如 Topic 分區分布、Broker 節點狀態監控&#xff09;。 ?Broker&#xff1a; 消息存儲與流轉的核心節點&#xff0c;負責消息的持久化…

單片機學完開發板,如何繼續提升自己的技能?

很多人學完開發板后都會卡在一個尷尬的階段&#xff1a;覺得自己會的東西不少&#xff0c;但又不知道下一步該干啥。會點C語言&#xff0c;能燒錄程序&#xff0c;能點亮LED&#xff0c;玩轉按鍵&#xff0c;搞定串口等等&#xff0c;能用開發板做點小玩意兒&#xff0c;但面對…

olmOCR大模型:支持結構化精準提取復雜PDF文件內容

基于streamlit與olmOCR大模型實現的pdf提取工具 import os import json import subprocess import pandas as pd from pathlib import Path import shutil import time import re import streamlit as st# 創建工作目錄 WORKSPACE_DIR "olmocr_workspace" os.maked…

五模型對比!Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多變量時間序列預測

目錄 預測效果基本介紹程序設計參考資料 預測效果 基本介紹 光伏功率預測&#xff01;五模型對比&#xff01;Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多變量時間序列預測(Matlab2023b 多輸入單輸出) 1.程序已經調試好&#xff0c;替換數據集后&#xff0c;僅運…

druid開啟防火墻之后的bug

bug以及解決方案 不允許執行多個語句不允許有注釋部分數據有誤識別&#xff0c;拋出異常&#xff0c;導致原本正常執行的語句被中斷 解決方案 application.yaml中對于druid配置如下&#xff1a; wall:enabled: true # 開啟防火墻config:multi-statement-allow: true # 允許多個…

SQLMesh 系列教程:Airbnb數據分析項目實戰

在本文中&#xff0c;我們將探討如何利用dbt項目的代碼庫來實現一個簡單的SQLMesh項目。本文的基礎是基于Udemy講師為dbt課程創建的示例項目&#xff0c;可以在這個GitHub repo中獲得。這個dbt項目是相對完整的示例&#xff0c;我們將使用它作為模板來演示SQLMesh&#xff08;下…

單片機寫的小液晶屏驅動+漢字滾屏

單片機寫的小液晶屏驅動漢字滾屏 stm32f401freertos內置HZK16 單片機漢字滾屏

【Golang那些事】go1.22和1.23 更新重點及測評

好久沒有寫文章了&#xff0c;攢了一年的Golang版本特性的技術點以及踩過的坑&#xff0c;那就在新年第一篇的文章中做一個總結吧&#xff1a; 一、關于迭代器 (一)迭代器去掉了共享共享內存 一個經典的面試題 說到Golang經典的面試題&#xff0c;大家可能都刷到過很多&…

python力扣438.找到字符串中所有字母異位詞

給定兩個字符串 s 和 p&#xff0c;找到 s 中所有 p 的 異位詞的子串&#xff0c;返回這些子串的起始索引。不考慮答案輸出的順序。 示例 1: 輸入: s “cbaebabacd”, p “abc” 輸出: [0,6] 解釋: 起始索引等于 0 的子串是 “cba”, 它是"abc" 的異位詞。 起始索引…

【大模型實戰篇】使用GPTQ量化QwQ-32B微調后的推理模型

1. 量化背景 之所以做量化&#xff0c;就是希望在現有的硬件條件下&#xff0c;提升性能。量化能將模型權重從高精度&#xff08;如FP32&#xff09;轉換為低精度&#xff08;如INT8/FP16&#xff09;&#xff0c;內存占用可減少50%~75%。低精度運算&#xff08;如INT8&#xf…

【MySQL】架構

MySQL架構 和其它數據庫相比&#xff0c;MySQL有點與眾不同&#xff0c;它的架構可以在多種不同場景中應用并發揮良好作用。主要體現在存儲引擎的架構上&#xff0c;插件式的存儲引擎架構將查詢處理和其它的系統任務以及數據的存儲提取相分離。這種架構可以根據業務的需求和實…

JavaScript 金額運算精度丟失問題及解決方案

JavaScript 金額運算精度丟失問題及解決方案 1. 前言2. 為什么 JavaScript 計算金額會精度丟失&#xff1f;2.1 JavaScript 使用 IEEE 754 雙精度浮點數2.2 浮點運算錯誤示例**錯誤示例 1&#xff1a;0.1 0.2 ≠ 0.3****錯誤示例 2&#xff1a;浮點乘法精度問題** 3. 解決方案…

Docker安裝,并pullMySQL和redis

卸載原Docker 您的 Linux 發行版可能提供非官方的 Docker 軟件包&#xff0c;這可能與 Docker 提供的官方軟件包沖突。在安裝 Docker Engine 正式版之前&#xff0c;您必須先卸載這些軟件包。 sudo dnf remove docker \ docker-client \ docker-client-latest \ docker-common…