C# boxing with string concatenation and interpolation

By Fons Sonnemans, posted on
15814 Views 5 Comments

C# boxing with String concatenation and interpolation

Boxing is evil and you should always try to avoid it. That is what I tell my students in my C# training. But what is boxing and how do you avoid it. Boxing is wrapping a value type (struct) inside a reference type (object or interface) variable.

using System;

namespace BoxingDemo {
    class Program {
        static void Main(string[] args) {
            int a = 5;
            object b = a;      // boxing
            IComparable c = a; // boxing
            int d = (int)b;    // unboxing, also evil (slow)
        }
    }
}

Boxing is evil because it is “slow”. It creates a reference type (the boxed value) which the garbage collector has to clean up (which is also “slow”). I will first explain how you can detect boxing and then how to avoid it when using string concatenation and interpolation.

Detect boxing and unboxing in IL

You can detect boxing and unboxing by examining the Intermediate Language (IL) which is generated by the compiler. I use the free ILSpy extension for this but there are alternatives like Reflector (paid) and dotPeek (free).

ILSpy shows you the following IL for the program I wrote above. For this you have to select the project in the Solution Explorer and then select the ‘Open output in ILSpy’ from the context menu. In ILSpy you select the Main method and from the dropdown in the toolbar you select ‘IL with C#’.

IL Spy

In ILSpy you see that there is boxing (box instruction) on line 24 and 28 and unboxing (unbox instruction) on line 32.

If you don’t like to install an extra extension you might want to try the website SharpLab.io. SharpLab is a .NET code playground that shows intermediate steps and results of code compilation. It is also very nice.

Detect boxing using the CLR Heap Allocation Analyzer

There is also an easier way to detect boxing. The CLR Heap Allocation Analyzer is an open source Roslyn based C# heap allocation diagnostic analyzer that can detect explicit and many implicit allocations like boxing, display classes, implicit delegate creations, etc. You can install it as a NuGet package or Visual Studio extension.

In the following screenshot you get two compiler warnings due to boxing on line 7 and 8. This is much easier than using ILSpy.

Visual Studio

Boxing with string concatenation (+ operator)

You get also boxing if you concatenate a value type to a string. This is caused by the ‘right’ parameter of the + operator method, it is of the type object. The salary gets boxed into an object.

Visual Studio

The CLR Heap Allocation Analyzer also shows me a warning (line 8). And in ILSpy you can see on line 23 you have boxing.

IL Spy

If I write the same code in Visual Studio 2019 and examine the IL you get a different result. The 2019 Roslyn compiler has added the ToString() call on line 23 which avoids the boxing. This is way better and faster.

ILSpy

If you are still using Visual Studio 2017 you should add the ToString() call manually to avoid the boxing. As you can see the compiler warning is gone.

Visual Studio

Boxing with string interpolation

String interpolation was added to C# 6.0 in 2015. It makes calling String.Format() much easier. Unfortunately, it can cause boxing. Even in Visual Studio 2019. See the code below in which I rewrote the previous code using string interpolation.

Visual Studio

On line 23 you get boxing in the call of String.Format() the second parameter is of the type object.

IL Spy

The solution is easy, just use salary.ToString("C2") to convert the value type to a string first (line 8).

Visual Studio

In the IL you can see that there is no boxing any more. String.Format() is even not called it uses the faster String.Concat(). This is perfect!

IL Spy

Closure

I hope you like this blog post. C# is a great and fast language but you have to know the quirks. Use analyzers to help you improve your coding skills.

Fons

All postings/content on this blog are provided "AS IS" with no warranties, and confer no rights. All entries in this blog are my opinion and don't necessarily reflect the opinion of my employer or sponsors. The content on this site is licensed under a Creative Commons Attribution By license.

Leave a comment

Blog comments

Yuriy

10-Jan-2020 2:39
the following expression: $"Salary {salary.ToString("C2")}" could be written more concisely as: $"Salary {salary:C2}"

Kris

11-Jan-2020 4:57
Yuriy - the original code was... $"Salary {salary:C2}" ...and was changed to... $"Salary {salary.ToString("C2")}" ...in order to avoid boxing and optimize the code for speed. Read the article again.

Craig

13-Jan-2020 4:25
While you are absolutely, 100%, technically correct in everything you say, this type of article is dangerous because it leads developers to spend waaaay to much time on micro-optimizations. They'll dutifully make sure there is no boxing/unboxing in their code, meanwhile they'll iterate over a collection and make a database query on each iteration, which is a much bigger performance hit than the odd boxing of a string during interpolation.

Nicholas Petersen

14-Jan-2020 12:01
"this type of article is dangerous because it leads developers to spend waaaay to much time on micro-optimizations." Don't worry. Be happy. The sky is not falling. One can have a care about not being wasteful, and chew gum at the same time. The crowd you represent say, it is so evil to care about how things actually work under the hood, and about not being wasteful, that even boxing and unboxing (!) shouldn't be understood by developers? I say that attitude is dangerous, if anything is. Cheers.

Morten

23-Jan-2020 9:56
Hi Fons. Thanks for your interesting article. Boxing/unboxing is new to me. Anyway, I have code with string interpolations like this: ... var id = 1; var message = $"the id was {id}"; ... From your article I gather that, in order to avoid boxing/un-boxing, such code should be re-written to: ... var id = 1; var message = $"the id was {id.ToString()}"; ... And I wonder, how much I would gain to re-write such code. Do you have any idea? Thanks in advance. Best regards Morten