我有一种情况,我有一个简单的、不可变的值类型:
public struct ImmutableStruct
{
private readonly string _name;
public ImmutableStruct( string name )
{
_name = name;
}
public string Name
{
get { return _name; }
}
}
当我装箱此值类型的实例时,我通常希望我装箱的任何内容在我进行拆箱时都会出现相同的结果。令我大吃一惊的是,事实并非如此。使用反射,有人可以通过重新初始化其中包含的数据轻松修改我的盒子的内存:
class Program
{
static void Main( string[] args )
{
object a = new ImmutableStruct( Guid.NewGuid().ToString() );
PrintBox( a );
MutateTheBox( a );
PrintBox( a );;
}
private static void PrintBox( object a )
{
Console.WriteLine( String.Format( "Whats in the box: {0} :: {1}", ((ImmutableStruct)a).Name, a.GetType() ) );
}
private static void MutateTheBox( object a )
{
var ctor = typeof( ImmutableStruct ).GetConstructors().Single();
ctor.Invoke( a, new object[] { Guid.NewGuid().ToString() } );
}
}
样本输出:
盒子里有什么:013b50a4-451e-4ae8-b0ba-73bdcb0dd612 :: ConsoleApplication1.ImmutableStruct 盒子里有什么:176380e4-d8d8-4b8e-a85e-c29d7f09acd0 :: ConsoleApplication1.ImmutableStruct
(实际上 MSDN 中有一个小提示表明这是预期的行为)
为什么 CLR 允许以这种微妙的方式改变装箱(不可变)值类型?我知道 readonly 并不能保证,而且我知道使用“传统”反射可以轻松地改变值实例。当对盒子的引用被复制并且突变出现在意想不到的地方时,这种行为就会成为一个问题。
我所知道的一件事是,这完全可以在值类型上使用反射 - 因为 System.Reflection APIobject
仅适用于。但是反射在使用值类型时会分开Nullable<>
(如果它们没有值,它们会被装箱为空)。这里有什么故事?