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