c#中的反射Reflection 是一个强大的机制,允许在运行时检查、访问和修改程序集、模块、类型及其成员(方法、属性、字段、事件等)的元数据,并动态创建实例和调用成员。他提供了极大的灵活性,但也要注意性能和安全性影响。
1. 反射的核心命名空间
反射功能主要位于 System.Reflection 和 System.Type 命名空间中。常用类包括:
-
Assembly:表示一个程序集,可以加载和检查其中的类型。 -
Type:表示类型声明(类、接口、数组、值类型、枚举等),是反射的核心。 -
MemberInfo:方法、属性、字段等的抽象基类。 -
MethodInfo、PropertyInfo、FieldInfo、EventInfo、ConstructorInfo:分别表示特定成员。 -
ParameterInfo:描述方法或构造函数的参数。 -
Attribute:处理自定义特性的基类。
2. 获取 Type 对象
要反射某个类型,首先需要获取其 Type 对象。常见方式:
// 1. 使用 typeof 运算符(编译时已知类型)
Type t1 = typeof(string);
// 2. 调用对象的 GetType() 方法(运行时获取)
string s = "hello";
Type t2 = s.GetType();
// 3. 使用 Type.GetType() 方法(通过名称字符串)
Type t3 = Type.GetType("System.String"); // 需要程序集限定名?简单类型可省略程序集
Type t4 = Type.GetType("System.Collections.Generic.List`1[[System.Int32]]"); // 泛型
3. 检查类型信息
通过 Type 对象可以获取类型名称、命名空间、基类、实现的接口、是否为抽象等:
Type type = typeof(List<int>);
Console.WriteLine(type.FullName); //System.Collections.Generic.List`1[[System.Int32, ...]]
Console.WriteLine(type.IsClass); // True
Console.WriteLine(type.IsGenericType); // True
Console.WriteLine(type.BaseType); // System.Object
4. 获取成员信息
csharp
Type type = typeof(string);
// 获取所有公共方法(包括继承的)
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
{
Console.WriteLine(method.Name);
}
// 获取特定方法,指定名称和参数类型(避免重载歧义)
MethodInfo substringMethod = type.GetMethod("Substring", new[] { typeof(int), typeof(int) });
// 获取所有公共属性
PropertyInfo[] properties = type.GetProperties();
// 获取所有公共字段(string 没有公共字段,但可以演示)
FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); // 包含私有字段
GetMethods()、GetProperties() 等可以接受 BindingFlags 枚举来指定搜索条件(如 Public、NonPublic、Instance、Static、DeclaredOnly 等)。
5. 动态创建对象
使用 Activator.CreateInstance 可以动态创建类型实例:
// 创建无参构造的对象
Type dictType = typeof(Dictionary<string, int>);
object dict = Activator.CreateInstance(dictType);
// 创建有参构造的对象
Type listType = typeof(List<int>);
object obj = Activator.CreateInstance(listType, 10); // 指定初始容量 10
List txtList=(List<int>)obj;
// 现在可以像普通 List<int> 一样使用
list.Add(100);
list.Add(200);
Console.WriteLine(list[0]); // 输出 100
// 或者通过 ConstructorInfo 调用
ConstructorInfo ctor = listType.GetConstructor(new[] { typeof(int) });
object list2 = ctor.Invoke(new object[] { 20 });
对于泛型类型,先获取开放泛型类型,再用 MakeGenericType 构造封闭类型:
Type openType = typeof(List<>);
Type closedType = openType.MakeGenericType(typeof(string));
object listOfString = Activator.CreateInstance(closedType);
6. 调用方法和访问属性/字段
一旦获得 MethodInfo、PropertyInfo 或 FieldInfo,就可以动态调用或读写。
调用方法:
Type type = typeof(string);
object str = "hello world";
MethodInfo method = type.GetMethod("ToUpper", new Type[0]); // 无参方法
object result = method.Invoke(str, null); // 返回 "HELLO WORLD"
// 带参数的方法
MethodInfo substrMethod = type.GetMethod("Substring", new[] { typeof(int), typeof(int) });
object substr = substrMethod.Invoke(str, new object[] { 0, 5 }); // "hello"
MethodInfo method = type.GetMethod("ToUpper", new Type[0]); // 无参方法
object result = method.Invoke(str, null); // 返回 "HELLO WORLD"
1. GetMethod 的作用
GetMethod 是 Type 类的一个方法,用于获取当前类型中指定名称的方法的元数据 (以 MethodInfo 对象返回)。如果方法有多个重载版本,你需要告诉编译器你想获取哪一个。
2. 为什么需要第二个参数 new Type[0]?
-
GetMethod可以只传一个方法名字符串,例如type.GetMethod("ToUpper")。但这样做有一个风险:如果存在多个同名方法(重载),编译器不知道你要哪一个,会抛出AmbiguousMatchException异常。 -
第二个参数是一个
Type[]数组,用来精确指定要查找的方法的参数类型列表 。new Type[0]创建了一个长度为0的Type数组 ,表示"没有任何参数",也就是我们要找的是无参数版本的 ToUpper 方法。 -
这里的
new Type[0]等价于Array.Empty<Type>(),它明确告诉GetMethod:"我要找的ToUpper方法不接受任何参数"。这样就能精确匹配到string.ToUpper()这个无参方法。
3. 为什么不能省略?
如果你只写 type.GetMethod("ToUpper"),对于 string 类型来说,ToUpper 确实只有一个无参版本(实际上还有 ToUpper(CultureInfo) 这种带参数的重载,但通常被忽略)。但在很多情况下,一个类可能有好几个同名方法(比如 Console.WriteLine 有十几个重载),如果不指定参数类型,就会引发歧义。因此,养成指定参数类型的习惯可以避免潜在的错误。
4. 其他常见写法
-
使用
Type.EmptyTypes静态字段(更易读):csharp
MethodInfo method = type.GetMethod("ToUpper", Type.EmptyTypes); -
对于有参数的方法,则传入对应参数类型的数组:
csharp
// 获取 Substring(int startIndex, int length) MethodInfo method = type.GetMethod("Substring", new Type[] { typeof(int), typeof(int) });
5. 这行代码的整体作用
-
type是string类型的Type对象。 -
type.GetMethod("ToUpper", new Type[0])找到了string类的无参ToUpper方法,并将其元数据赋值给method变量。 -
后续的
method.Invoke(str, null)就是在str这个实例上调用这个无参方法,相当于str.ToUpper()。
读写属性:
csharp
Type type = typeof(SomeClass);
object obj = Activator.CreateInstance(type);
PropertyInfo prop = type.GetProperty("SomeProperty");
prop.SetValue(obj, 42); // 设置属性值
int value = (int)prop.GetValue(obj); // 读取属性值
读写字段(包括私有):
csharp
FieldInfo field = type.GetField("_someField", BindingFlags.NonPublic | BindingFlags.Instance);
field.SetValue(obj, "new value");
string fieldValue = (string)field.GetValue(obj);
7. 使用自定义特性(Attributes)
反射常用于读取附加到类型或成员的自定义特性。
// 定义特性
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAttribute : Attribute
{
public string Description { get; set; }
public MyAttribute(string desc) { Description = desc; }
}
// 应用特性
[My("This is a test class")]
public class TestClass
{
[My("This is a method")]
public void TestMethod() { }
}
// 读取特性
Type type = typeof(TestClass);
var classAttr = type.GetCustomAttribute<MyAttribute>();
Console.WriteLine(classAttr?.Description);
MethodInfo method = type.GetMethod("TestMethod");
var methodAttr = method.GetCustomAttribute<MyAttribute>();
Console.WriteLine(methodAttr?.Description);
也可以使用 GetCustomAttributes() 获取所有特性。
8. 加载和检查程序集
反射可以加载程序集并遍历其中的类型:
csharp
// 从文件加载程序集
Assembly assembly = Assembly.LoadFrom("MyLibrary.dll");
// 获取所有类型
Type[] types = assembly.GetTypes();
foreach (Type t in types)
{
Console.WriteLine(t.FullName);
}
// 获取当前执行的程序集
Assembly current = Assembly.GetExecutingAssembly();
// 获取入口程序集
Assembly entry = Assembly.GetEntryAssembly();
9. 性能考虑
反射涉及元数据查找和动态调用,通常比直接代码慢得多(可能慢数十倍到数百倍)。如果频繁使用反射,可考虑以下优化:
-
缓存
MethodInfo、PropertyInfo等对象,避免重复查找。 -
使用
Delegate.CreateDelegate将方法转换为委托,提升调用性能。 -
使用表达式树(Expression Trees)动态生成代码并编译。
-
对于简单场景,考虑
dynamic关键字(基于 DLR,底层仍用反射但会缓存)。
10. 安全性与权限
-
反射可以访问私有成员,破坏了封装性,应谨慎使用。
-
在部分信任环境(如某些托管环境)中,反射可能受限(例如不能调用非公共成员)。
-
使用反射可能引发
SecurityException、MethodAccessException等异常。
11. 简单完整示例
下面是一个综合示例,演示如何动态创建对象、调用方法并读取特性:
public class Calculator
{
public int Add(int a, int b) => a + b;
[TestMethod]
public int Multiply(int a, int b) => a * b;
}
class Program
{
static void Main()
{
Type type = typeof(Calculator);
object obj = Activator.CreateInstance(type);
// 调用 Add 方法
MethodInfo addMethod = type.GetMethod("Add");
int result = (int)addMethod.Invoke(obj, new object[] { 3, 4 });
Console.WriteLine($"Add: {result}");
// 查找并调用带 TestMethod 特性的方法
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
{
if (method.GetCustomAttribute<TestMethodAttribute>() != null)
{
Console.WriteLine($"Executing {method.Name}...");
object output = method.Invoke(obj, new object[] { 3, 4 });
Console.WriteLine($"Result: {output}");
}
}
}
}
输出:
Add: 7
Executing Multiply...
Result: 12
12. 反射的常见应用场景
-
依赖注入容器:动态创建和组装对象。
-
序列化/反序列化:读取对象的字段和属性。
-
插件架构:从程序集动态加载类型。
-
ORM(对象关系映射):将数据库记录映射到对象属性。
-
单元测试框架:发现和运行带有特定特性的方法。
-
调试工具:检查对象内部状态。
13. 注意事项
-
避免在性能关键路径过度使用反射。
-
反射调用方法时参数类型必须严格匹配(包括派生关系),否则会引发
TargetException或ArgumentException。 -
处理泛型方法和类型时,需要正确处理类型参数。
-
注意访问权限:即使使用
BindingFlags.NonPublic也可能因安全策略失败。
总结
反射是 C# 中极为灵活的特性,允许在运行时探索和操作类型信息。掌握反射可以帮助你构建更动态、更可扩展的应用程序,但也要权衡性能和安全性。通过 Type、Assembly、MethodInfo 等类,你可以实现很多高级功能。希望这份复习对你有帮助!