Avoid boxing of structs implementing an interface


Structs are often used in situations where performance is critical, and are very powerful, but as the wise men say, with great power comes great responsibility and for value types, the responsibility is on the developer to make sure no boxing will occur.

The CLR states that when a value type is assigned to an interface reference, it will automatically be boxed, and boxing value types is bad.

This is what occurs in the following snippet (and how to solve this performance conundrum)


The problem:
using System;

namespace OneKStrongOxen
{
 internal interface IFoo
 {
  String Name { get; }
 }

 internal struct FooStruct : IFoo
 {
  public string Name
  {
   get { return "Struct"; }
  }
 }

 internal class Tester
 {
  public void Test(IFoo foo)
  {
   Console.Out.WriteLine(foo.Name);
  }

  public void TestGeneric<T>(T foo) where T : IFoo
  {
   Console.Out.WriteLine(foo.Name);
  }
 }


 internal class Asylum
 {
  private static void Main(string[] args)
  {
   var tester = new Tester();
   tester.Test(new FooStruct());
  }
 }
}

Now let's have a look at the Main IL bytecode:

    L_0011: initobj OneKStrongOxen.FooStruct
    L_0017: ldloc.2 
    L_0018: box OneKStrongOxen.FooStruct
    L_001d: callvirt instance void OneKStrongOxen.Tester::Test(class OneKStrongOxen.IFoo)
The instruction at address L_0018 shows that the struct is boxed and then passed to the method.

The workaround:

The workaround is to define a generic method whose template parameter will be constrained to the interface. When iknvoked, this method will use the struct rather than the interface definition for its template parameter, thus bypassing the need for affecting a struct to an interface.
Let's add this method to the Tester class and alter the Main body to actually call it:

internal class Tester
 {
  public void TestGeneric<T>(T foo) where T : IFoo
  {
   Console.Out.WriteLine(foo.Name);
  }
 }

 internal class Asylum
 {
  private static void Main(string[] args)
  {
   var tester = new Tester();
   tester.TestGeneric(new FooStruct());
  }
 }

And the IL now is:
    L_0024: ldloca.s struct2
    L_0026: initobj OneKStrongOxen.FooStruct
    L_002c: ldloc.2 
    L_002d: callvirt instance void OneKStrongOxen.Tester::TestGeneric<valuetype onekstrongoxen.foostruct="">(!!0)

See, there's no more box instruction, but now rather than calling Tester.Test(IFoo) we're calling Tester.Test<FooStruct>(FooStruc), effectively eliminating the need for assigning any value type to an interface.

Note that any assignment to an interface will still yield a boxing operation, even in the generic method, so any codependent methods will also need to be turned into generic method templates with a generic type constraints on your interface.

Conclusion

Thanks for watching another episode of Generics are wonderful.

Happy refactoring!

No comments:

Post a Comment

Please leave your comments in English or French and I will be pleased to answer them if you have any questions.

Spammers will be walked down the plank matey. Arrr!