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

Hos*_*Aly 309 c# generics operators equals-operator

根据MSDN中==运营商的文档,

对于预定义的值类型,如果操作数的值相等,则相等运算符(==)返回true,否则返回false.对于除string之外的引用类型,如果其两个操作数引用同一对象,则==返回true.对于字符串类型,==比较字符串的值.用户定义的值类型可以重载==运算符(请参阅运算符).用户定义的引用类型也是如此,尽管 默认情况下==的行为与上述预定义和用户定义的引用类型相同.

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

bool Compare<T>(T x, T y) { return x == y; }
Run Code Online (Sandbox Code Playgroud)

我得到错误运算符'=='不能应用于'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); } }
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 281

正如其他人所说,只有当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);
}
Run Code Online (Sandbox Code Playgroud)

除了其他任何东西,这避免了拳击/铸造.

  • 使用EqualityComparer <T>的好建议 (4认同)

Gio*_*lbo 139

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

类型T不一定是引用类型,因此编译器不能进行该假设.

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

    bool Compare<T>(T x, T y) where T : class
    {
        return x == y;
    }
Run Code Online (Sandbox Code Playgroud)

跟进另外一个问题,"但是,如果我使用引用类型,==运算符是否会使用预定义的引用比较,或者如果类型定义了一个,它会使用运算符的重载版本吗?"

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

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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

产量

内联:重载==调用

通用:

按任意键继续 ...

跟进2

我想指出将我的比较方法改为

    static bool Compare<T>(T x, T y) where T : Test
    {
        return x == y;
    }
Run Code Online (Sandbox Code Playgroud)

导致调用重载的==运算符.我想没有指定类型(作为where),编译器无法推断它应该使用重载运算符...虽然我认为即使没有指定类型它也会有足够的信息来做出决定.

  • 重载解析发生在编译时.因此,当我们在泛型类型`T`和`T`之间有`==`时,找到最好的重载,给定`T`带有什么约束(有一个特殊规则,它永远不会为此设置值类型(这将产生无意义的结果),因此必须有一些约束保证它是一个参考类型).在你的_Follow Up 2_中,如果你使用`DerivedTest`对象,并且`DerivedTest`派生自`Test`但是引入了`==`的新重载,你将再次遇到"问题".调用哪个重载,在编译时被"烧"到IL中. (11认同)
  • 回复:跟进2:实际上编译器会将它找到的最佳方法链接起来,在本例中是Test.op_Equal.但是如果你有一个派生自Test的类并覆盖运算符,那么仍然会调用Test的运算符. (4认同)
  • 我想指出的好习惯是你应该总是在重写的`Equals`方法中进行实际的比较(不在`==`运算符中). (4认同)
  • 奇怪的是,这似乎适用于一般引用类型(您希望这种比较是在引用相等性上),但对于字符串来说,它似乎也使用引用相等性 - 因此您最终可以比较 2 个相同的字符串并具有 == (当在具有类约束的通用方法)说它们是不同的。 (2认同)

Mar*_*ell 40

一般来说,EqualityComparer<T>.Default.Equals应该使用任何实现的工作IEquatable<T>,或者具有合理Equals实现的工作.

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

  • 等于(T值1,T值2)
  • NotEqual(T值1,T值2)
  • GreaterThan(T值1,T值2)
  • LessThan(T值1,T值2)
  • GreaterThanOrEqual(T value1,T value2)
  • LessThanOrEqual(T value1,T value2)


Ben*_*igt 26

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

.NET泛型不像C++模板那样.在C++模板中,在知道实际模板参数后会发生重载解析.

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

  • @nawfal:实际上没有,`==`不适用于所有值类型.更重要的是,它对所有类型都没有相同的含义,因此编译器不知道如何处理它. (3认同)
  • 但为什么编译器不能将它们视为通用对象?毕竟`==`适用于所有类型的引用类型或值类型.这应该是我认为你没有回答的问题. (2认同)

Joh*_*itb 12

编译不能知道T不能是struct(值类型).所以你必须告诉它它只能是我想的参考类型:

bool Compare<T>(T x, T y) where T : class { return x == y; }
Run Code Online (Sandbox Code Playgroud)

这是因为如果T可以是值类型,则可能存在x == y形成不良的情况 - 在类型没有运算符==定义的情况下.对此更为明显的是:

void CallFoo<T>(T x) { x.foo(); }
Run Code Online (Sandbox Code Playgroud)

这也失败了,因为你可以传递一个没有函数foo的类型T. C#强制您确保所有可能的类型始终具有函数foo.这是由where子句完成的.


Jon*_*jap 8

似乎没有类约束:

bool Compare<T> (T x, T y) where T: class
{
    return x == y;
}
Run Code Online (Sandbox Code Playgroud)

每个人都应该认识到,虽然class受限Equals==操作者继承Object.Equals,而一个结构覆盖的ValueType.Equals.

注意:

bool Compare<T> (T x, T y) where T: struct
{
    return x == y;
}
Run Code Online (Sandbox Code Playgroud)

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

到目前为止,我不明白为什么编译器会拒绝使用值类型相等运算符比较.我确实知道一个事实,这是有效的:

bool Compare<T> (T x, T y)
{
    return x.Equals(y);
}
Run Code Online (Sandbox Code Playgroud)

  • 第一个比较方法不*使用`Object.Equals`而是测试引用相等.例如,`Compare("0",0.ToString())`将返回false,因为参数将是对不同字符串的引用,两者都是零作为唯一字符. (2认同)

Mar*_*ell 8

您可以使用 C# 11 和 .NET 7+ 执行此操作:

    static void Main()
    {
        Console.WriteLine(Compare(2, 2));
        Console.WriteLine(Compare(2, 3));
    }
    static bool Compare<T>(T x, T y) where T : IEqualityOperators<T, T, bool>
    {
        return x == y;
    }
Run Code Online (Sandbox Code Playgroud)

(您可能更喜欢使用where T : INumber<T>,它涵盖了这种情况以及更多情况,但这取决于您的具体需求;并非所有可等式类型都是数字)


Rec*_*cep 6

这里有一个 MSDN Connect 条目

亚历克斯·特纳的回复开头是:

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


Chr*_*phe 5

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

var methodInfo = typeof (T).GetMethod("op_Equality", 
                             BindingFlags.Static | BindingFlags.Public);    
Run Code Online (Sandbox Code Playgroud)

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

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

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


U. *_*lle 5

在我的情况下,我想对相等运算符进行单元测试。我需要在相等运算符下调用代码,而无需显式设置泛型类型。建议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);
}
Run Code Online (Sandbox Code Playgroud)