.NET进阶——深入理解反射(1)反射入门

130 阅读7分钟

反射(Reflection)从入门到精通:C# 完整指南

反射是 C# 中最强大的特性之一,它允许程序在运行时动态地 “观察” 和 “操作” 自身的结构(类、方法、属性、字段等),无需在编译时知道具体类型。

对开发者来说,反射是实现 “灵活设计” 的核心工具(比如工厂模式、依赖注入、ORM 框架),但也是一把双刃剑(性能开销、破坏封装)。下面从基础概念→核心组件→常用操作→实战案例,一步步教你掌握反射。

一、反射的核心定义

1. 通俗理解

反射就像:

  • 你买了一台手机(程序),但不知道它的内部结构(类、方法);
  • 反射是一个 “透视镜”,让你能在手机运行时(程序运行时)看到内部的芯片(类)、电路板(方法)、接口(属性);
  • 甚至可以用反射 “修改” 手机内部的配置(动态调用方法、修改属性)。

2. 官方定义

反射是指程序在运行时,能够:

  • 获取类型信息:知道一个对象是什么类型、有哪些方法 / 属性 / 字段;
  • 动态创建实例:无需new关键字,根据类型名创建对象;
  • 动态调用成员:无需编译时绑定,调用对象的方法、访问属性 / 字段;
  • 操作泛型:动态创建泛型类型、调用泛型方法;
  • 修改元数据:(高级)修改程序集的元数据(如特性)。

二、反射的核心组件(必知)

反射的所有操作都基于 .NET 的几个核心类,位于 System.Reflection 命名空间:

核心类 / 接口作用示例用法
Type表示类型的元数据(反射的核心)Type type = typeof(string);
Assembly表示程序集(.dll/.exe)Assembly asm = Assembly.Load("MyAssembly");
MethodInfo表示方法的元数据MethodInfo method = type.GetMethod("ToString");
PropertyInfo表示属性的元数据PropertyInfo prop = type.GetProperty("Length");
FieldInfo表示字段的元数据FieldInfo field = type.GetField("_name", BindingFlags.NonPublic);
ConstructorInfo表示构造函数的元数据ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes);
ParameterInfo表示方法 / 构造函数的参数元数据ParameterInfo[] paras = method.GetParameters();

三、反射的基础操作(从简单到复杂)

0. 准备工作:引入命名空间

所有反射操作都需要引入 System.Reflection

using System.Reflection;

1. 操作 1:获取类型(Type)

Type 是反射的 “入口”,获取 Type 对象的 3 种常用方式:

方式 1:typeof() 运算符(编译时已知类型)
// 获取string类型的Type对象
Type stringType = typeof(string);
Console.WriteLine(stringType.Name); // 输出:String
Console.WriteLine(stringType.FullName); // 输出:System.String
方式 2:GetType() 方法(已知对象实例)
string str = "Hello";
Type strType = str.GetType();
Console.WriteLine(strType == typeof(string)); // 输出:True(同一个Type对象)
方式 3:Type.GetType() 方法(运行时已知类型名)
// 完整类型名(命名空间+类名)
Type logType = Type.GetType("Singleton.LogManager");
if (logType != null)
{
    Console.WriteLine($"获取到类型:{logType.Name}");
}

2. 操作 2:动态创建实例(无需new

通过反射创建对象的 2 种方式:

方式 1:Activator.CreateInstance()(最常用)
// 1. 获取Type对象
Type logType = typeof(Singleton.LogManager);

// 2. 创建实例(调用无参构造函数)
object logInstance = Activator.CreateInstance(logType);

// 3. 转换为具体类型(安全转换)
Singleton.LogManager log = logInstance as Singleton.LogManager;
if (log != null)
{
    log.WriteLog("反射创建实例成功!");
}
方式 2:ConstructorInfo.Invoke()(调用指定构造函数)
// 1. 获取Type对象
Type personType = typeof(Person);

// 2. 获取带参构造函数(参数:string name, int age)
ConstructorInfo ctor = personType.GetConstructor(new Type[] { typeof(string), typeof(int) });

// 3. 调用构造函数创建实例
object personInstance = ctor.Invoke(new object[] { "张三", 20 });

// 4. 使用实例
Person person = personInstance as Person;
Console.WriteLine(person.Name); // 输出:张三

3. 操作 3:动态调用方法

通过反射调用对象的方法:

示例:调用LogManager.WriteLog()方法
// 1. 创建实例(或已有实例)
Singleton.LogManager log = Singleton.LogManager.GetInstance();

// 2. 获取Type对象
Type logType = log.GetType();

// 3. 获取方法信息(方法名:WriteLog,参数:string)
MethodInfo writeLogMethod = logType.GetMethod("WriteLog", new Type[] { typeof(string) });

// 4. 调用方法(参数:实例对象,方法参数数组)
writeLogMethod.Invoke(log, new object[] { "反射调用方法成功!" });

4. 操作 4:动态访问 / 修改属性和字段

访问属性(Property)
// 1. 创建实例
Person person = new Person("李四", 25);

// 2. 获取Type对象
Type personType = person.GetType();

// 3. 获取属性信息(属性名:Name)
PropertyInfo nameProp = personType.GetProperty("Name");

// 4. 读取属性值
string name = (string)nameProp.GetValue(person);
Console.WriteLine($"原始姓名:{name}"); // 输出:李四

// 5. 修改属性值
nameProp.SetValue(person, "王五");
Console.WriteLine($"修改后姓名:{person.Name}"); // 输出:王五
访问私有字段(Field)
// 1. 创建实例
Person person = new Person("赵六", 30);

// 2. 获取Type对象
Type personType = person.GetType();

// 3. 获取私有字段(字段名:_age,需指定BindingFlags)
FieldInfo ageField = personType.GetField("_age", BindingFlags.NonPublic | BindingFlags.Instance);

// 4. 读取私有字段值
int age = (int)ageField.GetValue(person);
Console.WriteLine($"私有字段_age:{age}"); // 输出:30

// 5. 修改私有字段值
ageField.SetValue(person, 35);
Console.WriteLine($"修改后年龄:{person.Age}"); // 输出:35(假设Age属性返回_age)

5. 操作 5:反射与泛型

反射可以动态创建泛型类型、调用泛型方法:

示例 1:动态创建泛型List<string>
// 1. 获取泛型类型定义(List<>)
Type listGenericType = typeof(List<>);

// 2. 指定泛型参数(string),创建具体类型
Type stringListType = listGenericType.MakeGenericType(typeof(string));

// 3. 创建实例
object listInstance = Activator.CreateInstance(stringListType);

// 4. 转换为IList<string>使用
IList<string> stringList = listInstance as IList<string>;
stringList.Add("反射创建泛型List");
Console.WriteLine(stringList[0]); // 输出:反射创建泛型List
示例 2:动态调用泛型方法
// 1. 定义一个带泛型方法的类
public class GenericHelper
{
    public void GenericMethod<T>(T value)
    {
        Console.WriteLine($"泛型方法调用:{value},类型:{typeof(T).Name}");
    }
}

// 2. 反射调用泛型方法
GenericHelper helper = new GenericHelper();
Type helperType = helper.GetType();

// 3. 获取泛型方法定义
MethodInfo genericMethodDef = helperType.GetMethod("GenericMethod");

// 4. 指定泛型参数(int),创建具体方法
MethodInfo intMethod = genericMethodDef.MakeGenericMethod(typeof(int));

// 5. 调用方法
intMethod.Invoke(helper, new object[] { 123 }); // 输出:泛型方法调用:123,类型:Int32

四、反射的核心工具:BindingFlags(绑定标志)

当访问私有成员(私有方法 / 属性 / 字段)或静态成员时,需要指定 BindingFlags 来控制反射的搜索范围:

常用 BindingFlags 组合作用
BindingFlags.Public搜索公共成员
BindingFlags.NonPublic搜索私有 / 保护 / 内部成员
BindingFlags.Instance搜索实例成员
BindingFlags.Static搜索静态成员
`BindingFlags.PublicBindingFlags.Instance`搜索公共实例成员
`BindingFlags.NonPublicBindingFlags.Instance`搜索私有实例成员

示例:获取私有静态字段(如单例的_instance

Type logType = typeof(Singleton.LogManager);
// 获取私有静态字段:_instance
FieldInfo instanceField = logType.GetField("_instance", BindingFlags.NonPublic | BindingFlags.Static);
object instance = instanceField.GetValue(null); // 静态字段传null

五、反射的实战案例(结合单例模式)

案例 1:反射破坏单例模式

单例模式的私有构造函数,可被反射调用,破坏单例:

// 1. 获取单例类的Type
Type singletonType = typeof(Singleton.RedisCacheManager);

// 2. 获取私有构造函数
ConstructorInfo ctor = singletonType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);

// 3. 调用私有构造函数,创建多个实例
object instance1 = ctor.Invoke(null);
object instance2 = ctor.Invoke(null);

// 4. 验证是否为不同实例
Console.WriteLine(instance1 == instance2); // 输出:False(单例被破坏)

防护方案:在构造函数中添加校验):

private RedisCacheManager()
{
    if (_instance != null)
    {
        throw new InvalidOperationException("单例类不允许创建多个实例!");
    }
}

案例 2:反射工厂模式(动态创建支付服务)

基于反射的工厂模式,无需硬编码,可通过配置动态切换实现:

// 支付服务接口
public interface IPaymentService
{
    string Pay(decimal amount, string orderId);
}

// 支付宝实现
public class AlipayService : IPaymentService
{
    public string Pay(decimal amount, string orderId) => $"支付宝支付:{orderId}{amount}元";
}

// 微信支付实现
public class WeChatPayService : IPaymentService
{
    public string Pay(decimal amount, string orderId) => $"微信支付:{orderId}{amount}元";
}

// 反射工厂
public class PaymentFactory
{
    // 从配置文件读取支付类型(如appsettings.json)
    private static string _paymentType = "AlipayService"; // 可动态修改

    public static IPaymentService CreatePayment()
    {
        try
        {
            // 1. 获取当前程序集
            Assembly asm = Assembly.GetExecutingAssembly();
            
            // 2. 获取类型(假设在当前命名空间)
            Type paymentType = asm.GetType($"YourNamespace.{_paymentType}");
            
            // 3. 创建实例并转换为接口
            return Activator.CreateInstance(paymentType) as IPaymentService;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"创建支付服务失败:{ex.Message}");
            return new AlipayService(); // 默认实现
        }
    }
}

// 调用
IPaymentService payment = PaymentFactory.CreatePayment();
payment.Pay(100, "20251205001"); // 输出:支付宝支付:20251205001,100元

六、反射的优缺点(何时使用?)

优点

  1. 灵活性:无需编译时绑定,可动态扩展(如插件化架构);
  2. 解耦:减少硬编码依赖,便于替换实现(如支付服务切换);
  3. 通用性:可编写通用工具(如序列化、ORM 框架);
  4. 元数据访问:可读取类型的特性(Attribute),实现声明式编程。

缺点

  1. 性能开销:反射比直接调用慢(约 10-100 倍),频繁调用会影响性能;
  2. 破坏封装:可访问私有成员,违反面向对象的封装原则;
  3. 类型安全:编译时无法检查类型,运行时可能抛出异常;
  4. 代码可读性差:反射代码比直接调用更复杂,不易维护。

适用场景

  • 框架开发:依赖注入(DI)、ORM(EF Core)、序列化(JSON.NET);
  • 工厂模式:动态创建对象,减少硬编码;
  • 插件化架构:动态加载外部程序集(如扩展插件);
  • 元数据处理:读取 / 修改特性(Attribute)、生成文档。

不适用场景

  • 高频调用:如循环中频繁调用的方法;
  • 性能敏感场景:如游戏、实时系统;
  • 简单业务逻辑:直接调用更简洁、高效。

七、反射的性能优化

如果必须使用反射,可通过以下方式优化性能:

  1. 缓存反射信息:将TypeMethodInfo等缓存到静态字段,避免重复获取;
  2. 使用Delegate.CreateDelegate:将MethodInfo转换为委托,调用速度接近直接调用;
  3. 使用表达式树(Expression Tree) :比反射更快,适合动态创建方法调用;
  4. 避免频繁创建实例:尽量复用已创建的实例。

示例:缓存 MethodInfo

public class ReflectionCache
{
    // 静态字典缓存MethodInfo
    private static readonly Dictionary<string, MethodInfo> _methodCache = new Dictionary<string, MethodInfo>();

    public static MethodInfo GetCachedMethod(Type type, string methodName, Type[] parameterTypes)
    {
        string key = $"{type.FullName}.{methodName}_{string.Join(",", parameterTypes.Select(t => t.Name))}";
        if (!_methodCache.TryGetValue(key, out MethodInfo method))
        {
            method = type.GetMethod(methodName, parameterTypes);
            _methodCache[key] = method;
        }
        return method;
    }
}

八、总结:反射学习路径

  1. 基础概念:理解反射的定义、核心组件(Type、Assembly 等);
  2. 基础操作:掌握获取 Type、创建实例、调用方法、访问属性 / 字段;
  3. 进阶操作:学习反射与泛型、BindingFlags、特性(Attribute);
  4. 实战案例:结合工厂模式、单例防护、DI 框架等场景练习;
  5. 性能优化:了解反射的性能开销,掌握缓存等优化技巧;
  6. 最佳实践:明确反射的适用场景,避免滥用。

反射是 C# 开发的 “高级技能”,掌握它能让你写出更灵活、可扩展的代码,但也要注意其局限性。建议先从简单的案例入手(如反射创建实例、调用方法),逐步深入到复杂场景(如泛型、特性处理)。