C# Generic Type is boxed?
I executed the following code:
using System;
using System.Collections.Generic;
namespace TestReleaseAndDebug
{
public class GClass<T1, T2>
{
public T1 Name { get; set; }
public T2 Age { get; set; }
public void Display()
{
Console.WriteLine("Name: " + Name);
Console.WriteLine("Age: " + Age);
}
}
class Program
{
static void Main(string[] args)
{
GClass<string, int> person = new GClass<string, int>();
person.Name = "RAM";
person.Age = 34;
string name = "RAM";
int age = 34;
Console.WriteLine("Name: " + name);
Console.WriteLine("Age: " + age);
person.Display();
Console.Read();
}
}
}
I am having two local variables in the Main function they are name and age. I am printing them using console.writeline method. It prints without any problem. The IL of the main method is as shown below:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 90 (0x5a)
.maxstack 2
.locals init ([0] class TestReleaseAndDebug.GClass`2<string,int32> person,
[1] string name,
[2] int32 age)
IL_0000: nop
IL_0001: newobj instance void class TestReleaseAndDebug.GClass`2<string,int32>::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldstr "RAM"
IL_000d: callvirt instance void class TestReleaseAndDebug.GClass`2<string,int32>::set_Name(!0)
IL_0012: nop
IL_0013: ldloc.0
IL_0014: ldc.i4.s 34
IL_0016: callvirt instance void class TestReleaseAndDebug.GClass`2<string,int32>::set_Age(!1)
IL_001b: nop
IL_001c: ldstr "RAM"
IL_0021: stloc.1
IL_0022: ldc.i4.s 34
IL_0024: stloc.2
IL_0025: ldstr "Name: "
IL_002a: ldloc.1
IL_002b: call string [mscorlib]System.String::Concat(string,
string)
IL_0030: call void [mscorlib]System.Console::WriteLine(string)
IL_0035: nop
IL_0036: ldstr "Age: "
IL_003b: ldloc.2
IL_003c: box [mscorlib]System.Int32
IL_0041: call string [mscorlib]System.String::Concat(object,
object)
IL_0046: call void [mscorlib]System.Console::WriteLine(string)
IL_004b: nop
IL_004c: ldloc.0
IL_004d: callvirt instance void class TestReleaseAndDebug.GClass`2<string,int32>::Display()
IL_0052: nop
IL_0053: call int32 [mscorlib]System.Console::Read()
IL_0058: pop
IL_0059: ret
} // end of method Program::Main
I have another Generic class 'GClass'. In the generic class I have two properties and one method (Display). In the Display method I display the two properties the same way I displayed the local variables in the Main method. The IL of the Generic Class Display method is given below:
.method public hidebysig instance void Display() cil manage开发者_JAVA技巧d
{
// Code size 56 (0x38)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Name: "
IL_0006: ldarg.0
IL_0007: call instance !0 class TestReleaseAndDebug.GClass`2<!T1,!T2>::get_Name()
IL_000c: box !T1
IL_0011: call string [mscorlib]System.String::Concat(object,
object)
IL_0016: call void [mscorlib]System.Console::WriteLine(string)
IL_001b: nop
IL_001c: ldstr "Age: "
IL_0021: ldarg.0
IL_0022: call instance !1 class TestReleaseAndDebug.GClass`2<!T1,!T2>::get_Age()
IL_0027: box !T2
IL_002c: call string [mscorlib]System.String::Concat(object,
object)
IL_0031: call void [mscorlib]System.Console::WriteLine(string)
IL_0036: nop
IL_0037: ret
} // end of method GClass`2::Display
I am passing 'string' as the type parameter to T1 and using this type to declare Name property. When the Name Property is displayed using Console.Writeline, it is boxing the Name ( IL_000c: box !T1). You can find this in IL.
Why boxing happens eventhough it is a string type?
This is so because compiler is not sure that both T1
and T2
would always be a reference type or value-type. so it puts them into Object by default for both cases when T1 or T2, either of them is a value-type or reference-type.
The type Object
can act in duality. It can box-unbox for value-types and hold references to the instances of any sub-class types when it is a reference-type.
So in case when T1 is string, it is actually not boxing, it's holding the reference of the string instance because Object is the base class of the string type, in fact any .Net type.
and in case when T2 is int, it is simple boxing-unboxing.
The compiler must generate IL that can work across all generic types. The compiler can't know that you always instatiate GCClass
with <string, int>
. It has to cope with the eventuality that T1
is a value type.
However, I'd expect box
on a reference type to be a no-op. The JIT generates different machine code from the Display
method's IL for reference and value types. For reference types, I'd expect the box
instruction to be eliminated.
If you're sure that T1
will never be a value type you can add a : class
constraint to it, which will remove that box
instruction.
Check out the CLI specification
In Partition III, section 4.1, about the box
instruction :
If typeTok is a value type, the box instruction converts val to its boxed form. When typeTok is a non-nullable type (§1.8.2.4), this is done by creating a new object and copying the data from val into the newly allocated object. If it is a nullable type, this is done by inspecting val’s HasValue property; if it is false, a null reference is pushed onto the stack; otherwise, the result of boxing val’s Value property is pushed onto the stack. If typeTok is a reference type, the box instruction does nothing
So the boxing occurs only if the generic type parameter is actually a value type. If it's a reference type, this instruction has no effect.
精彩评论