C#中generics参数的空或默认比较

我有一个像这样定义的generics方法:

public void MyMethod<T>(T myArgument) 

我想要做的第一件事是检查myArgument的值是否为该types的默认值,如下所示:

 if (myArgument == default(T)) 

但是这不会编译,因为我不能保证T将实现==运算符。 所以我把代码切换到这个:

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

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

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

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

我需要支持引用types和值types。

为了避免装箱,比较generics的最佳方法是EqualityComparer<T>.Default 。 这尊重IEquatable<T> (没有装箱)以及object.Equals ,并处理所有Nullable<T> “提升”的细微差别。 因此:

 if(EqualityComparer<T>.Default.Equals(obj, default(T))) { return obj; } 

这将匹配:

  • null为类
  • null(空)为Nullable<T>
  • 零/假/等其他结构

这个怎么样:

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

使用static object.Equals()方法避免了你自己null检查的需要。 用object.显式限定调用object. 根据您的上下文,可能不是必需的,但我通常将static调用与types名称前缀,以使代码更可溶。

我能find一个详细讨论这个问题的Microsoft Connect文章 :

不幸的是,这种行为是在devise上,并没有一个简单的解决scheme来使用可能包含值types的types参数。

如果已知types是引用types,则定义在对象上的默认重载会testing引用相等的variables,尽pipetypes可能会指定自己的自定义重载。 编译器根据variables的静态types确定使用哪个重载(确定不是多态)。 因此,如果更改示例以将genericstypes参数T约束为非密封引用types(例如Exception),编译器可以确定要使用的特定重载,并编译以下代码:

 public class Test<T> where T : Exception 

如果已知types是值types,则根据所使用的确切types执行特定的值相等性testing。 这里没有比较好的“默认”比较,因为参考比较对值types没有意义,编译器也不知道要发出哪个特定的值比较。 编译器可以发出对ValueType.Equals(Object)的调用,但是这种方法使用了reflection,并且与特定的值比较相比效率非常低。 因此,即使你要在T上指定一个值types约束,编译器在这里也没有什么合理的:

 public class Test<T> where T : struct 

在你提出的情况下,编译器甚至不知道T是一个值还是引用types,对于所有可能的types也没有什么可以产生的。 对于值types,引用比较不会有效,对于不会超载的引用types,某种types的值比较将会是意想不到的。

这是你可以做的…

我已经validation了这两种方法都适用于参考和值types的通用比较:

 object.Equals(param, default(T)) 

要么

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

要与“==”运算符进行比较,您需要使用以下方法之一:

如果T的所有情况都来自已知的基类,那么可以让编译器知道使用genericstypes限制。

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

然后,编译器将识别如何在MyBase上执行操作,并且不会抛出“Operator ==”不能应用于您现在看到的types为“T”和“T”types的操作数。

另一个select是将T限制为实现IComparable任何types。

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

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

尝试这个:

 if (EqualityComparer<T>.Default.Equals(myArgument, default(T))) 

应该编译,并做你想做的。

(编辑)的

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

为了处理所有types的T,包括T是一个原始types,你需要在两种比较方法中进行编译:

  T Get<T>(Func<T> createObject) { T obj = createObject(); if (obj == null || obj.Equals(default(T))) return obj; // .. do a bunch of stuff return obj; } 

这里会有一个问题 –

如果你打算让它适用于任何types,那么对于引用types,默认(T)将始终为空,对于值types,默认(T)将为0(或者结构满为0)。

这可能不是你以后的行为。 如果您希望以通用方式工作,则可能需要使用reflection来检查T的types,并处理与引用types不同的值types。

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

我想你可能需要将这个逻辑分成两部分,先检查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; } 

在IsNull方法中,我们依赖于ValueType对象不能被定义为null的事实,所以如果value碰巧是一个从ValueType派生的类,我们已经知道它不是null。 另一方面,如果它不是一个值types,那么我们可以将值转换为null对象。 我们可以通过直接转换为对象来避免对ValueType的检查,但是这意味着值types会被装箱,这是我们可能想要避免的,因为这意味着在堆上创build了一个新的对象。

在IsNullOrEmpty方法中,我们正在检查string的特殊情况。 对于所有其他types,我们正在比较值(它已经知道不是空值),对于所有引用types的默认值是null,对于值types通常是某种forms的零(如果它们是整数)。

使用这些方法,下面的代码就像您所期望的那样运行:

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

不知道这是否符合你的要求,但是你可以将T约束为一个实现了一个接口的types,例如IComparable,然后使用这个接口的ComparesTo()方法(IIRC支持/处理null) :

 public void MyMethod<T>(T myArgument) where T : IComparable ... if (0 == myArgument.ComparesTo(default(T))) 

可能还有其他一些接口可以使用,比如IEquitable等等。

@ilitirit:

 public class Class<T> where T : IComparable { public T Value { get; set; } public void MyMethod(T val) { if (Value == val) return; } } 

运算符'=='不能应用于'T'和'T'types的操作数

我想不出一种方法来做到这一点,没有显式的空testing,然后调用Equals方法或object.Equals如上所述。

你可以使用System.Comparison来devise一个解决scheme,但是实际上这将会导致更多的代码行,并增加复杂性。

我想你近了。

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

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

你只需要逆向调用equals的对象就可以实现一个优雅的无效的方法。

 default(T).Equals(myArgument);