泛型类型的相等运算符和默认值是C#中的两个便利功能。但我们无法轻易地无缝使用它们。例如,我希望以下代码可以编译,
public static bool EqualsDefault<T>(T subject){
return subject == default(T);
}
不幸的是,它会失败,尽管有一个反直觉的选择,
public static bool EqualsDefault<T>(T subject){
return object.Equals(subject, default(T));
}
所以我的问题是为什么C#禁止第一个代码片段?
答案 0 :(得分:7)
它不起作用的原因是内置的引用相等运算符不能应用于值类型。
让我们退后一步,注意System.Object实际上并没有定义一个等于运算符==
。 C#语言定义了带签名的内置引用相等运算符(参见C#5规范第7.6.10节):
bool operator ==(object x, object y);
但是,有两个规则可以应用:
预定义的引用类型相等运算符需要以下之一:
- 两个操作数都是已知为引用类型或文字null的类型的值。此外,从操作数的类型到另一个操作数的类型存在显式引用转换(第6.2.4节)。
- 一个操作数是类型T的值,其中T是类型参数,另一个操作数是文字null。此外,T没有值类型约束。
规范然后指出,这意味着将运算符应用于两个值类型是错误的,除非类型显式定义了相等运算符。由于没有约束,因此允许值类型,并且两个操作数都不为null。因此,无法应用内置的相等运算符并产生错误。
要解决此问题,您可以将T
限制为引用类型:
public static bool EqualsDefault<T>(T subject) where T : class {
return subject == default(T);
}
但是,您需要注意以上内容始终是参考比较。编译器只会在编译时调用最具体适用类型的==
运算符,在本例中为object
。
更好的选择是使用EqualityComparer<T>.Default
来阻止值类型的装箱:
public static bool EqualsDefault<T>(T subject) {
return EqualityComparer<T>.Default.Equals(subject, default(T));
}
我想你可以问为什么C#没有设计成一个默认的相等运算符,可以应用于没有装箱的值类型。我不知道完整的原因,但我怀疑现在确定在哪种情况下调用哪种方法可能会比现在更令人困惑。我认为如果在普通方法中调用重载运算符,但在泛型方法中使用另一种机制则不合需要。虽然你可以说现在可以用引用类型发生。
答案 1 :(得分:0)
如前所述,编译器抱怨,因为T可以是任何东西:引用类型或结构。
因此,如果你用约束来明确它:
public static bool EqualsDefault<T>(T subject) where T : class{
return subject == default(T);
}
它不会再抱怨了。因为现在,编译器知道,它始终是引用类型。
显然,对于第二个。
public static bool EqualsDefault<T>(T subject){
return object.Equals(subject, default(T));
}
它的工作原理是,.net中的所有预定义或用户定义的类型直接或间接地从对象和对象继承实现了Equals方法。