对引用和值类型进行空检查

时间:2011-10-05 07:10:17

标签: c# types extension-methods

(更新 - 来自评论)问题:使用一种扩展方法优于另一种方法有什么优势吗?

根据我在codeproject article关于扩展方法的讨论,我不确定以下内容是否正确。

目前,我有以下扩展方法:

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

哪个按预期工作。在评论中已经建议我改变它,以便它只检查引用类型,如:

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);
}

再次按预期工作。 第二种方法优于第一种方法,鉴于运行快速基准测试,我们谈论的是10000次运行的0.001秒差异。

基准测试输出(Core i3 @ 4ghz,RAID 0 ssd):

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

基准代码:

        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);
}

3 个答案:

答案 0 :(得分:2)

我会将您的代码保留为

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

因为它更容易阅读。

在相关的说明中:源可以是值类型吗?如果不是,您可以将T约束为T:class

答案 1 :(得分:1)

这两种方法基本相同。

在原始版本中,如果T是值类型,则测试始终失败:值类型永远不等于空指针。因为条件总是假的,所以测试被优化掉了。

在第二个版本中,测试是明确的,但结果完全相同。

我认为没有理由偏爱另一个。您的原始版本可能在值类型上略快一些,但我会添加一条注释来解释它的工作原理。

答案 2 :(得分:1)

除了性能改进之外,由于它使代码的可读性降低(但不是过于严重),因此可以解释,我更担心的是你的两个方法没有相同的语义。确实有值类型可以为null:Nullable<TValue>,更好地称为TValue?

以下代码:

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

在第一个实现中抛出ArgumentNullException,而不是在第二个实现中抛出(提供的列表以前已经正确初始化)。