0

In Jon Skeet's book "C# in Depth, Third Edition" I read this

"All of this would be enough to make generics worthwhile, but there are performance improvements, too. First, because the compiler can perform more enforcement, that leaves less to be checked at execution time. Second, the JIT can treat value types in a particularly clever way that manages to eliminate boxing and unboxing in many situations."

Does that mean in some situations generics might cause boxing? If yes, can someone give an example?

Foo
  • 4,206
  • 10
  • 39
  • 54
  • 7
    I think that statement should be read as, "*Many situations that might otherwise require boxing can be avoided by using generics*". – p.s.w.g Mar 14 '14 at 20:23
  • @p.s.w.g Write an answer so it can be upvoted. – Kyle W Mar 14 '14 at 20:25
  • So my code that uses generics is guaranteed to have no run time boxing, unless I write code to box explicitly? – Foo Mar 14 '14 at 20:25
  • I strongly suspect that the statement should be read in context of when it was written... Before generics (.Net 1.0 times) every collection (except `Array`) would force boxing for elements. Now-days it is actually hard to find enough cases of boxing to see "in many situations" to be true. – Alexei Levenkov Mar 14 '14 at 20:39
  • @AlexeiLevenkov Skeet's statement is saying exactly what you said. Foo - you are not guaranteed to have no boxing / unboxing it's just not as common of an issue as it used to be. – Kevin Mar 14 '14 at 21:19

3 Answers3

3

Boxing needs to occur when calling virtual methods, so the following will cause first to be boxed:

public static bool Equals<T>(T first, T other) where T : struct
{
    return first.Equals(other);
}

bool eq = Equals(1, 2);

Changing the constraint on T can prevent boxing:

public static bool Equals<T>(T first, T other) where T : IEquatable<T>
{
   return first.Equals(other);
}
Lee
  • 142,018
  • 20
  • 234
  • 287
  • +1, examined both with ildasm and there is no boxing with the `Iequatable` constraint, while there is with the `struct` constraint. One curiosity, you state _boxing needs to occur when calling virtual methods_, but both emitted callvirt on either `System.Object.Equals()` or `IEquatable``1<!!T>.Equals()`, which doesn't seem to fit. Win7, targeting .NET 4.0 in release mode. – jdphenix Mar 14 '14 at 20:49
  • 1
    @jdphenix - The use of the `constrained` opcode allows the JIT to avoid boxing when calling the method in this case - see [msdn](http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.constrained.aspx). – Lee Mar 14 '14 at 20:59
1

Some methods can operate on either boxed or unboxed structures, while others can operate on structures only if they are boxed. If a method holds a boxed structure, it can pass that to methods which require boxed structures without having to box it. By contrast, if a method holds an unboxed structure, each call to a method which needs a boxed structure will require a separate boxing operation.

Consider: interface IStooge { whatever }; struct Stooge : IStooge { whatever};

void Moe(Stooge x)
{
  Larry(x);
}

void Larry<T>(T x) where T:IStooge
{
  for (int i=0; i<1000000; i++)
    Curly(x);
}

void Curly(IStooge x)
{ whatever; }

Moe has an unboxed Stooge. Larry can work with either boxed or unboxed things that implement IStooge. Curly only accepts boxed implementations of IStooge.

If Moe passes an unboxed Stooge to Larry, then Larry is going to create a new boxed Stooge, copy the data from x to that, pass a reference to the new object to Curly, and then abandon that new object; it will then repeat that process 999,999 more times, starting each time with a new boxed Stooge.

If either Moe had cast x to IStooge before passing it, or if Larry were (like Curly) a non-generic method that only accepted boxed implementations of of IStooge, then x would have been boxed before the call to Larry. On each pass through the loop, Larry would pass Curly a reference to the already-boxed Stooge, rather than having to create a new boxed instance. The net effect would be that the number of boxing operations required would be reduced enormously by either making Larry non-generic or using it in non-generic fashion.

In cases where generics can eliminate boxing (they usually can), they naturally reduce boxing. In cases where boxing is going to end up being necessary, however, it's often better to do it in an outer scope rather than a nested one. Generics often prevent boxing in outer scopes, which is good in the cases where it can be prevented altogether, but not so good if it ends up moving it from someplace where it could be done once to someplace it will have to be done repeatedly.

supercat
  • 77,689
  • 9
  • 166
  • 211
-1

I believe Jon is referring to the fact that a new instance of the Generic type gets compiled for each value-type. If you do List, List, List, there will only be one compiled class for it, because they all inherit from object. If you have List, List, and List, there will be a separate compiled type for each one of those, created by the JITter. This is how it avoids the boxing, because it doesn't put it in the List.

To your second question, I don't know that it's guaranteed to have no runtime boxing, but in the general use of the class, it should be eliminated.

Kyle W
  • 3,702
  • 20
  • 32