eaglet

本博专注于基于微软技术的搜索相关技术
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

巧用 __makeref 为结构体的私有字段赋值

Posted on 2010-05-26 10:38  eaglet  阅读(2043)  评论(0编辑  收藏  举报

在一些特殊的应用中,我们需要对类或结构体对象实例的私有字段赋值,比如我们需要改变某个只读属性(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

修改成功。