c#反射复习

c#中的反射Reflection 是一个强大的机制,允许在运行时检查、访问和修改程序集、模块、类型及其成员(方法、属性、字段、事件等)的元数据,并动态创建实例和调用成员。他提供了极大的灵活性,但也要注意性能和安全性影响。

1. 反射的核心命名空间

反射功能主要位于 System.ReflectionSystem.Type 命名空间中。常用类包括:

  • Assembly:表示一个程序集,可以加载和检查其中的类型。

  • Type:表示类型声明(类、接口、数组、值类型、枚举等),是反射的核心。

  • MemberInfo:方法、属性、字段等的抽象基类。

  • MethodInfoPropertyInfoFieldInfoEventInfoConstructorInfo:分别表示特定成员。

  • 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 枚举来指定搜索条件(如 PublicNonPublicInstanceStaticDeclaredOnly 等)。

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. 调用方法和访问属性/字段

一旦获得 MethodInfoPropertyInfoFieldInfo,就可以动态调用或读写。

调用方法:

复制代码
 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 的作用

GetMethodType 类的一个方法,用于获取当前类型中指定名称的方法的元数据 (以 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. 这行代码的整体作用

  • typestring 类型的 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. 性能考虑

反射涉及元数据查找和动态调用,通常比直接代码慢得多(可能慢数十倍到数百倍)。如果频繁使用反射,可考虑以下优化:

  • 缓存 MethodInfoPropertyInfo 等对象,避免重复查找。

  • 使用 Delegate.CreateDelegate 将方法转换为委托,提升调用性能。

  • 使用表达式树(Expression Trees)动态生成代码并编译。

  • 对于简单场景,考虑 dynamic 关键字(基于 DLR,底层仍用反射但会缓存)。

10. 安全性与权限

  • 反射可以访问私有成员,破坏了封装性,应谨慎使用。

  • 在部分信任环境(如某些托管环境)中,反射可能受限(例如不能调用非公共成员)。

  • 使用反射可能引发 SecurityExceptionMethodAccessException 等异常。

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. 注意事项

  • 避免在性能关键路径过度使用反射。

  • 反射调用方法时参数类型必须严格匹配(包括派生关系),否则会引发 TargetExceptionArgumentException

  • 处理泛型方法和类型时,需要正确处理类型参数。

  • 注意访问权限:即使使用 BindingFlags.NonPublic 也可能因安全策略失败。

总结

反射是 C# 中极为灵活的特性,允许在运行时探索和操作类型信息。掌握反射可以帮助你构建更动态、更可扩展的应用程序,但也要权衡性能和安全性。通过 TypeAssemblyMethodInfo 等类,你可以实现很多高级功能。希望这份复习对你有帮助!

相关推荐
yongui478342 小时前
基于C#实现视频文件解封装与媒体流读取方案
开发语言·c#·媒体
游乐码21 小时前
c#万物之父装箱拆箱
开发语言·c#
GIS程序猿21 小时前
批量出图工具,如何使用C#实现动态文本
开发语言·arcgis·c#·arcgis插件·gis二次开发
量子物理学21 小时前
三、C#高级进阶语法——特性(Attribute)
java·算法·c#
量子物理学1 天前
四、C#高级进阶语法——委托(Delegate)
开发语言·c#
bepeater12341 天前
Laravel9.X核心特性全面解析
c语言·c++·c#·php
lpfasd1231 天前
Markdown 导出 Word 文档技术方案
开发语言·c#·word
m5655bj1 天前
通过 C# 将 PPT 文档转换为 HTML 格式
c#·html·powerpoint
未来之窗软件服务1 天前
AI人工智能(十五)C# AI的智障行为http服务—东方仙盟练气期
开发语言·http·c#