-
反射:动态获取类型信息
反射:动态获取类型信息
概述
反射(Reflection)是C#中动态获取类型信息的核心技术,允许程序在运行时访问、检查甚至修改类型的成员(类、属性、方法、字段等),而无需在编译时知道类型的具体信息。反射打破了静态类型的限制,常用于实vb.net教程C#教程python教程SQL教程access 2010教程
现插件化架构、ORM框架(如Entity Framework)、依赖注入容器(如Autofac)等场景,但也存在性能代价,需合理使用。
-
核心反射类与命名空间
反射的核心类位于System.Reflection命名空间:
Type:表示类型的元数据(类、接口、结构体等);
ConstructorInfo:表示构造函数;
MethodInfo:表示方法;
PropertyInfo:表示属性;
FieldInfo:表示字段;
BindingFlags:控制反射时成员的搜索范围(如公共/私有、实例/静态)。 -
实例代码:反射操作完整流程
以下实例通过反射操作User类,覆盖获取类型信息、创建对象、调用方法、访问私有成员等核心场景:
csharp
using System;
using System.Reflection; // 必须引用反射命名空间
// 目标类:包含公共/私有成员
public class User
{
public int Id { get; set; }
public string Name { get; set; }
private int _age; // 私有字段
// 构造函数
public User(int id, string name)
{
Id = id;
Name = name;
_age = 18; // 默认年龄
}
// 公共方法
public void SayHello()
{
Console.WriteLine($"Hello, I'm {Name}, {_age} years old.");
}
// 私有方法
private void SetAge(int age)
{
if (age > 0) _age = age;
}
}
class ReflectionExample
{
static void Main()
{
try
{
// -------------------------- 1. 获取Type对象 --------------------------
Type userType = typeof(User); // 通过typeof获取Type(编译时已知类型)
// 或通过对象实例获取:User user = new User(1,"Alice"); Type userType = user.GetType();
Console.WriteLine($"类型名称:{userType.Name}");
Console.WriteLine($"完整类型名:{userType.FullName}");
Console.WriteLine($"是否为类:{userType.IsClass}");
// -------------------------- 2. 创建对象(通过构造函数) --------------------------
// 获取构造函数:参数类型为int和string
ConstructorInfo ctor = userType.GetConstructor(new Type[] { typeof(int), typeof(string) });
if (ctor == null) throw new MissingMethodException("找不到指定构造函数");
// 调用构造函数创建对象
object userObj = ctor.Invoke(new object[] { 1, "Alice" }); // 参数:id=1, name=Alice
// -------------------------- 3. 调用公共方法 --------------------------
MethodInfo sayHelloMethod = userType.GetMethod("SayHello"); // 获取公共方法
sayHelloMethod.Invoke(userObj, null); // 调用方法(无参数)
// -------------------------- 4. 修改私有字段 --------------------------
// 获取私有字段:需指定BindingFlags(非公共+实例)
FieldInfo ageField = userType.GetField("_age", BindingFlags.NonPublic | BindingFlags.Instance);
if (ageField == null) throw new MissingFieldException("找不到私有字段_age");
ageField.SetValue(userObj, 25); // 修改私有字段值
sayHelloMethod.Invoke(userObj, null); // 再次调用方法验证变化
// --------------------------5. 调用私有方法 --------------------------
MethodInfo setAgeMethod = userType.GetMethod("SetAge", BindingFlags.NonPublic | BindingFlags.Instance);
if (setAgeMethod == null) throw new MissingMethodException("找不到私有方法SetAge");
setAgeMethod.Invoke(userObj, new object[] {30}); // 调用私有方法(参数30)
sayHelloMethod.Invoke(userObj, null);
// --------------------------6. 修改公共属性 --------------------------
PropertyInfo nameProp = userType.GetProperty("Name"); // 获取公共属性
nameProp.SetValue(userObj, "Bob"); // 修改属性值
sayHelloMethod.Invoke(userObj, null);
}
catch (TargetInvocationException ex)
{
// 反射调用方法时,目标方法抛出的异常会被包装在TargetInvocationException中
Console.WriteLine($"方法执行错误:{ex.InnerException.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"反射操作错误:{ex.Message}");
}
}
}
-
逐行讲解
3.1 获取Type对象
Type userType = typeof(User):通过typeof关键字直接获取类型的元数据(编译时已知类型);
user.GetType():通过对象实例获取Type(运行时动态获取,适用于未知类型的对象);
Type对象包含类型的所有信息:名称、父类、接口、成员等。
3.2 创建对象
GetConstructor(new Type[] {typeof(int), typeof(string)}):根据参数类型数组查找构造函数;
ctor.Invoke(new object[] {1, "Alice"}):调用构造函数,传入参数值数组,返回对象实例;
若构造函数不存在,GetConstructor返回null,需处理MissingMethodException。
3.3 调用公共方法
GetMethod("SayHello"):默认查找公共实例方法;
Invoke(userObj, null):第一个参数是对象实例(静态方法为null),第二个参数是方法参数数组(无参数则为null)。
3.4 修改私有字段
GetField("_age", BindingFlags.NonPublic | BindingFlags.Instance):必须指定BindingFlags组合,否则无法获取私有成员;
SetValue(userObj,25):修改私有字段的值,第一个参数是对象实例,第二个是新值。
3.5 调用私有方法
GetMethod("SetAge", BindingFlags.NonPublic | BindingFlags.Instance):同理,需指定BindingFlags;
Invoke(userObj, new object[] {30}):传入方法参数值数组。
3.6 修改公共属性
GetProperty("Name"):获取公共属性;
SetValue(userObj, "Bob"):修改属性值(内部调用属性的set方法)。
3.7 异常处理
TargetInvocationException:反射调用方法时,目标方法抛出的异常会被包装在此异常中,需通过InnerException获取实际错误;
MissingMethodException/FieldException:找不到指定成员时抛出;
ArgumentException:参数类型不匹配时抛出。 -
基础知识拓展
4.1 反射的性能代价
反射比直接调用慢10-100倍,原因是:
动态解析类型元数据(编译时无法优化);
装箱/拆箱操作(Invoke参数为object数组);
安全检查(访问私有成员时)。
优化方法:
缓存反射结果:将Type、MethodInfo、PropertyInfo等对象缓存到静态字典中,避免重复获取;
使用表达式树:用Expression动态生成委托,性能接近直接调用;
减少反射调用次数:尽量批量处理,避免循环内反射。
示例:缓存MethodInfo
csharp
private static readonly Dictionary<string, MethodInfo> _methodCache = new Dictionary<string, MethodInfo>();
public static MethodInfo GetCachedMethod(Type type, string methodName)
{
string key = $"{type.FullName}.{methodName}";
if (!_methodCache.ContainsKey(key))
{
_methodCache[key] = type.GetMethod(methodName);
}
return _methodCache[key];
}
4.2 BindingFlags详解
BindingFlags是位掩码,需组合使用:
| 标志 | 作用 |
|---|---|
| Instance | 搜索实例成员 |
| Static | 搜索静态成员 |
| Public | 搜索公共成员 |
| NonPublic | 搜索私有/保护/内部成员 |
| DeclaredOnly | 仅搜索当前类的成员(不包括继承的) |
常用组合:
获取公共实例方法:BindingFlags.Public | BindingFlags.Instance;
获取私有实例字段:BindingFlags.NonPublic | BindingFlags.Instance;
获取静态公共属性:BindingFlags.Public | BindingFlags.Static。
4.3 反射的应用场景
插件系统:通过Assembly.LoadFrom加载外部DLL,反射创建插件实例;
csharp
Assembly pluginAssembly = Assembly.LoadFrom("Plugin.dll");
Type pluginType = pluginAssembly.GetType("Plugin.MyPlugin");
IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType); // 假设IPlugin是插件接口
plugin.Run();
ORM框架:用反射映射实体类到数据库表(如属性名对应列名);
依赖注入:容器(如Autofac)用反射创建对象,自动注入依赖;
序列化/反序列化:JSON.NET用反射将对象转换为JSON字符串。
4.4 反射与Dynamic的区别
| 特性 | 反射 | Dynamic |
|---|---|---|
| 核心目的 | 获取类型元数据,动态操作成员 | 编译时不检查类型,运行时动态解析 |
| 灵活性 | 高(可访问私有成员、修改字段) | 低(仅支持公共成员调用) |
| 性能 | 较慢(需解析元数据) | 较慢(依赖反射,但比手动反射简洁) |
| 使用场景 | 插件系统、ORM、DI | 简化动态类型调用(如COM交互、JSON解析) |
-
常见问题与解决
5.1 无法获取私有成员
原因:未指定正确的BindingFlags组合;
解决:添加BindingFlags.NonPublic | BindingFlags.Instance(实例私有)或BindingFlags.NonPublic | BindingFlags.Static(静态私有)。
5.2 反射调用方法抛出TargetInvocationException
原因:目标方法内部抛出异常;
解决:通过ex.InnerException获取实际异常信息。
5.3 性能瓶颈
原因:重复获取Type/MethodInfo等对象;
解决:缓存反射结果,或用表达式树替代反射。
5.4 泛型方法的反射调用
问题:无法直接获取泛型方法(如List.Add(T item));
解决:用MakeGenericMethod指定泛型参数类型:
csharp
Type listType = typeof(List<int>);
MethodInfo addMethod = typeof(List<>).GetMethod("Add").MakeGenericMethod(typeof(int));
object listObj = Activator.CreateInstance(listType);
addMethod.Invoke(listObj, new object[] {10});
-
最佳实践
1.尽量避免反射:仅在动态场景下使用(如插件系统),优先用强类型调用;
2.缓存反射结果:将Type、MethodInfo等对象缓存到静态字典,减少重复解析;
3.精确指定BindingFlags:避免不必要的成员搜索,提高性能;
4.处理异常:反射操作易抛出异常,需捕获并处理(如MissingMethodException、TargetInvocationException);
5.使用表达式树优化:对于高频反射调用,用Expression树动态生成委托,性能接近直接调用。
总结
反射是C#中强大的动态编程工具,允许程序在运行时操作类型信息,但需权衡灵活性与性能。核心要点:
通过Type对象获取类型元数据;
用BindingFlags控制成员搜索范围;
缓存反射结果优化性能;
合理使用反射场景(插件、ORM、DI),避免滥用。
掌握反射能让你编写更灵活、通用的代码,但也要注意其局限性和性能代价。
(本节完)
下一章:LINQ查询语法与方法语法
(深入讲解LINQ的两种语法,以及如何用LINQ操作集合数据)
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49389.html
最新更新
自定义特性
反射:动态获取类型信息
异步方法定义与调用
目录操作与路径处理
文件读写:StreamReader/StreamWriter
try-catch-finally异常捕获
try-catch-finally异常捕获
队列(Queue)与栈(Stack)
常用集合:List、Dictionary、HashSet
正则表达式(Regex)基础
SQL SERVER中递归
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
uniapp/H5 获取手机桌面壁纸 (静态壁纸)
[前端] DNS解析与优化
为什么在js中需要添加addEventListener()?
JS模块化系统
js通过Object.defineProperty() 定义和控制对象
这是目前我见过最好的跨域解决方案!
减少回流与重绘
减少回流与重绘
如何使用KrpanoToolJS在浏览器切图
performance.now() 与 Date.now() 对比










