C# value type boxing by interfaces - Enter generics

This blog post continues on a previously written blog post, which you can find here. In that blog post, I illustrated that interfaces are reference types, and if you use interfaces together with structs, you have to be aware that boxing and unboxing might take place.

In this article it is time to combine this with generics. The nice thing about generics is that the compiler will actually generate the corresponding code behind the scenes in order to be able to handle the templated type, a.k.a. T.

For the sake of reference, here are the interface and struct used in the previous post.

interface IBankAccount
{
    void Add(int amount);
}

struct BankAccount : IBankAccount
{
    int value;

    public BankAccount(int value) => this.value = value;
    void IBankAccount.Add(int amount) => value += amount;
    public void Add(int amount) => value += amount;
    public void Print() => Console.WriteLine($"Value: {value}");
}

Inside my console application, I defined the following generic method:

static void AddGeneric<T>(ref T ba, int amount) => ((IBankAccount)ba).Add(amount);

This method looks straightforward: it casts, whatever ba might be into the interface IBankAccount in order to invoke the Add method on it. I even pass the argument by reference, since we wouldn't want the Add to happen on a copy of the BankAccount, now would we? When we execute the following code, the result is just like before:

BankAccount ba = new BankAccount(100);
AddGeneric(ref ba, 50);
ba.Print(); // Prints 100 and not 150

The problem is still not solved, because the Add still takes place on a boxed version of the BankAccount. The compiler has to few information to deduct that you want to execute the Add method on the value type. So let's rewrite this method with constraints:

static void AddGenericContrained<T>(ref T ba, int amount) where T : IBankAccount => ba.Add(amount);

And when we execute:

BankAccount ba = new BankAccount(
AddGenericContrained(ref ba, 50);
ba.Print(); // Prints 150!!!

This seems to work, all thanks to the compiler (and the code of course). In this case, the compiler can clearly deduct that you want to execute the implementation of IBankAccount.Add on the struct (value type) BankAccount and therefore generates the appropriate code that skips the formality of boxing, and immediately applies the function.

Conclusion

If, and only if, you work with interfaces on value types, be aware of the consequences related to boxing. Remember that using generic constraint to constrain the interface method call will assist the compiler in generating the right code that directs the method call directly to the right function.