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 timenote: since version 6.0 C# compiler optimizes concatenation involving
bool
,char
,IntPtr
,UIntPtr
typesCreating 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 withis
/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?
Mentioned in Motti's answer, just illustrating with code samples:
Parameters involved
But this is safe:
Return type
Checking unconstrained T against null
Use of dynamic
Another one
enumValue.HasFlag
System.Collections
such asArrayList
orHashTable
.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>
andDictionary<TKey,TValue>
.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:
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
Adding any value type value into the ArrayList causes boxing:
Calling non-virtual GetType() method on value type: