zoukankan      html  css  js  c++  java
  • 巧用 __makeref 为结构体的私有字段赋值

    在一些特殊的应用中,我们需要对类或结构体对象实例的私有字段赋值,比如我们需要改变某个只读属性(Readonly Property)的值,本文将重点介绍如何来实现这样的功能。特别是对结构体私有字段赋值,我们需要用到一个未公开关键字 __makeref 才能够实现。

    首先我们来看看类的情况

            class Student
            {
                private string _Name;
     
                public string Name
                {
                    get
                    {
                        return _Name;
                    }
                }
     
                private int _Age;
     
                public int Age
                {
                    get
                    {
                        return _Age;
                    }
                }
     
                public Student()
                {
                    _Name = null;
                    _Age = 0;
                }
     
                public Student(string name, int age)
                {
                    _Name = name;
                    _Age = age;
                }
     
                public override string ToString()
                {
                    return string.Format("Name:{0} Age:{1}", Name, Age);
                }
            }
     

    上面这个Student 类有两个只读属性 Name 和 Age, 这两个只读属性只能在类的构造函数中赋值,如果我们希望在程序中动态改变这两个只读属性,正常的途径无法实现,我们需要通过反射的方法来做。下面看代码:

                //创建一个 student 实例
                Student student = new Student();
     
                //获取 _Name 这个私有字段的信息,注意这里必须要用
                //BindingFlags.NonPublic | BindingFlags.Instance 才可以获取到
                FieldInfo fiName = typeof(Student).GetField("_Name", 
                    BindingFlags.NonPublic | BindingFlags.Instance);
     
                //获取 _Age 这个私有字段的信息,注意这里必须要用
                FieldInfo fiAge = typeof(Student).GetField("_Age",
                    BindingFlags.NonPublic | BindingFlags.Instance);
     
                //为_Name 字段赋值
                fiName.SetValue(student, "ABC");
     
                //为 _Age 字段赋值
                fiAge.SetValue(student, 28);
     
                //打印
                Console.WriteLine(student);

    上面代码通过反射的方式,动态修改了Name 和 Age 这两个只读属性的值

    打印结果是:

    Name:ABC Age:28

    下面再看结构体的情况

            struct Student
            {
                private string _Name;
     
                public string Name
                {
                    get
                    {
                        return _Name;
                    }
                }
     
                private int _Age;
     
                public int Age
                {
                    get
                    {
                        return _Age;
                    }
                }
     
                public Student(string name, int age)
                {
                    _Name = name;
                    _Age = age;
                }
     
                public override string ToString()
                {
                    return string.Format("Name:{0} Age:{1}", Name, Age);
                }
            }
     

    将 class 改成 struct,删除默认构造函数。

    然后我们再执行前面那段反射代码,打印结果是:

    Name: Age:0

    为什么值没有被改变呢?

    在继续下面之前,请先思考1分钟 ……

    struct 和 class 的区别在于 struct 是值类型,而 class 是引用类型,值类型在作为参数传递时将把自身的复制版本传递给函数,而这个复制版本已经不是其本身。

    也就是说调用 fiName.SetValue(student, "ABC"); 这个函数时,CLR 会先将student 复制一份到堆栈中,SetValue函数实际上是操作这个复制版本,或者说是 student 的克隆人的Name属性,而不是 student 本身。克隆人被改变了,但本人没有变。这也是我们无法对结构体对象实例反射赋值的原因。

    到了这里我们似乎已经无路可走,难道我们就无法对值类型对象反射赋值了吗?幸运的是.Net 为我们提供了一个未公开关键字 __makeref ,这个关键字可以将一个值类型转换为一个TypedReference 类型,这个类型描述既包含指向某位置的托管指针,也包含该位置可能存储的类型的运行时表示形式的对象(引自MSDN)。

    FieldInfo 这个类还提供了一个 SetValueDirect 函数,这个函数可以设置给定对象支持的字段值(引自MSDN)。这个函数需要 .net 2.0 sp1 及以上版本支持。

    修改反射代码如下

                //创建一个 student 实例
                Student student = new Student();
     
                //获取 _Name 这个私有字段的信息,注意这里必须要用
                //BindingFlags.NonPublic | BindingFlags.Instance 才可以获取到
                FieldInfo fiName = typeof(Student).GetField("_Name",
                    BindingFlags.NonPublic | BindingFlags.Instance);
     
                //获取 _Age 这个私有字段的信息,注意这里必须要用
                FieldInfo fiAge = typeof(Student).GetField("_Age",
                    BindingFlags.NonPublic | BindingFlags.Instance);
     
                //为_Name 字段赋值
                fiName.SetValueDirect(__makeref(student), "ABC");
     
                //为 _Age 字段赋值
                fiAge.SetValueDirect(__makeref(student), 28);
     
                //打印
                Console.WriteLine(student);

    打印结果:

    Name:ABC Age:28

    修改成功。

  • 相关阅读:
    [转]Modernizr的介绍和使用
    java动态代理使用详解
    ajax上传文件以及使用中常见问题处理
    cmd下查询端口占用以及根据进程id名称结束进程
    水平居中 垂直居中
    inline-block和float
    一起入门前端(三)
    一起入门前端(二)
    一起入门前端(一)
    WPF初学——自定义样式
  • 原文地址:https://www.cnblogs.com/eaglet/p/1744213.html
Copyright © 2011-2022 走看看