(As a result of doing the research to answer this question, I (think I have!) determined that the answer is "no." However, I had to look in several different places to figure this out, so I think there is still value to the question. But I won't be devastated if the community votes to close.)
For example:
void f<T>(T val) where T : IComparable
{
val.CompareTo(null);
}
void g()
{
f(4);
}
Is 4
boxed? I know that explicitly casting a value type to an interface that it implements triggers boxing:
((IComparable)4).CompareTo(null); // The Int32 "4" is boxed
What I don't know is whether passing a value type as a generic parameter with an interface constraint is tantamount to performing a cast--the language "where T is an IComparable" sort of suggests casting, but simply turning T
into IComparable
seems like it would defeat the entire purpose of being generic!
To clarify, I would like to be sure neither of these things happens in the code above:
g
calls f(4)
, the 4
is cast to IComparable
since there is an IComparable
constraint on f
's parameter type.f
, val.CompareTo(null)
does not cast val
from Int32
to IComparable
in order to call CompareTo
.But I would like to understand the general case; not just what happens with int
s and IComparable
s.
Now, if I put the below code into LinqPad:
void Main()
{
((IComparable)4).CompareTo(null);
f(4);
}
void f<T>(T val) where T : IComparable
{
val.CompareTo(null);
}
And then examine the generated IL:
IL_0001: ldc.i4.4
IL_0002: box System.Int32
IL_0007: ldnull
IL_0008: callvirt System.IComparable.CompareTo
IL_000D: pop
IL_000E: ldarg.0
IL_000F: ldc.i4.4
IL_0010: call UserQuery.f
f:
IL_0000: nop
IL_0001: ldarga.s 01
IL_0003: ldnull
IL_0004: constrained. 01 00 00 1B
IL_000A: callvirt System.IComparable.CompareTo
IL_000F: pop
IL_0010: ret
It's clear that boxing occurs as expected for the explicit cast, but no boxing is obvious either in f
itself* or at its call site in Main
. This is good news. However, that's also just one example with one type. Is this lack of boxing something that can be assumed for all cases?
*This MSDN article discusses the constrained
prefix and states that using it in conjunction with callvirt
will not trigger boxing for value types as long as the called method is implemented on the type itself (as opposed to a base class). What I'm not sure of is whether the type will always still be a value type when we get here.
As you figured out already, When a struct
is passed to generic method, It will not be boxed.
Runtime creates new method for every "Type Argument". When you call a generic method with a value type, you're actually calling a dedicated method created for respective value type. So there is no need of boxing.
When calling the interface method which is not directly implemented in your struct type, then boxing will happen. Spec calls this out here:
If thisType is a value type and thisType does not implement method then ptr is dereferenced, boxed, and passed as the 'this' pointer to the callvirt method instruction.
This last case can occur only when method was defined on Object, ValueType, or Enum and not overridden by thisType. In this case, the boxing causes a copy of the original object to be made. However, because none of the methods of Object, ValueType, and Enum modify the state of the object, this fact cannot be detected.
So, as long as you explicitly[1] implement interface member in your struct itself, boxing will not occur.
How, when and where are generic methods made concrete?
1.Not to be confused with Explicit interface implementation. It is to say that your interface method should be implemented in struct itself rather than its base type.