372

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

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

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

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

我收到错误Operator '==' cannot be applied to operands of type 'T' and '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); } }
4

13 回答 13

329

As others have said, it will only work when T is constrained to be a reference type. Without any constraints, you can compare with null, but only null - and that comparison will always be false for non-nullable value types.

Instead of calling Equals, it's better to use an IComparer<T> - and if you have no more information, EqualityComparer<T>.Default is a good choice:

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

Aside from anything else, this avoids boxing/casting.

于 2008-12-24T07:32:38.407 回答
152

"...by default == behaves as described above for both predefined and user-defined reference types."

Type T is not necessarily a reference type, so the compiler can't make that assumption.

However, this will compile because it is more explicit:

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

Follow up to additional question, "But, in case I'm using a reference type, would the the == operator use the predefined reference comparison, or would it use the overloaded version of the operator if a type defined one?"

I would have thought that == on the Generics would use the overloaded version, but the following test demonstrates otherwise. Interesting... I'd love to know why! If someone knows please share.

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

Output

Inline: Overloaded == called

Generic:

Press any key to continue . . .

Follow Up 2

I do want to point out that changing my compare method to

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

causes the overloaded == operator to be called. I guess without specifying the type (as a where), the compiler can't infer that it should use the overloaded operator... though I'd think that it would have enough information to make that decision even without specifying the type.

于 2008-12-24T06:44:55.227 回答
45

In general, EqualityComparer<T>.Default.Equals should do the job with anything that implements IEquatable<T>, or that has a sensible Equals implementation.

If, however, == and Equals are implemented differently for some reason, then my work on generic operators should be useful; it supports the operator versions of (among others):

  • Equal(T value1, T value2)
  • NotEqual(T value1, T value2)
  • GreaterThan(T value1, T value2)
  • LessThan(T value1, T value2)
  • GreaterThanOrEqual(T value1, T value2)
  • LessThanOrEqual(T value1, T value2)
于 2008-12-24T10:18:15.907 回答
37

So many answers, and not a single one explains the WHY? (which Giovanni explicitly asked)...

.NET generics do not act like C++ templates. In C++ templates, overload resolution occurs after the actual template parameters are known.

In .NET generics (including C#), overload resolution occurs without knowing the actual generic parameters. The only information the compiler can use to choose the function to call comes from type constraints on the generic parameters.

于 2010-12-12T20:38:04.250 回答
13

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

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

这是因为如果 T 可以是值类型,则可能x == y会出现格式错误的情况 - 在类型没有定义运算符 == 的情况下。同样的情况也会发生,这更明显:

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

That fails too, because you could pass a type T that wouldn't have a function foo. C# forces you to make sure all possible types always have a function foo. That's done by the where clause.

于 2008-12-24T06:42:44.140 回答
9

It appears that without the class constraint:

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

One should realize that while class constrained Equals in the == operator inherits from Object.Equals, while that of a struct overrides ValueType.Equals.

Note that:

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

also gives out the same compiler error.

As yet I do not understand why having a value type equality operator comparison is rejected by the compiler. I do know for a fact though, that this works:

bool Compare<T> (T x, T y)
{
    return x.Equals(y);
}
于 2008-12-24T06:58:03.343 回答
6

Well in my case I wanted to unit-test the equality operator. I needed call the code under the equality operators without explicitly setting the generic type. Advises for EqualityComparer were not helpful as EqualityComparer called Equals method but not the equality operator.

Here is how I've got this working with generic types by building a LINQ. It calls the right code for == and != operators:

/// <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);
}
于 2016-09-03T09:39:35.763 回答
4

There is an MSDN Connect entry for this here

Alex Turner's reply starts with:

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.

于 2009-08-19T15:51:59.693 回答
4

If you want to make sure the operators of your custom type are called you can do so via reflection. Just get the type using your generic parameter and retrieve the MethodInfo for the desired operator (e.g. op_Equality, op_Inequality, op_LessThan...).

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

Then execute the operator using the MethodInfo's Invoke method and pass in the objects as the parameters.

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

This will invoke your overloaded operator and not the one defined by the constraints applied on the generic parameter. Might not be practical, but could come in handy for unit testing your operators when using a generic base class that contains a couple of tests.

于 2011-02-18T14:25:36.787 回答
2

I wrote the following function looking at the latest msdn. It can easily compare two objects x and y:

static bool IsLessThan(T x, T y) 
{
    return ((IComparable)(x)).CompareTo(y) <= 0;
}
于 2013-06-20T02:03:45.813 回答
1

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

The above will work because == is taken care of in case of user-defined reference types.
In case of value types, == can be overridden. In which case, "!=" should also be defined.

I think that could be the reason, it disallows generic comparison using "==".

于 2008-12-24T06:43:28.383 回答
0

The .Equals() works for me while TKey is a generic type.

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


    // ...
}
于 2019-10-18T06:16:52.023 回答
0

I have 2 solutions and they're very simply.

Solution 1: Cast the generic typed variable to object and use == operator.

Example:

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

Solution 2: Use object.Equals(object, object) method.

Example:

void Foo<T>(T t1, T t2)
{
   if (object.Equals(t1, t2)
   {
       // ...
       // ..
       // .
   }
}
于 2021-08-20T11:10:15.697 回答