技术2022-07-05  141

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

    修改成功。


    最新回复(0)