ChatGPT解决这个技术问题 Extra ChatGPT

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

根据 MSDN== 运算符的文档,

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

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

bool 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); } }
再次查看我的答案以获取您后续问题的答案。
了解即使没有泛型,也有一些类型在同一类型的两个操作数之间不允许使用 == 可能很有用。这适用于不重载 operator ==struct 类型(“预定义”类型除外)。作为一个简单的例子,试试这个:var map = typeof(string).GetInterfaceMap(typeof(ICloneable)); Console.WriteLine(map == map); /* compile-time error */
继续我自己的旧评论。例如(参见 other thread),如果使用 var kvp1 = new KeyValuePair<int, int>(); var kvp2 = kvp1;,则不能检查 kvp1 == kvp2,因为 KeyValuePair<,> 是一个结构,它不是 C# 预定义类型,并且它不会重载 operator ==。然而,var li = new List<int>(); var e1 = li.GetEnumerator(); var e2 = e1; 给出了一个例子,你不能用它来做 e1 == e2(这里我们有嵌套结构 List<>.Enumerator(运行时称为 "List`1+Enumerator[T]"),它不会重载 ==)。
RE:“那么为什么这段代码无法编译?” -- 呃...因为您不能从 void 返回 bool...
@BrainSlugs83 感谢您捕获一个 10 岁的错误!

J
Jon Skeet

正如其他人所说,它仅在 T 被限制为引用类型时才有效。在没有任何约束的情况下,您可以与 null 进行比较,但只能与 null 进行比较 - 对于不可为 null 的值类型,该比较始终为 false。

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

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

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


谢谢。我试图编写一个简单的包装类,所以我只想将操作委托给实际的包装成员。但是知道 EqualityComparer.Default 确实为我增加了价值。 :)
次要的,乔恩;你可能想在我的帖子上注明关于 pobox vs yoda 的评论。
使用 EqualityComparer 的好技巧
+1 指出它可以与 null 进行比较,对于不可为 null 的值类型,它将始终为 false
@BlueRaja:是的,因为与空文字进行比较有特殊规则。因此“没有任何限制,您可以与 null 进行比较,但只能与 null 进行比较”。它已经在答案中了。那么,为什么这不正确呢?
G
Glory Raj

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

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

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


谢谢。我没有注意到该声明仅与引用类型有关。
回复:跟进2:实际上编译器会将它链接到它找到的最佳方法,在这种情况下是Test.op_Equal。但是,如果您有一个派生自 Test 并覆盖运算符的类,则仍将调用 Test 的运算符。
我想指出的一个好习惯是,您应该始终在重写的 Equals 方法中(而不是在 == 运算符中)进行实际比较。
重载解决发生在编译时。因此,当我们在泛型类型 TT 之间有 == 时,会找到最佳重载,给定 T 承载的约束(有一个特殊规则,它永远不会为此设置值类型 (这将给出一个毫无意义的结果),因此必须有一些约束来保证它是一个引用类型)。在您的 Follow Up 2 中,如果您使用 DerivedTest 对象,并且 DerivedTest 派生自 Test 但引入了新的 == 重载,您将再次遇到“问题” .调用哪个重载,在编译时被“烧录”到 IL 中。
奇怪的是,这似乎适用于一般引用类型(您希望此比较在引用相等上)但对于字符串它似乎也使用引用相等 - 因此您最终可以比较 2 个相同的字符串并具有 == (当在具有类约束的通用方法)说它们是不同的。
M
Marc Gravell

通常,EqualityComparer<T>.Default.Equals 应该使用任何实现 IEquatable<T> 或具有合理 Equals 实现的东西来完成这项工作。

但是,如果由于某种原因 ==Equals 的实现方式不同,那么我在 generic operators 上的工作应该很有用;它支持以下的 operator 版本:

相等(T 值 1,T 值 2)

不相等(T 值 1,T 值 2)

大于(T 值 1,T 值 2)

小于(T 值 1,T 值 2)

大于等于(T 值 1,T 值 2)

小于等于(T 值 1,T 值 2)


很有趣的图书馆! :) (旁注:我可以建议使用 www.yoda.arachsys.com 的链接,因为我工作场所的防火墙阻止了 pobox 吗?其他人可能面临同样的问题。)
这个想法是 pobox.com/~skeet始终指向我的网站 - 即使它移动到其他地方。为了后代,我倾向于通过 pobox.com 发布链接 - 但您可以当前替换为 yoda.arachsys.com。
pobox.com 的问题在于它是一个基于 Web 的电子邮件服务(或者公司的防火墙这么说),所以它被阻止了。这就是为什么我无法点击它的链接。
“但是,如果由于某种原因 == 和 Equals 的实现方式不同” - 天哪!然而,多么!也许我只需要看到一个相反的用例,但是具有不同等于语义的库可能会遇到比泛型问题更大的问题。
@EdwardBrey 你没有错;如果编译器可以强制执行,那就太好了,但是......
B
Ben Voigt

这么多答案,没有一个能解释为什么? (乔瓦尼明确要求)......

.NET 泛型不像 C++ 模板。在 C++ 模板中,在知道实际模板参数之后发生重载决议。

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


但是为什么编译器不能将它们视为通用对象呢?毕竟 == 适用于所有类型,无论是引用类型还是值类型。那应该是我认为你没有回答的问题。
@nawfal:实际上不,== 不适用于所有值类型。更重要的是,它对所有类型的含义并不相同,因此编译器不知道如何处理它。
Ben,哦,是的,我错过了我们可以在没有任何 == 的情况下创建的自定义结构。您能否在答案中也包含该部分,因为我想这是这里的重点
J
Johannes Schaub - litb

编译不能知道 T 不能是结构(值类型)。所以你必须告诉它它只能是我认为的参考类型:

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 子句完成的。


感谢您的澄清。我不知道值类型不支持开箱即用的 == 运算符。
Hosam,我用 gmcs(单声道)测试过,它总是比较参考。 (即它不为 T 使用可选定义的 operator==)
此解决方案有一个警告: operator== 不能重载; see this StackOverflow question
J
Jon Limjap

似乎没有类约束:

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

应该意识到,虽然 == 运算符中的 class 受约束的 Equals 继承自 Object.Equals,而 struct 的约束 ValueType.Equals 覆盖了 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);
}

你知道我是一个 C# 菜鸟。但我认为它失败了,因为编译器不知道该怎么做。因为 T 还不知道,如果允许值类型,做什么取决于类型 T。对于引用,无论 T 是什么,都只是比较引用。如果您执行 .Equals,则仅调用 .Equal。
但如果你对一个值类型执行 == ,则值类型不必实现该运算符。
这是有道理的,litb :) 用户定义的结构可能不会重载 ==,因此编译器会失败。
第一个比较方法使用 Object.Equals 而是测试引用相等性。例如,Compare("0", 0.ToString()) 将返回 false,因为参数将是对不同字符串的引用,这两个字符串的唯一字符都是零。
关于最后一个的小问题-您没有将其限制为结构,因此可能会发生 NullReferenceException
U
U. Bulle

好吧,就我而言,我想对相等运算符进行单元测试。我需要在不显式设置泛型类型的情况下调用相等运算符下的代码。 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);
}

R
Recep

here 有一个 MSDN Connect 条目

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

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


C
Christophe

如果要确保调用自定义类型的运算符,可以通过反射来实现。只需使用您的泛型参数获取类型并检索所需运算符的 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});

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


L
Leandro Caniglia

我写了以下函数查看最新的 msdn。它可以轻松比较两个对象 xy

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

您可以去掉布尔值并编写 return ((IComparable)(x)).CompareTo(y) <= 0;
如果 T 不是 IComparable 怎么办?这不会引发无效的强制转换异常吗?如果 TIComparable,为什么不将它添加到泛型类型约束中呢?
@MetaFight IComparable 是一个接口。
@Charlie 是的,我知道。我担心的是,没有任何东西可以保证 T 确实实现了该接口。如果没有,那么你会得到一个例外。如果是这样,那么它可能应该是通用约束的一部分。
s
shahkalpesh

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

以上将起作用,因为 == 在用户定义的引用类型的情况下得到处理。在值类型的情况下, == 可以被覆盖。在这种情况下,“!=”也应该被定义。

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


谢谢。我相信引用类型也可以覆盖运算符。但失败的原因现在很清楚了。
== 令牌用于两个不同的运算符。如果对于给定的操作数类型存在相等运算符的兼容重载,则将使用该重载。否则,如果两个操作数都是相互兼容的引用类型,则将使用引用比较。请注意,在上面的 Compare 方法中,编译器无法判断第一个含义适用,但可以判断第二个含义适用,因此 == 标记将使用后一个 ,即使 T 重载等式-check 运算符(例如,如果它是 String 类型)
M
Masoud Darvishian

.Equals() 对我有用,而 TKey 是通用类型。

public virtual TOutputDto GetOne(TKey id)
{
    var entity =
        _unitOfWork.BaseRepository
            .FindByCondition(x => 
                !x.IsDelete && 
                x.Id.Equals(id))
            .SingleOrDefault();


    // ...
}

那是 x.Id.Equals,而不是 id.Equals。据推测,编译器对 x 的类型有所了解。
1
1 JustOnly 1

我有 2 个解决方案,它们非常简单。

解决方案 1:将泛型类型变量转换为 object 并使用 == 运算符。

例子:

void Foo<T>(T t1, T t2)
{
   object o1 = t1;
   object o2 = t2;
   if (o1 == o2)
   {
      // ...
      // ..
      // . 
   }
}

解决方案 2:使用 object.Equals(object, object) 方法。

例子:

void Foo<T>(T t1, T t2)
{
   if (object.Equals(t1, t2)
   {
       // ...
       // ..
       // .
   }
}