我有一个像这样定义的泛型方法:
public void MyMethod(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),这是我开始的地方.有没有更好的方法来解决这个问题?
我需要支持两种引用类型和值类型.
为了避免装箱,比较泛型的最佳方法是使用EqualityComparer
.这尊重IEquatable
(没有拳击)以及object.Equals
处理所有Nullable
"提升"的细微差别.因此:
if(EqualityComparer.Default.Equals(obj, default(T))) { return obj; }
这将匹配:
类为null
null(空)for Nullable
其他结构的零/假/等
这个怎么样:
if (object.Equals(myArgument, default(T))) { //... }
使用该static object.Equals()
方法可以避免您自己进行null
检查.object.
根据您的上下文,可能没有必要显式地限定调用,但我通常static
使用类型名称为调用添加前缀,以使代码更易于解析.
我能够找到一篇详细讨论此问题的Microsoft Connect文章:
不幸的是,这种行为是设计上的,并没有一个简单的解决方案来启用可能包含值类型的类型参数.
如果已知类型是引用类型,则对象上定义的默认重载测试变量以引用相等,尽管类型可以指定自己的自定义重载.编译器根据变量的静态类型确定要使用的重载(确定不是多态的).因此,如果您更改示例以将泛型类型参数T约束为非密封引用类型(例如Exception),则编译器可以确定要使用的特定重载,以下代码将编译:
public class Testwhere T : Exception
如果已知类型是值类型,则根据使用的确切类型执行特定值相等性测试.这里没有好的"默认"比较,因为参考比较对值类型没有意义,并且编译器无法知道要发出哪个特定值比较.编译器可以发出对ValueType.Equals(Object)的调用,但是此方法使用反射,并且与特定值比较相比效率很低.因此,即使您要在T上指定值类型约束,编译器也无法在此处生成:
public class Testwhere T : struct
在您提供的情况下,编译器甚至不知道T是值还是引用类型,同样没有任何生成可以对所有可能类型有效的内容.参考比较对于值类型无效,对于不重载的引用类型,某种值比较会出乎意料.
这是你可以做的......
我已经验证了这两种方法都可以用于参考和值类型的通用比较:
object.Equals(param, default(T))
要么
EqualityComparer.Default.Equals(param, default(T))
要与"=="运算符进行比较,您需要使用以下方法之一:
如果T的所有情况都来自已知的基类,您可以让编译器知道使用泛型类型限制.
public void MyMethod(T myArgument) where T : MyBase
编译器然后识别如何执行操作,MyBase
并且不会抛出"运算符'=='不能应用于现在看到的'T'和'T'类型的操作数"错误.
另一种选择是将T限制为任何实现的类型IComparable
.
public void MyMethod(T myArgument) where T : IComparable
然后使用IComparable接口CompareTo
定义的方法.
试试这个:
if (EqualityComparer.Default.Equals(myArgument, default(T)))
应该编译,并做你想要的.
(编辑)的
Marc Gravell有最好的答案,但是我想发布一个简单的代码片段,我用它来演示它.只需在一个简单的C#控制台应用程序中运行它:
public static class TypeHelper{ public static bool IsDefault(T val) { return EqualityComparer .Default.Equals(obj,default(T)); } } static void Main(string[] args) { // value type Console.WriteLine(TypeHelper .IsDefault(1)); //False Console.WriteLine(TypeHelper .IsDefault(0)); // True // reference type Console.WriteLine(TypeHelper .IsDefault("test")); //False Console.WriteLine(TypeHelper .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(this T val) { return EqualityComparer .Default.Equals(val, default(T)); } }