The unbox.any
unboxes and loads the value type in the stack (so it copies it):
From MSDN:
The resulting object reference or value type is pushed onto the stack.
When applied to the boxed form of a value type, the unbox.any instruction extracts the value contained within obj (of type O), and is therefore equivalent to unbox followed by ldobj.
What you are thinking is the unbox
instruction:
The unbox instruction converts the object reference (type O), the boxed representation of a value type, to a value type pointer (a managed pointer, type &), its unboxed form. The supplied value type (valType) is a metadata token indicating the type of value type contained within the boxed object.
Unlike Box, which is required to make a copy of a value type for use in the object, unbox is not required to copy the value type from the object. Typically it simply computes the address of the value type that is already present inside of the boxed object.
I don't even know how you can force the compiler to use the unbox
instruction (reading from here it isn't used in C#, or at least it wasn't used in the compiler in 2010... I've done some tests, mixing ref
, boxing and unboxing and I wasn't able to force the compiler to use it)
Mmmh... By taking a look at the ILSpy (they are experts in decompiling C# code), it seems that unbox
is used only in the "private" implementation of certain switch
(the switch
statement is compiled in different ways depending on the number and on the types of conditions it has). The only reference about unbox
is in a method called MatchLegacySwitchOnStringWithHashtable
... I will say the name is quite clear. Another reference is in the Unsafe.il file... the file is "linked" to the Unsafe
class of .NET. See the proposal here about the Unsafe.Unbox<T>
method. The method was accepted and is part of .NET now. The corresponding C# code doesn't compile:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T Unbox<T>(object box) where T : struct
{
return ref (T)box;
}
In truth, by taking a look at the .NET code it is probably implemented as an intrinsic.
Cancel everything.. Charlieface has found how to force the use of unbox
:
public struct MyStruct
{
public int A;
public int Test()
{
object st2 = new MyStruct();
int a = ((MyStruct)st2).A;
return a;
}
}
The method Test()
is compiled to:
// Methods
.method public hidebysig
instance int32 Test () cil managed
{
// Method begins at RVA 0x2050
// Code size 25 (0x19)
.maxstack 1
.locals init (
[0] valuetype MyStruct
)
IL_0000: ldloca.s 0
IL_0002: initobj MyStruct
IL_0008: ldloc.0
IL_0009: box MyStruct
IL_000e: unbox MyStruct
IL_0013: ldfld int32 MyStruct::A
IL_0018: ret
} // end of method MyStruct::Test
From some tests I'll say that the intelligent opcode in the family is ldfld
: it can work both with value types (Test1
), references to value types (Test2
) and directly unboxed value types (Test3
).
public struct MyStruct
{
public int A;
public int Test1(MyStruct st)
{
int a = st.A;
return a;
}
public int Test2(ref MyStruct st)
{
int a = st.A;
return a;
}
public int Test3(MyStruct st)
{
object st2 = st;
int a = ((MyStruct)st2).A;
return a;
}
}
is compiled to
.method public hidebysig
instance int32 Test1 (
valuetype MyStruct st
) cil managed
{
// Method begins at RVA 0x2050
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.1
IL_0001: ldfld int32 MyStruct::A
IL_0006: ret
} // end of method MyStruct::Test1
.method public hidebysig
instance int32 Test2 (
valuetype MyStruct& st
) cil managed
{
// Method begins at RVA 0x2050
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.1
IL_0001: ldfld int32 MyStruct::A
IL_0006: ret
} // end of method MyStruct::Test2
.method public hidebysig
instance int32 Test3 (
valuetype MyStruct st
) cil managed
{
// Method begins at RVA 0x2058
// Code size 17 (0x11)
.maxstack 8
IL_0000: ldarg.1
IL_0001: box MyStruct
IL_0006: unbox MyStruct
IL_000b: ldfld int32 MyStruct::A
IL_0010: ret
} // end of method MyStruct::Test3
The ldfld
opcode is always the same, and it is working with two (three) different types: int32
and reference to int32
(and reference to boxed int32
).