I have a situation where I have a simple, immutable value type:
public struct ImmutableStruct
{
private readonly string _name;
public ImmutableStruct( string name )
{
_name = name;
}
public string Name
{
get { return _name; }
}
}
When I box an instance of this value type, I would normally expect that whatever it is that I boxed would come out the same when I do an unbox. To my big suprise this is not the case. Using Reflection someone may easily modify my box’s memory by reinitializing the data contained therein:
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() } );
}
}
Sample output:
Whats in the box: 013b50a4-451e-4ae8-b0ba-73bdcb0dd612 ::
ConsoleApplication1.ImmutableStruct Whats in the box:
176380e4-d8d8-4b8e-a85e-c29d7f09acd0 ::
ConsoleApplication1.ImmutableStruct
(There’s actually a small hint in the MSDN that indicates this is the intended behavior)
Why does the CLR allow mutating boxed (immutable) value types in this subtle way? I know that readonly is no guarantee, and I know that using “traditional” reflection a value instance can be easily mutated. This behavior becomes an issue, when the reference to the box is copied around and mutations show up in unexpected places.
One thing I have though about is that this enables using Reflection on value types at all – since the System.Reflection API works with object only. But Reflection breaks apart when using Nullable<> value types (they get boxed to null if they do not have a Value). Whats the story here?
Boxes aren’t immutable as far as the CLR is concerned. Indeed, in C++/CLI I believe there’s a way of mutating them directly.
However, in C# the unboxing operation always takes a copy – it’s the C# language which prevents you from mutating the box, not the CLR. The IL unbox instruction merely provides a typed pointer into the box. From section 4.32 of partition III of ECMA-335 (the
unboxinstruction):The C# compiler always generates IL which results in
unboxbeing followed by a copying operation, orunbox.anywhich is equivalent tounboxfollowed byldobj. The generated IL isn’t part of the C# spec of course, but this is (section 4.3 of the C# 4 spec):In this case, you’re using reflection and therefore bypassing the protection offered by C#. (It’s a particularly odd use of reflection too, I must say… calling a constructor "on" a target instance is very strange – I don’t think I’ve ever seen that before.)