How generic saves us from packaging

When calling a method, we often run a null check. Someone checks out in a separate method, so that the code looks cleaner, and it turns out something like this:


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

And what is interesting with such a check, I see the use of the object attribute on a massive scale, because you can use the generic. Let's try replacing our method with generic and compare performance.


Before testing, there is one more drawback of the object argument. Value types can never be null (Nullable type does not count). A method call, such as ThrowIfNull (5) , is meaningless, however, since the type of the argument is object, the compiler will allow the method to be called. 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 improve the signature of the method, the generic method will have to be divided into two, indicating constraints. The trouble is that you cannot specify a Nullable constraint, however, you can specify a nullable argument, with a struct constraint.


We begin to test 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 num = 5;
        [MethodImpl(MethodImplOptions.NoInlining)]
        public void ThrowIfNullGenericArg(T arg)
            where T : class
        {
            if (arg == null)
            {
                throw new ArgumentNullException();
            }
        }
        [MethodImpl(MethodImplOptions.NoInlining)]
        public void ThrowIfNullGenericArg(Nullable 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();
        }
    }

MethodMeanErrorStddev
CallMethodWithObjArgString1.784 ns0.0166 ns0.0138 ns
CallMethodWithObjArgNullableInt124.335 ns0.2480 ns0.2320 ns
CallMethodWithGenericArgString1.746 ns0.0290 ns0.0271 ns
CallMethodWithGenericArgNullableInt2.158 ns0.0089 ns0.0083 ns

Our generic nullable type worked 2000 times faster! And all because of the notorious packaging (boxing) . When we call CallMethodWithObjArgNullableInt, then our nullable-int is "packed" and placed on the heap. Packaging is a very expensive operation, from that the method also sags in performance. Thus, using generic we can avoid packaging.


So, the generic argument is better than object because:


  1. Saves from packaging
  2. Allows you to improve the method signature when using restrictions

Upd. Thanks to the zelyony habrayuzer for the comment. Methods inline, for more accurate measurements added the attribute MethodImpl (MethodImplOptions.NoInlining) .


Also popular now: