2008-12-24 50 views
264

按照==操作者在MSDN文档,不能将运算符==应用于C#中的泛型类型吗?

对于预定义的值的类型,如果 的操作数的值相等的 等号(==)返回true, 否则返回false。对于字符串以外的参考类型 ,==返回true,如果 其两个操作数指向相同的 对象。对于字符串类型,== 比较字符串的值。 用户定义的值类型可能会过载 ==运算符(请参阅运算符)。所以 用户定义的参考类型,尽管默认情况下为 ==对于两个预定义参数类型和 用户定义的参考类型的行为如上所述 。

那么为什么这段代码片段无法编译?

void Compare<T>(T x, T y) { return x == y; } 

我得到的错误操作“==”不能应用于类型的操作数“T”和“T”。我想知道为什么,因为据我所知==运算符是为所有类型预定义的?

编辑:谢谢大家。起初我没有注意到这个陈述只是关于参考类型。我还认为所有值类型都提供了逐位比较,我现在知道的是而不是正确。

但是,如果我使用的是引用类型,那么==运算符是否会使用预定义的引用比较,或者如果类型定义了运算符,它会使用运算符的重载版本吗?

编辑2:通过反复试验,我们了解到==运算符在使用不受限制的泛型类型时将使用预定义的引用比较。实际上,编译器会使用它可以找到的限制类型参数的最佳方法,但不会再看到。例如,下面的代码将始终打印true,即使Test.test<B>(new B(), new B())叫做:

class A { public static bool operator==(A x, A y) { return true; } } 
class B : A { public static bool operator==(B x, B y) { return false; } } 
class Test { void test<T>(T a, T b) where T : A { Console.WriteLine(a == b); } } 
+0

见我的答案回答你的后续问题。 – 2008-12-24 12:48:03

+0

理解即使没有泛型,在同一类型的两个操作数之间不允许使用'=='也有一些类型。这对于`struct ==`不会超载的`struct`类型(除了“预定义”类型)是正确的。作为一个简单的例子,试试这个:var map = typeof(string).GetInterfaceMap(typeof(ICloneable)); Console.WriteLine(map == map);/*编译时错误* /` – 2013-08-19 08:54:52

+0

继续我自己的旧评论。例如(见[其他线程](https://stackoverflow.com/questions/6379915/)),`var kvp1 = new KeyValuePair (); var kvp2 = kvp1;`,那么你不能检查`kvp1 == kvp2`,因为`KeyValuePair <,>`是一个结构体,它不是C#预定义的类型,并且它不会重载'operator ==`。然而,一个例子是`var li = new List (); var e1 = li.GetEnumerator(); var e2 = e1;``不能用'e1 == e2`(这里我们有嵌套结构体`List <>。Enumerator`(称为```List`1 + Enumerator [T]“``由运行时)不会超载`==`)。 – 2017-06-18 14:45:05

回答

113

“...默认情况下==对于预定义和用户定义的引用类型的行为如上所述。”

类型T不一定是引用类型,所以编译器不能做出这样的假设。

然而,这将编译,因为它是更明确:

bool Compare<T>(T x, T y) where T : class 
    { 
     return x == y; 
    } 

跟进到另一个问题,“但是,万一我使用引用类型,将在==操作符使用预定义的引用比较,还是使用运算符的重载版本(如果类型定义了一个)?

我原以为==上的泛型会使用重载版本,但下面的测试说明不然。有趣...我想知道为什么!如果有人知道请分享。

namespace TestProject 
{ 
class Program 
{ 
    static void Main(string[] args) 
    { 
     Test a = new Test(); 
     Test b = new Test(); 

     Console.WriteLine("Inline:"); 
     bool x = a == b; 
     Console.WriteLine("Generic:"); 
     Compare<Test>(a, b); 

    } 


    static bool Compare<T>(T x, T y) where T : class 
    { 
     return x == y; 
    } 
} 

class Test 
{ 
    public static bool operator ==(Test a, Test b) 
    { 
     Console.WriteLine("Overloaded == called"); 
     return a.Equals(b); 
    } 

    public static bool operator !=(Test a, Test b) 
    { 
     Console.WriteLine("Overloaded != called"); 
     return a.Equals(b); 
    } 
    } 
} 

输出

内联: 重载==称为

通用:

按任意键继续。 。 。

跟进2

我想指出的是,改变我的比较方法

static bool Compare<T>(T x, T y) where T : Test 
    { 
     return x == y; 
    } 

导致重载==操作符被调用。我猜没有指定类型(作为,其中),编译器不能推断它应该使用重载操作符......尽管我认为它会有足够的信息来做出该决定,即使没有指定类型。

+0

谢谢。我没有注意到这个陈述只是关于参考类型。 – 2008-12-24 08:04:11

12

的编译无法知道牛逼不可能是一个结构(值类型)。所以,你要告诉它,它只能是引用类型的,我认为:

bool Compare<T>(T x, T y) where T : class { return x == y; } 

这是因为如果T可能是值类型,有可能是在那里x == y会生病形成的情况下 - 在情况下,当一个类型没有按没有定义的运算符==。同样会发生这个是比较明显的:

void CallFoo<T>(T x) { x.foo(); } 

失败过,因为你可以传递不会有一个函数foo类型T。 C#强制你确保所有可能的类型总是有一个函数foo。这由where子句完成。

+1

感谢您的澄清。我不知道这些值类型不支持开箱即用的==操作符。 – 2008-12-24 08:03:29

+1

Hosam,我用gmcs(单声道)进行测试,它总是比较引用。 (即它不使用任意定义的运算符== for T) – 2008-12-24 08:43:59

+0

这个解决方案有一个警告:operator ==不能被重载; [看到这个StackOverflow问题](http://stackoverflow.com/questions/2919232/using-overloaded-operator-in-a-generic-function)。 – 2010-05-27 10:00:56

1
 

bool Compare(T x, T y) where T : class { return x == y; } 
 

上述将工作,因为在用户定义的引用类型的情况下==照顾。
在值类型的情况下,==可以被覆盖。在这种情况下,还应该定义“!=”。

我认为这可能是原因,它不允许使用“==”进行泛型比较。

+2

谢谢。我相信引用类型也可以覆盖操作符。但失败的原因现在很清楚。 – 2008-12-24 08:16:40

+1

`==`令牌用于两个不同的运算符。如果对于给定的操作数类型,存在相等运算符的兼容重载,则将使用该重载。否则,如果两个操作数都是彼此兼容的引用类型,则将使用引用比较。请注意,在上面的`Compare`方法中,编译器无法确定第一个意义是否适用,但可以指出第二个意义适用,所以即使'T'重载相等性,检查运算符(例如,如果它是`String`类型)*。 – supercat 2013-05-30 21:24:29

6

看来,没有类约束:

bool Compare<T> (T x, T y) where T: class 
{ 
    return x == y; 
} 

每个人都应该认识到,虽然class==运营商的限制EqualsObject.Equals继承,而struct的覆盖ValueType.Equals

需要注意的是:

bool Compare<T> (T x, T y) where T: struct 
{ 
    return x == y; 
} 

也给出了相同的编译器错误。

至今我不明白为什么有一个值类型相等运算符比较被编译器拒绝。我确实知道一个事实,但它的工作原理如下:

bool Compare<T> (T x, T y) 
{ 
    return x.Equals(y); 
} 
249

正如其他人所说的那样,它只会在T被限制为引用类型时才起作用。如果没有任何约束,可以将其与null进行比较,但只能为null - 并且对于不可为空的值类型,该比较始终为false。

而不是调用的Equals的,最好是使用IComparer<T> - 如果你有没有更多的信息,EqualityComparer<T>.Default是一个不错的选择:

public bool Compare<T>(T x, T y) 
{ 
    return EqualityComparer<T>.Default.Equals(x, y); 
} 
从别的

除此之外,这避免了拳击/铸造。

34

一般而言,EqualityComparer<T>.Default.Equals应该使用任何实施IEquatable<T>或具有合理的Equals实现的任何工作。

但是,如果==Equals由于某种原因实现不同,那么我在generic operators上的工作应该是有用的;它支持操作者的版本(等等):

  • 等于(T值1,T值2)
  • NotEqual(T值1,T值2)
  • GREATERTHAN(T值1,T值2)
  • 每种不超过(T值1,T值2)
  • GreaterThanOrEqual(T值1,T值2)
  • LessThanOrEqual(T值1,T值2)
4

有这个here

亚历克斯·特纳的答复的MSDN连接条目开头:

不幸的是,这种行为是 设计并没有一个简单的 解决方案来实现使用==的与类型 参数可能包含值 类型。

21

这么多答案,而不是一个解释为什么? (Giovanni明确要求)...

.NET泛型不像C++模板。在C++模板中,重载分辨率在实际模板参数已知之后发生。

在.NET泛型(包括C#)中,在不知道实际泛型参数的情况下发生重载解析。编译器可以用来选择要调用的函数的唯一信息来自泛型参数的类型约束。

4

如果你想确保你的自定义类型的操作符被调用,你可以通过反射来实现。只需使用泛型参数获取类型,然后检索所需操作符的MethodInfo(例如,op_Equality,op_Inequality,op_LessThan ...)。

var methodInfo = typeof (T).GetMethod("op_Equality", 
          BindingFlags.Static | BindingFlags.Public);  

然后使用MethodInfo的Invoke方法执行运算符并传入对象作为参数。

var result = (bool) methodInfo.Invoke(null, new object[] { object1, object2}); 

这将调用您的重载操作符,而不是泛型参数上应用的约束定义的操作符。可能不实际,但在使用包含一些测试的通用基类时,可以派上用场,以便对您的操作员进行单元测试。

1

我写了下面的函数看最新的msdn。它可以很容易地比较两个对象xy

static bool IsLessThan(T x, T y) 
{ 
    return ((IComparable)(x)).CompareTo(y) <= 0; 
} 
2

那么在我的情况下,我想单元测试等号运算符。我需要在等式运算符下调用代码,而不明确设置泛型类型。对于EqualityComparer的建议没有帮助,因为EqualityComparer被称为Equals方法,但不是等于运算符。

以下是我如何通过构建LINQ来处理泛型类型。它调用正确的代码==!=运营商:

/// <summary> 
/// Gets the result of "a == b" 
/// </summary> 
public bool GetEqualityOperatorResult<T>(T a, T b) 
{ 
    // declare the parameters 
    var paramA = Expression.Parameter(typeof(T), nameof(a)); 
    var paramB = Expression.Parameter(typeof(T), nameof(b)); 
    // get equality expression for the parameters 
    var body = Expression.Equal(paramA, paramB); 
    // compile it 
    var invokeEqualityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile(); 
    // call it 
    return invokeEqualityOperator(a, b); 
} 

/// <summary> 
/// Gets the result of "a =! b" 
/// </summary> 
public bool GetInequalityOperatorResult<T>(T a, T b) 
{ 
    // declare the parameters 
    var paramA = Expression.Parameter(typeof(T), nameof(a)); 
    var paramB = Expression.Parameter(typeof(T), nameof(b)); 
    // get equality expression for the parameters 
    var body = Expression.NotEqual(paramA, paramB); 
    // compile it 
    var invokeInequalityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile(); 
    // call it 
    return invokeInequalityOperator(a, b); 
} 
相关问题