Can't operator == be applied to generic types in C#?
根据MSDN中的
For predefined value types, the
equality operator (==) returns true if
the values of its operands are equal,
false otherwise. For reference types
other than string, == returns true if
its two operands refer to the same
object. For the string type, ==
compares the values of the strings.
User-defined value types can overload
the == operator (see operator). So can
user-defined reference types, although
by default == behaves as described
above for both predefined and
user-defined reference types.
那么,为什么这个代码片段无法编译呢?
1 | bool Compare<T>(T x, T y) { return x == y; } |
我得到错误运算符"=="不能应用于"t"和"t"类型的操作数。我想知道为什么,因为据我所知,
编辑:谢谢大家。我一开始没有注意到这个语句只是关于引用类型的。我还认为所有的值类型都提供了逐位比较,我现在知道这是不正确的。
但是,在我使用引用类型的情况下,
编辑2:通过反复试验,我们了解到当使用不受限制的泛型类型时,
1 2 3 | 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); } } |
正如其他人所说,只有当t被约束为引用类型时,它才能工作。如果没有任何约束,则可以与空进行比较,但只能与空进行比较——对于不可为空的值类型,该比较始终为假。
最好使用
1 2 3 4 | public bool Compare<T>(T x, T y) { return EqualityComparer<T>.Default.Equals(x, y); } |
除此之外,这可以避免拳击/施法。
"…默认情况下,==对于预定义的和用户定义的引用类型,其行为如上所述。"
类型t不一定是引用类型,因此编译器不能做出这样的假设。
但是,这将编译,因为它更明确:
1 2 3 4 | bool Compare<T>(T x, T y) where T : class { return x == y; } |
后续问题,"但是,如果我使用的是引用类型,==运算符会使用预定义的引用比较吗,如果类型定义了引用比较,它会使用重载版本的运算符吗?"
我本以为在泛型上的==将使用重载的版本,但下面的测试则相反地证明了这一点。有趣…我很想知道为什么!如果有人知道,请分享。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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); } } } |
产量
内联:重载==调用
Generic:
按任意键继续。…
随访2
我想指出,将比较方法改为
1 2 3 4 | static bool Compare<T>(T x, T y) where T : Test { return x == y; } |
导致调用重载的==运算符。我猜如果不指定类型(作为where),编译器就不能推断它应该使用重载运算符…尽管我认为它有足够的信息来做出决定,即使没有指定类型。
一般来说,
但是,如果由于某种原因,
- 相等(t值1,t值2)
- NotEqual(t值1,t值2)
- 大于(t值1,t值2)
- Lessthan(t值1,t值2)
- 大于或等于(t值1,t值2)
- 小于等于(t值1,t值2)
这么多答案,没有一个能解释为什么?(乔瓦尼明确要求)
NET泛型不能像C++模板那样运行。在C++模板中,在已知实际模板参数之后发生过载解析。
在.NET泛型(包括C)中,重载解析发生在不知道实际泛型参数的情况下。编译器可以用来选择要调用的函数的唯一信息来自于对泛型参数的类型约束。
编译不知道不能是结构(值类型)。所以你必须告诉它只能是参考类型,我想:
1 | bool Compare<T>(T x, T y) where T : class { return x == y; } |
这是因为如果t可以是一个值类型,那么在某些情况下,
1 | void CallFoo<T>(T x) { x.foo(); } |
这也失败了,因为您可以传递一个没有函数foo的类型t。c强制您确保所有可能的类型始终具有foo函数。这是由WHERE子句完成的。
似乎没有类约束:
1 2 3 4 | bool Compare<T> (T x, T y) where T: class { return x == y; } |
应该认识到,虽然
注意:
1 2 3 4 | bool Compare<T> (T x, T y) where T: struct { return x == y; } |
也会产生相同的编译器错误。
到目前为止,我还不明白为什么编译器拒绝使用值类型相等运算符比较。不过,我确实知道这是可行的:
1 2 3 4 | bool Compare<T> (T x, T y) { return x.Equals(y); } |
在我的例子中,我想对等式运算符进行单元测试。我需要在相等运算符下调用代码,而不显式设置泛型类型。对于
下面是我如何通过构建一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /// <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); } |
如果要确保调用自定义类型的运算符,可以通过反射来实现。只需使用泛型参数获取类型并检索所需运算符的MethodInfo(例如op_equality、op_equality、op_equality、op_lessthan…)。
1 2 |
然后使用MethodInfo的invoke方法执行该运算符,并将对象作为参数传入。
1 |
这将调用重载运算符,而不是由应用于泛型参数的约束定义的运算符。可能不实用,但在使用包含两个测试的通用基类时,可以方便地对运算符进行单元测试。
这里有一个msdn连接项
亚历克斯·特纳的回答始于:
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.
我编写了以下函数来查看最新的msdn。它可以很容易地比较两个对象:
1 2 3 4 | static bool IsLessThan(T x, T y) { return ((IComparable)(x)).CompareTo(y) <= 0; } |
1 2 3 | <wyn> bool Compare(T x, T y) where T : class { return x == y; } </wyn> |
上面的内容将起作用,因为在用户定义的引用类型的情况下,==会得到处理。对于值类型,==可以被重写。在这种情况下,"!="还应定义。
我认为这可能是原因,它不允许使用"=="进行一般比较。