总目录
前言
反射是.NET框架中一个强大的特性,允许程序在运行时检查和操作类型信息。通过反射,开发者可以动态地创建对象、调用方法、访问属性等,为程序提供了极大的灵活性。本文将详细讲解C#反射的使用方法及其应用场景。
一、什么是反射?
1. 定义
- 反射(Reflection) 是指程序在运行时能够检查和操作其自身的类型信息。通过反射,可以获取类型的成员(如方法、属性、事件等)并动态地调用它们。
- 在.NET框架中,反射的主要实现位于
System.Reflection
命名空间中。在程序运行时通过 System.Reflection 命名空间提供的 API,可完成如下操作:- 获取类型信息:如类名、命名空间、继承关系、实现的接口等。
- 动态创建对象:无需显式实例化类型。
- 访问成员:包括私有字段、方法、属性等。
- 执行方法:通过方法名或参数动态调用。
反射,简单来说,就是程序在运行时能够自我检查,获取类型、方法、字段等元数据信息的能力。
2. 作用
- 动态类型操作:在运行时获取类型信息(元数据),并根据这些信息执行相应的操作(动态创建对象实例或执行方法)。
- 实现插件系统:通过反射加载外部程序集,实现可扩展的插件架构。
- 序列化/反序列化:获取对象的字段和属性,实现数据的序列化和反序列化。
- 自定义特性处理:读取类型或成员上的自定义特性,根据特性进行特定处理。
- 代码分析工具:开发调试器或分析工具时,反射可以帮助你获取程序内部的状态。
3. 反射相关类与命名空间
反射的核心功能依赖于 System.Reflection
命名空间中的类型,其中包含 Type
、Assembly
、MethodInfo
等关键类。
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
对象,然后使用GetValue
和SetValue
方法访问或设置属性值。
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
对象,然后使用GetValue
和SetValue
方法访问或设置属性值。
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
对象进行缓存 - 预获取
MethodInfo
、PropertyInfo
并缓存。
- 对重复使用的
- 使用表达式树:对于某些复杂的反射操作,可以使用表达式树来生成高效的IL代码。
- 预编译表达式:使用
System.Linq.Expressions
生成动态方法 - 使用 Delegate 或 Expression:将反射调用转为委托提高性能
- 预编译表达式:使用
- 减少反射调用次数:尽量减少不必要的反射调用,尤其是在循环中。
- BindingFlags优化:精确指定成员搜索范围
2)基准测试对比
操作类型 | 直接调用 | 反射调用 | 委托缓存 | Emit IL |
---|---|---|---|---|
简单方法调用(100万次) | 5ms | 6500ms | 15ms | 8ms |
属性访问(100万次) | 2ms | 3200ms | 10ms | 6ms |
以上数据,仅供参考。
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}");}
}
代码说明
-
Person 类:
- 包含两个属性:
Name
和Age
。 - 提供了一个构造函数用于初始化这两个属性。
- 包含两个属性:
-
PropertyGetterFactory 类:
CreatePropertyGetter
方法接收一个PropertyInfo
对象作为参数。- 使用表达式树构建一个从对象到其属性值的委托(
Func<object, object>
)。 - 这个委托可以直接调用,传入目标对象,返回该对象对应属性的值。
-
Main 方法:
- 创建一个
Person
实例并初始化其属性。 - 获取
Person
类的Name
属性的PropertyInfo
。 - 调用
CreatePropertyGetter
方法生成属性访问器。 - 使用生成的属性访问器获取
personObj
的Name
属性值,并输出结果。
- 创建一个
输出结果
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}");}}
}
代码说明
-
Person 类:
- 包含两个属性:
Name
和Age
。 - 提供了一个无参构造函数,初始化
Name
为"Unknown",Age
为0。 - 重写了
ToString
方法,方便输出对象信息。
- 包含两个属性:
-
ObjectActivatorFactory 类:
CreateParameterlessConstructor
方法接收一个Type
对象作为参数。- 首先检查该类型是否包含无参构造函数,如果没有,则抛出异常。
- 使用
DynamicMethod
创建一个新的动态方法,返回类型为object
,没有参数。 - 使用
ILGenerator
生成中间语言(IL)代码,调用指定类型的无参构造函数并返回新创建的对象。 - 最后,将动态方法编译为委托并返回。
-
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 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。