VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • 反射:动态获取类型信息

反射:动态获取类型信息
概述
反射(Reflection)是C#中动态获取类型信息的核心技术,允许程序在运行时访问、检查甚至修改类型的成员(类、属性、方法、字段等),而无需在编译时知道类型的具体信息。反射打破了静态类型的限制,常用于实vb.net教程C#教程python教程SQL教程access 2010教程
现插件化架构、ORM框架(如Entity Framework)、依赖注入容器(如Autofac)等场景,但也存在性能代价,需合理使用。

  1. 核心反射类与命名空间
    反射的核心类位于System.Reflection命名空间:
    Type:表示类型的元数据(类、接口、结构体等);
    ConstructorInfo:表示构造函数;
    MethodInfo:表示方法;
    PropertyInfo:表示属性;
    FieldInfo:表示字段;
    BindingFlags:控制反射时成员的搜索范围(如公共/私有、实例/静态)。
  2. 实例代码:反射操作完整流程
    以下实例通过反射操作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}");
	}
	}
	}
  1. 逐行讲解
    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:参数类型不匹配时抛出。
  2. 基础知识拓展
    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解析)
  1. 常见问题与解决
    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. 最佳实践
    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


相关教程