简体   繁体   中英

Null checking on reference and value types

(Update - from comments) Question: Is there any advantage of using one extension method over the other?

From a discussion that I am having in my codeproject article on extension methods, I am unsure whether the following is correct or not.

Currently, I have the following extension method:

public static bool In<T>(this T source, params T[] list)
{
    if (null == source) throw new ArgumentNullException("source");
    return list.Contains(source);
}

Which works as expected. It has been suggested in the comments that I change it so that it only checks reference types like so:

public static bool In<T>(this T source, params T[] list)
{
     if (!typeof(T).IsValueType)
     {
         if (Equals(source, default(T))) throw new ArgumentNullException("source");
     }
     return list.Contains(source);
}

Again that works as expected. Is there any advantage of the second method over the first given that running a quick benchmark, we are talking about 0.001 of a second difference for 10000 runs.

Output of benchmark (Core i3 @ 4ghz, RAID 0 ssd's):

Testing performance...

Value type, original: 00:00:00.0033289
Value type, from code project: 00:00:00.0033027
Reference type, original: 00:00:00.0076951
Reference type, from code project: 00:00:00.0068459

Benchmark code:

        Console.WriteLine("Testing performance...");
        Console.WriteLine("");

        const Int32 _runs = 10000;

        Stopwatch sw = new Stopwatch();
        Console.Write("Value type, original: ");
        sw.Start();
        for (Int32 i = 0; i < _runs; i++)
        {
            try
            {
                i.In(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
            }
            catch (Exception)
            {
                // do nothing    
            }
        }
        sw.Stop();
        Console.WriteLine(sw.Elapsed.ToString());

        sw = new Stopwatch();
        Console.Write("Value type, from code project: ");
        sw.Start();
        for (Int32 i = 0; i < _runs; i++)
        {
            try
            {
                i.In2(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
            }
            catch (Exception)
            {
                // do nothing    
            }
        }
        sw.Stop();
        Console.WriteLine(sw.Elapsed.ToString());


        sw = new Stopwatch();
        Console.Write("Reference type, original: ");
        sw.Start();
        for (Int32 i = 0; i < _runs; i++)
        {
            try
            {
                "This String".In("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
            }
            catch (Exception)
            {
                // do nothing    
            }
        }
        sw.Stop();
        Console.WriteLine(sw.Elapsed.ToString());

        sw = new Stopwatch();
        Console.Write("Reference type, from code project: ");
        sw.Start();
        for (Int32 i = 0; i < _runs; i++)
        {
            try
            {
                "This String".In("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
            }
            catch (Exception)
            {
                // do nothing    
            }
        }
        sw.Stop();
        Console.WriteLine(sw.Elapsed.ToString());

        Console.WriteLine("");
        Console.ReadLine();


public static bool In<T>(this T source, params T[] list)
{
    if (source == null) throw new ArgumentNullException("source");
    return list.Contains(source);
}

public static bool In2<T>(this T source, params T[] list)
{
    if (!typeof(T).IsValueType)
    {
        if (Equals(source, default(T))) throw new ArgumentNullException("source");
    }
    return list.Contains(source);
}

I would leave your code as

public static bool In<T>(this T source, params T[] list)
{
    if (null == source) throw new ArgumentNullException("source");
    return list.Contains(source);
}

because it is easier to read.

On a related note: can source ever be a value type? If not you could constrain T as T:class .

The two methods are basically equivalent.

In your original version, if T is a value type, then the test always fails: a value type is never equal to a null pointer. Because the condition is always false, the test is optimized away.

In the second version, the test is made explicitly, but the result is exactly the same.

I see no reason to prefer one over the other. Your original is probably slightly faster on value types, but I would add a comment explaining why it works.

Aside from the performance amelioration, wich is debattable as it makes the code less readable (but not overly so), I'm more concerned by the fact your two methods do not have the same semantic. There are indeed value types that can be null: Nullable<TValue> , better known as TValue? .

The following code:

int? nullableInt = null;
nullableInt.In(list);

throws an ArgumentNullException in the first implementation and not in the second one (provided list has been previously correctly initialized).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM