90

I'm trying to collect all of the situations in which boxing occurs in C#:

  • Converting value type to System.Object type:

    struct S { }
    object box = new S();
    
  • Converting value type to System.ValueType type:

    struct S { }
    System.ValueType box = new S();
    
  • Converting value of enumeration type to System.Enum type:

    enum E { A }
    System.Enum box = E.A;
    
  • Converting value type into interface reference:

    interface I { }
    struct S : I { }
    I box = new S();
    
  • Using value types in C# string concatenation:

    char c = F();
    string s1 = "char value will box" + c;
    

    note: constants of char type are concatenated at compile time

    note: since version 6.0 C# compiler optimizes concatenation involving bool, char, IntPtr, UIntPtr types

  • Creating delegate from value type instance method:

    struct S { public void M() {} }
    Action box = new S().M;
    
  • Calling non-overridden virtual methods on value types:

    enum E { A }
    E.A.GetHashCode();
    
  • Using C# 7.0 constant patterns under is expression:

    int x = …;
    if (x is 42) { … } // boxes both 'x' and '42'!
    
  • Boxing in C# tuple types conversions:

    (int, byte) _tuple;
    
    public (object, object) M() {
      return _tuple; // 2x boxing
    }
    
  • Optional parameters of object type with value type default values:

    void M([Optional, DefaultParameterValue(42)] object o);
    M(); // boxing at call-site
    
  • Checking value of unconstrained generic type for null:

    bool M<T>(T t) => t != null;
    string M<T>(T t) => t?.ToString(); // ?. checks for null
    M(42);
    

    note: this may be optimized by JIT in some .NET runtimes

  • Type testing value of unconstrained or struct generic type with is/as operators:

    bool M<T>(T t) => t is int;
    int? M<T>(T t) => t as int?;
    IEquatable<T> M<T>(T t) => t as IEquatable<T>;
    M(42);
    

    note: this may be optimized by JIT in some .NET runtimes

Are there any more situations of boxing, maybe hidden, that you know of?

controlflow
  • 6,667
  • 1
  • 31
  • 55
  • 2
    I dealt with this some time ago, and found this quite interesting: [Detecting (un)boxing using FxCop](http://msdn.microsoft.com/en-us/magazine/cc163930.aspx) – George Duckett Nov 03 '11 at 13:24
  • Very nice conversions ways you have shown. Infact i did not knew the 2nd one or perhaps never tried it :) Thanks – Zenwalker Nov 03 '11 at 13:49
  • 12
    it should be community wiki question – Sly Nov 03 '11 at 14:22
  • 2
    What about nullable types? `private int? nullableInteger` – allansson Nov 03 '11 at 14:58
  • 1
    @allansson, nullable types are just kind of value types – controlflow Nov 03 '11 at 15:13
  • Might want to add a framework version, as ArrayLists were the biggest culprits of boxing in 1.1 – Chris S Nov 03 '11 at 17:52
  • I can't believe that 5th (`char.ToString`) and 7th (`enum.GetHashCode`) cause boxing! Great tip, and worst gotcha if it's true.. – nawfal Apr 16 '13 at 09:29
  • possible duplicate of [Boxing and unboxing: when does it come up?](http://stackoverflow.com/questions/1949122/boxing-and-unboxing-when-does-it-come-up) – nawfal Jun 10 '13 at 09:34
  • See this article for getting FxCop to detect (un)boxing. http://msdn.microsoft.com/en-us/magazine/cc163930.aspx#S2 – George Duckett Jun 10 '13 at 09:45
  • 1
    Note that as of .NET Core 2.1, `Enum.HasFlag` doesn't box I believe: https://blogs.msdn.microsoft.com/dotnet/2018/04/18/performance-improvements-in-net-core-2-1. While I could see `box` instruction in IL still in 2.1 app, it doesn't allocate, hence I dont see the perf penalty. – nawfal Oct 24 '18 at 06:45

5 Answers5

43

That’s a great question!

Boxing occurs for exactly one reason: when we need a reference to a value type. Everything you listed falls into this rule.

For example since object is a reference type, casting a value type to object requires a reference to a value type, which causes boxing.

If you wish to list every possible scenario, you should also include derivatives, such as returning a value type from a method that returns object or an interface type, because this automatically casts the value type to the object / interface.

By the way, the string concatenation case you astutely identified also derives from casting to object. The + operator is translated by the compiler to a call to the Concat method of string, which accepts an object for the value type you pass, so casting to object and hence boxing occurs.

Over the years I’ve always advised developers to remember the single reason for boxing (I specified above) instead of memorize every single case, because the list is long and hard to remember. This also promotes understanding of what IL code the compiler generates for our C# code (for example + on string yields a call to String.Concat). When your’e in doubt what the compiler generates and if boxing occurs, you can use IL Disassembler (ILDASM.exe). Typically you should look for the box opcode (there is just one case when boxing might occur even though the IL doesn't include the box opcode, more detail below).

But I do agree that some boxing occurrences are less obvious. You listed one of them: calling a non-overridden method of a value type. In fact, this is less obvious for another reason: when you check the IL code you don’t see the box opcode, but the constraint opcode, so even in the IL it’s not obvious that boxing happens! I won't get into the exact detail why to prevent this answer from becoming even longer...

Another case for less obvious boxing is when calling a base class method from a struct. Example:

struct MyValType
{
    public override string ToString()
    {
        return base.ToString();
    }
}

Here ToString is overridden, so calling ToString on MyValType won’t generate boxing. However, the implementation calls the base ToString and that causes boxing (check the IL!).

By the way, these two non-obvious boxing scenarios also derive from the single rule above. When a method is invoked on the base class of a value type, there must be something for the this keyword to refer to. Since the base class of a value type is (always) a reference type, the this keyword must refer to a reference type, and so we need a reference to a value type and so boxing occurs due to the single rule.

Here is a direct link to the section of my online .NET course that discusses boxing in detail: http://motti.me/mq

If you are only interested in more advanced boxing scenarios here is a direct link there (though the link above will take you there as well once it discusses the more basic stuff): http://motti.me/mu

I hope this helps!

Motti

Motti Shaked
  • 1,854
  • 15
  • 8
  • 1
    If a `ToString()` is called on a particular value type which doesn't override it, will the value type be boxed at the call site, or will the method be dispatched (without boxing) to an auto-generated override which does nothing but chain (with boxing) to the base method? – supercat Feb 07 '14 at 20:46
  • @supercat Calling any method that calls `base` in a value type will cause boxing. This includes virtual methods that are not overridden by the struct and the `Object` methods that are not virtual at all (like `GetType()`). See [this question](http://stackoverflow.com/q/1249086). – Şafak Gür Feb 14 '14 at 12:11
  • @ŞafakGür: I know the end result will be boxing. I was wondering about the exact mechanism via which it happens. Since the compiler generating IL may not know whether the type is a value type or reference (it could be generic) it's going to generate a callvirt regardless. The JITter would know whether the type is a value type, and whether it overrides ToString, so it could generate call-site code to do the boxing; alternatively, it could auto-generate for every struct that doesn't override `ToString` a mehtod `public override void ToString() { return base.ToString(); }` and... – supercat Feb 14 '14 at 16:00
  • ...have the boxing occur within that method. Since the method would be very short, it could then be in-lined. Doing things with the latter approach would allow a struct's `ToString()` method to be accessed via Reflection just like any other and used to create a static delegate which takes the struct type as a `ref` parameter [such a thing works with non-inherited struct methods], but I just tried creating a such delegate and it didn't work. Is it possible to create a static delegate for a struct's `ToString()` method, and if so what should the parameter type be? – supercat Feb 14 '14 at 16:03
  • 1
    Links are broken. – OfirD Apr 12 '17 at 18:57
6

Calling non-virtual GetType() method on value type:

struct S { };
S s = new S();
s.GetType();
Viacheslav Ivanov
  • 1,535
  • 13
  • 22
  • 3
    `GetType` requires boxing not just because it's non-virtual, but because value-type storage locations, unlike heap objects, don't have a "hidden" field which `GetType()` can use to identify their type. – supercat Feb 07 '14 at 20:44
  • @supercat Hmmm. 1. Boxing added by compiler and hidden field used by runtime. May be compiler add boxing because it know about runtime… 2. When we call non-virtual ToString(string) on enum value it also requires boxing and I not believe that compiler add this because it knows about Enum.ToString(string) implementation details. Thus is, I think, I can say that boxing always occurred when non-virtual method on "base value type" called. – Viacheslav Ivanov Feb 16 '14 at 12:56
  • I hadn't considered `Enum` having any non-virtual methods of its own, though a `ToString()` method for an `Enum` would need to have access to type information. I wonder if `Object`, `ValueType`, or `Enum` has any non-virtual methods which could perform their jobs without type information. – supercat Feb 16 '14 at 18:08
3

Mentioned in Motti's answer, just illustrating with code samples:

Parameters involved

public void Bla(object obj)
{

}

Bla(valueType)

public void Bla(IBla i) //where IBla is interface
{

}

Bla(valueType)

But this is safe:

public void Bla<T>(T obj) where T : IBla
{

}

Bla(valueType)

Return type

public object Bla()
{
    return 1;
}

public IBla Bla() //IBla is an interface that 1 inherits
{
    return 1;
}

Checking unconstrained T against null

public void Bla<T>(T obj)
{
    if (obj == null) //boxes.
}

Use of dynamic

dynamic x = 42; (boxes)

Another one

enumValue.HasFlag

Community
  • 1
  • 1
nawfal
  • 70,104
  • 56
  • 326
  • 368
0
  • Using the non-generic collections in System.Collections such as ArrayList or HashTable.

Granted these are specific instances of your first case, but they can be hidden gotchas. It's amazing the amount of code I still come across today that use these instead of List<T> and Dictionary<TKey,TValue>.

Jesse C. Slicer
  • 19,901
  • 3
  • 68
  • 87
0

Adding any value type value into the ArrayList causes boxing:

ArrayList items = ...
numbers.Add(1); // boxing to object
sll
  • 61,540
  • 22
  • 104
  • 156