C#反射机制详解:从窥视成员到获取和修改私有数据

听雨眠
听雨眠
发布时间:2026-10-14 | 3人浏览过
原创

文章详细解析了C#中的反射技术,涵盖如何使用GetMembers配合BindingFlags全面检查类结构,以及如何利用FieldInfo、PropertyInfo和MethodInfo突破封装,实现读取和写入私有字段的真实值。

结果或目标

掌握如何在.NET环境中使用反射机制,不仅能在程序运行时查看类的内部构造(包括私有字段、属性和方法),还能在不改变源代码的情况下,动态地获取甚至篡改类的私有状态。

准备条件

本文假设读者具备C#基础语法知识,并在集成开发环境(如Visual Studio 2012及以上版本)中熟悉控制台应用程序的建立与调试。核心涉及命名空间:System.Reflection。

第1步:构建测试模型并定义类结构

为了演示反射的威力,我们需要一个包含各种访问修饰符的成员类。创建一个名为 RefClass 的类,其中包含不同可见度的字段和属性:

public class RefClass
{
    private int _test3;
    private int _test1 { get; set; }
    protected int Test2 { get; set; }
    public int Test3 { get; set; }
}

第2步:窥视类的全部公共成员

反射的第一步是理解目标类的结构。我们首先通过 typeof 获取类型,并使用 GetMembers() 进行初步扫描:

Type t = typeof(RefClass);
MemberInfo[] minfos = t.GetMembers();
foreach (MemberInfo minfo in minfos)
{
    Console.WriteLine(minfo.Name);
}

观察结果:

如上输出所示,默认情况下 GetMembers() 仅返回公开(Public)的成员。由于所有类最终都继承自 Object,因此也会看到 GetType、ToString 等基类方法。值得注意的是,私有(Private)和保护(Protected)成员在此模式下被过滤掉了。

第3步:深入探查——筛选特定访问级别的成员

如果我们想要查看包括私有、受保护成员在内的完整内容,需要借助 BindingFlags 枚举参数:

MemberInfo[] minfos = t.GetMembers(
    BindingFlags.NonPublic | 
    BindingFlags.Instance | 
    BindingFlags.Public
);

说明:

  • NonPublic: 包含非公共成员。
  • Instance: 包含实例成员(不包含静态成员)。
  • Public: 包含公共成员。

输出效果:

进阶操作:排除父类干扰

如果在上述基础上只想关注当前类(而非其父类 Object)定义的成员,只需追加 DeclaredOnly:

MemberInfo[] minfos = t.GetMembers(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);

此时结果变为:

第4步:识别成员的类型(字段 vs 方法 vs 属性)

单纯列出名字不够直观,我们可以利用 MemberType 进行区分。同时,如果希望查看到之前在步骤3中遗漏的静态方法,需加入 BindingFlags.Static:

// 假设 RefClass 中新增了 Show2() 和 Show3(string s) 方法... 
MemberInfo[] minfos = t.GetMembers(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static );
foreach (MemberInfo minfo in minfos)
{
    // 此处可根据 MemberTypes.Field / .Method / .Property 进行分类输出
    Console.WriteLine($"名称: {minfo.Name}; 类型: {minfo.MemberType}");
}

执行对比:

第5步:深入字段操作——获取和修改私有变量值

这是反射最核心的应用之一。通常情况下,我们无法通过外部直接读写 _test1 等私有字段。但在反射面前,没有不可见的秘密:

  1. 获取: 使用 GetFields() 拿到字段句柄,通过 GetValue(instance) 读取内存中的值。
    RefClass rc = new RefClass();
    rc.Test3 = 3; // 设置一个公开属性来触发后台逻辑或者确保初始化
    FieldInfo f = t.GetField("_test1", BindingFlags.NonPublic | BindingFlags.Instance);
    object val = f.GetValue(rc);
  2. 修改: 通过 SetValue(instance, value) 强行改变状态。
    f.SetValue(rc, 100);

过程图解:

可以看到即便字段标记为 Private,我们依然拿到了当前的内存赋值:

修改后:

第6步:深入属性操作——绕过 Get/Set 方法调用

属性(Property)底层实际上是成对的 getter 和 setter 方法。操作起来比字段复杂一些:

PropertyInfo[] props = t.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach (var prop in props)
{
    // 获取 Setter 方法
    MethodInfo setMethod = prop.GetSetMethod(true); // true 表示包括非公开的 setter
    if(setMethod != null)
    {
        // 调用 Setter:第一个参数是对象实例,第二个是参数数组(这里设值为 123)
        setMethod.Invoke(rc, new object[] { 123 });
    }
    
    // 获取 Getter 方法验证结果
    MethodInfo getMethod = prop.GetGetMethod(true);
    object result = getMethod.Invoke(rc, null);
    Console.WriteLine($"属性 {prop.Name} 的新值为: {result}");
}

底层细节分析:

我们可以通过反射进一步分析这些生成方法的IL代码特征,例如查看 GetMethodBody().GetILAsByteArray().Length 等元数据信息:

第7步:深入方法操作——动态执行带参方法

如果需要动态调用类中任意标记的方法:

  1. 使用 GetMethods() 加上必要的 BindingFlags 定位目标方法。
  2. 检查参数匹配性(通过 GetParameters())以防止 TargetInvocationException。
  3. 使用 Invoke(instance, parameters) 执行。
MethodInfo m = t.GetMethod("Show3", BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);
// 假设该方法接收 string 参数
string ret = (string)m.Invoke(null, new object[] { "Hello C# Reflection" });

在执行过程中,必须严格保证传入的参数数组顺序和类型与方法签名一致:

以上就是关于C#反射机制的详细实战教程。通过这个机制开发者可以灵活实现插件化框架设计、ORM映射转换等高级功能,更多关于C#高阶技术的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表