运算符==不能应用于C#中的泛型类型吗?

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:通过反复试验,我们了解到当使用不受限制的泛型类型时,==运算符将使用预定义的引用比较。实际上,编译器将使用它能为受限类型参数找到的最佳方法,但不会再进一步查找。例如,下面的代码将始终打印true,即使调用Test.test(new B(), new B())

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被约束为引用类型时,它才能工作。如果没有任何约束,则可以与空进行比较,但只能与空进行比较——对于不可为空的值类型,该比较始终为假。

最好使用IComparer,而不是调用equals,如果您没有更多信息,EqualityComparer.Default是一个不错的选择:

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),编译器就不能推断它应该使用重载运算符…尽管我认为它有足够的信息来做出决定,即使没有指定类型。


一般来说,EqualityComparer.Default.Equals应该对任何实现IEquatable或有明智的Equals实现的东西进行处理。

但是,如果由于某种原因,==Equals的实现方式不同,那么我对通用运算符的研究应该是有用的;它支持(除其他之外)的运算符版本:

  • 相等(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可以是一个值类型,那么在某些情况下,x == y的格式可能不正确——在某些情况下,类型没有定义操作符==。同样的情况也会发生,更明显的是:

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

应该认识到,虽然==操作符中的class约束Equals继承自Object.Equals,而结构的ValueType.Equals则重写。

注意:

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


在我的例子中,我想对等式运算符进行单元测试。我需要在相等运算符下调用代码,而不显式设置泛型类型。对于EqualityComparer的建议没有帮助,因为EqualityComparer称为Equals方法,但不是相等运算符。

下面是我如何通过构建一个LINQ来使用泛型类型。它调用==!=运算符的正确代码:

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
var methodInfo = typeof (T).GetMethod("op_Equality",
                             BindingFlags.Static | BindingFlags.Public);

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

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

这将调用重载运算符,而不是由应用于泛型参数的约束定义的运算符。可能不实用,但在使用包含两个测试的通用基类时,可以方便地对运算符进行单元测试。


这里有一个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。它可以很容易地比较两个对象:xy

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>

上面的内容将起作用,因为在用户定义的引用类型的情况下,==会得到处理。对于值类型,==可以被重写。在这种情况下,"!="还应定义。

我认为这可能是原因,它不允许使用"=="进行一般比较。