How to avoid boxing structs that implement interfaces in C#

Posted by : on

Category : C#

The problem of boxing value types

Structs in C# are value types, but sometimes when they implement an interface and we pass them as arguments to methods that require a parameter that implements that interface, they will get boxed. I say sometimes because that depends on how we have defined that method. Let’s see a common example.

Lets suppose that we have the following interface:

public interface IFoo
{
    // some methods here
}

and the following struct that implements it:

struct Foo : IFoo
{
    // our struct that implements IFoo
}

When we have a method that has an IFoo parameter we usually write something like this:

public void Bar(IFoo barParameter)
{
    // Do something with the barParameter
}

But this has a problem. When barParameter is a class, which is a reference type, everything works as expected, but if barParameter is a struct that implements IFoo, the compiler expects an IFoo object. That means that because the struct is a value type it will get boxed. Boxing the struct costs performance as an operation, but even worse we have memory allocation on the heap, which means that eventually the garbage collector will have to collect it.

A better way to avoid boxing, is by using generics and also taking care that our struct overrides the methods of the object class.

Let’s start with the first:

Changing the definition of our method to use generics

A better way to write the above method, is this:

public void Bar<T>(T barParameter) where T : IFoo
{
    // Do something with the barParameter
}

this keeps our barParameter as its original type. The method works the same with classes that implement IFoo as before, but our structs will avoid boxing. The reason is that now, we have IFoo as a constraint and constraints use OpCodes.Constrained Field.

By looking the above documentation, we see that if our parameter is a reference type the callvirt instruction is used, but if it is a value type then the call instruction is used. This solves our first problem that can cause boxing, but we are not done yet.

Overriding object’s class methods

Even if the barParameter stays a value type when it is a struct, if inside our method we use methods of the object class, like ToString or Equals the barParameter will have to get boxed, because it inherits those methods from the object class. We have to make sure that our struct (in our case the Foo struct) overrides those methods. That way, boxing won’t occur. In case that we use the Equals method for comparison and our Bar method is going to be used by others, a good practice is to require that the T type also implements the IEquatable<T> interface. So an even better way to define our method would be:

public void Bar<T>(T barParameter) where T : IFoo, IEquatable<T>
{
    // Do something with the barParameter
    // Do something with the barParameter that uses the Equals method
}

Adding the ref keyword

By using the above, we avoid the boxing problem, but now a have created a new one, but this has an easy solution. Because now the barParameter is a value type when it is a struct, if our struct is big (above 16 bytes) we will have performance problems. We tried to gain performance by avoiding boxing, but we will loose in performance because of the copying of the value types. Thankfully the solution now, is simple. We just have to pass our value type by reference by changing how we define our method, one more time:

public void Bar<T>(ref T barParameter) where T : IFoo, IEquatable<T>
{
    // Do something with the barParameter
    // Do something with the barParameter that uses the Equals method
}

Be careful with the above. The Bar method’s behavior now, may be different from before. The reason is that if we make changes to the properties of the barParameter in the first case, those changes won’t “stick” to our original struct, because the barParameter is just a copy. Now barParameter is a reference to our struct and every change will be reflected to the original struct. Obviously a better practice is our structs to always be immutable, that solves nasty surprises and there are many arguments online for the benefit of that, but this is a different conversation.

Conclusion

The change from

public void Bar(IFoo barParameter)

to

public void Bar<T>(ref T barParameter) where T : IFoo

or even

public void Bar<T>(ref T barParameter) where T : IFoo, IEquatable<T>

may seem an unnecessary complication, but is a good performance improvement when we have structs and actually is a matter of habit, that eventually doesn’t take much more time to write. Avoiding the garbage collector when we can, is always a good thing.

Thank you for reading, I hope you found this info useful and as always for questions and comments, use the comments section, or contact me directly via the contact form or email, also if you don’t want to miss any of the new articles, you can always subscribe to my newsletter or the RSS feed.


Follow me: