我有一个这样定义的泛型方法:

public void MyMethod<T>(T myArgument)

我要做的第一件事是检查myArgument的值是否为该类型的默认值,就像这样:

if (myArgument == default(T))

但这不能编译,因为我不能保证T会实现==运算符。所以我把代码转换成这样:

if (myArgument.Equals(default(T)))

现在这个编译了,但是如果myArgument为null就会失败,这是我测试的一部分。我可以像这样添加一个显式的空检查:

if (myArgument == null || myArgument.Equals(default(T)))

现在我觉得这是多余的。ReSharper甚至建议我将myArgument == null部分更改为myArgument == default(T),这是我开始的地方。有没有更好的方法来解决这个问题?

我需要同时支持引用类型和值类型。


当前回答

这个怎么样:

if (object.Equals(myArgument, default(T)))
{
    //...
}

使用static object.Equals()方法可以避免您自己进行空检查。用object显式地限定调用。根据上下文可能没有必要,但我通常用类型名作为静态调用的前缀,只是为了使代码更容易解决。

其他回答

这个怎么样:

if (object.Equals(myArgument, default(T)))
{
    //...
}

使用static object.Equals()方法可以避免您自己进行空检查。用object显式地限定调用。根据上下文可能没有必要,但我通常用类型名作为静态调用的前缀,只是为了使代码更容易解决。

(编辑)

Marc Gravell给出了最好的答案,但我想发布一个简单的代码片段来演示它。在一个简单的c#控制台应用程序中运行:

public static class TypeHelper<T>
{
    public static bool IsDefault(T val)
    {
         return EqualityComparer<T>.Default.Equals(obj,default(T));
    }
}

static void Main(string[] args)
{
    // value type
    Console.WriteLine(TypeHelper<int>.IsDefault(1)); //False
    Console.WriteLine(TypeHelper<int>.IsDefault(0)); // True

    // reference type
    Console.WriteLine(TypeHelper<string>.IsDefault("test")); //False
    Console.WriteLine(TypeHelper<string>.IsDefault(null)); //True //True

    Console.ReadKey();
}

还有一件事:使用VS2008的人可以尝试将此作为扩展方法吗?我在这里停留在2005年,我很好奇这是否会被允许。


编辑:下面是如何让它作为一个扩展方法工作:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // value type
        Console.WriteLine(1.IsDefault());
        Console.WriteLine(0.IsDefault());

        // reference type
        Console.WriteLine("test".IsDefault());
        // null must be cast to a type
        Console.WriteLine(((String)null).IsDefault());
    }
}

// The type cannot be generic
public static class TypeHelper
{
    // I made the method generic instead
    public static bool IsDefault<T>(this T val)
    {
        return EqualityComparer<T>.Default.Equals(val, default(T));
    }
}

我找到了一篇Microsoft Connect的文章,详细讨论了这个问题:

Unfortunately, this behavior is by design and there is not an easy solution to enable use of with type parameters that may contain value types. If the types are known to be reference types, the default overload of defined on object tests variables for reference equality, although a type may specify its own custom overload. The compiler determines which overload to use based on the static type of the variable (the determination is not polymorphic). Therefore, if you change your example to constrain the generic type parameter T to a non-sealed reference type (such as Exception), the compiler can determine the specific overload to use and the following code would compile:

public class Test<T> where T : Exception

If the types are known to be value types, performs specific value equality tests based on the exact types used. There is no good "default" comparison here since reference comparisons are not meaningful on value types and the compiler cannot know which specific value comparison to emit. The compiler could emit a call to ValueType.Equals(Object) but this method uses reflection and is quite inefficient compared to the specific value comparisons. Therefore, even if you were to specify a value-type constraint on T, there is nothing reasonable for the compiler to generate here:

public class Test<T> where T : struct

在您介绍的例子中,编译器甚至不知道T是值还是引用类型,同样也没有生成对所有可能类型都有效的东西。引用比较对于值类型无效,某些类型的值比较对于没有重载的引用类型是不可预期的。

以下是你可以做的事情。

我已经验证了这两个方法都适用于引用类型和值类型的泛型比较:

object.Equals(param, default(T))

or

EqualityComparer<T>.Default.Equals(param, default(T))

要与"=="操作符进行比较,您需要使用以下方法之一:

如果T的所有情况都派生自一个已知的基类,则可以使用泛型类型限制让编译器知道。

public void MyMethod<T>(T myArgument) where T : MyBase

然后,编译器识别如何在MyBase上执行操作,不会抛出“Operator '=='不能应用于类型为'T'和'T'的操作数”错误,你现在看到的。

另一种选择是将T限制为实现IComparable的任何类型。

public void MyMethod<T>(T myArgument) where T : IComparable

然后使用IComparable接口定义的CompareTo方法。

我认为您可能需要将这个逻辑分为两部分,并首先检查null。

public static bool IsNullOrEmpty<T>(T value)
{
    if (IsNull(value))
    {
        return true;
    }
    if (value is string)
    {
        return string.IsNullOrEmpty(value as string);
    }
    return value.Equals(default(T));
}

public static bool IsNull<T>(T value)
{
    if (value is ValueType)
    {
        return false;
    }
    return null == (object)value;
}

In the IsNull method, we're relying on the fact that ValueType objects can't be null by definition so if value happens to be a class which derives from ValueType, we already know it's not null. On the other hand, if it's not a value type then we can just compare value cast to an object against null. We could avoid the check against ValueType by going straight to a cast to object, but that would mean that a value type would get boxed which is something we probably want to avoid since it implies that a new object is created on the heap.

在IsNullOrEmpty方法中,我们检查字符串的特殊情况。对于所有其他类型,我们将值(已经知道不为空)与它的默认值进行比较,对于所有引用类型,默认值为空,对于值类型,通常是某种形式的零(如果它们是整型)。

使用这些方法,下面的代码会像你预期的那样运行:

class Program
{
    public class MyClass
    {
        public string MyString { get; set; }
    }

    static void Main()
    {
        int  i1 = 1;    Test("i1", i1); // False
        int  i2 = 0;    Test("i2", i2); // True
        int? i3 = 2;    Test("i3", i3); // False
        int? i4 = null; Test("i4", i4); // True

        Console.WriteLine();

        string s1 = "hello";      Test("s1", s1); // False
        string s2 = null;         Test("s2", s2); // True
        string s3 = string.Empty; Test("s3", s3); // True
        string s4 = "";           Test("s4", s4); // True

        Console.WriteLine();

        MyClass mc1 = new MyClass(); Test("mc1", mc1); // False
        MyClass mc2 = null;          Test("mc2", mc2); // True
    }

    public static void Test<T>(string fieldName, T field)
    {
        Console.WriteLine(fieldName + ": " + IsNullOrEmpty(field));
    }

    // public static bool IsNullOrEmpty<T>(T value) ...

    // public static bool IsNull<T>(T value) ...
}

这里有个问题

如果你想让它适用于任何类型,default(T)对于引用类型总是空的,对于值类型总是0(或充满0的结构体)。

不过,这可能不是你想要的行为。如果希望以通用方式工作,可能需要使用反射检查T的类型,并处理不同于引用类型的值类型。

或者,您可以在此设置一个接口约束,该接口可以提供一种方法来检查类/结构的默认值。