Written by 12:40 Computer Environment, Languages & Coding

How Generics save from Boxing

At the method input, we often perform a null test. Someone makes the test as a separate method, so that the code looks cleaner, and gets something like this:

public void ThrowIfNull(object obj)
{
   if(obj == null)
   {
      throw new ArgumentNullException();
   }
}

The interesting thing about this test is that I see a frequent use of the object attribute, though you can use generic. Let’s try to replace our method with generic and compare performance.

Before testing, you need to take into account another drawback of the object argument. Value types can never be null (Nullable type is not counted). Calling a method like ThrowIfNull(5) is meaningless, but since the type of the argument is object, the compiler will call the method. As for me, this reduces the quality of the code, which in some situations is much more important than performance. In order to get rid of this behavior and to improve the signature of the method, the generic method will have to be divided into two, with constraints. The trouble is that you can not specify a Nullable constraint, however, you can specify the nullable argument, with the struct constraint.

Let’s start testing the performance and use the BenchmarkDotNet library. Attach attributes, run, and look at the results.

public class ObjectArgVsGenericArg
{
   public string str = "some string";
   public Nullable<int> num = 5;

   [MethodImpl(MethodImplOptions.NoInlining)]
   public void ThrowIfNullGenericArg<T>(T arg)
      where T : class
   {
      if (arg == null)
      {
         throw new ArgumentNullException();
      }
   }

   [MethodImpl(MethodImplOptions.NoInlining)]
   public void ThrowIfNullGenericArg<T>(Nullable<T> arg)
      where T : struct
   {
      if (arg == null)
      {
         throw new ArgumentNullException();
      }
   }

   [MethodImpl(MethodImplOptions.NoInlining)]
   public void ThrowIfNullObjectArg(object arg)
   {
      if (arg == null)
      {
         throw new ArgumentNullException();
      }
   }

   [Benchmark]
   public void CallMethodWithObjArgString()
   {
      ThrowIfNullObjectArg(str);
   }

   [Benchmark]
   public void CallMethodWithObjArgNullableInt()
   {
      ThrowIfNullObjectArg(num);
   }

   [Benchmark]
   public void CallMethodWithGenericArgString()
   {
      ThrowIfNullGenericArg(str);
   }

   [Benchmark]
   public void CallMethodWithGenericArgNullableInt()
   {
      ThrowIfNullGenericArg(num);
   }
}

class Program
{
   static void Main(string[] args)
   {
      var summary = BenchmarkRunner.Run<ObjectArgVsGenericArg>();
   }
}
Method Mean Error StdDev
CallMethodWithObjArgString 1.784 ns 0.0166 ns 0.0138 ns
CallMethodWithObjArgNullableInt 124.335 ns 0.2480 ns 0.2320 ns
CallMethodWithGenericArgString 1.746 ns 0.0290 ns 0.0271 ns
CallMethodWithGenericArgNullableInt 2.158 ns 0.0089 ns 0.0083 ns

Our generic showed 100 times faster performance on the nullable type! All this is because of the boxing. When we call CallMethodWithObjArgNullableInt, our nullable-int is being “boxed” and placed in the heap. Boxing is a very expensive operation, that’s why the performance of the method is down. So, we can avoid boxing using generic.

Thus, the generic argument is better than the object because it:

  1. Saves from boxing
  2. Improves the method signature by using constraints
Tags: Last modified: September 23, 2021
Close