Can't operator == be applied to generic types in C#?

125,250

Solution 1

"...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.

Solution 2

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.

Solution 3

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)

Solution 4

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.

Solution 5

The compile can't know T couldn't be a struct (value type). So you have to tell it it can only be of reference type i think:

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

It's because if T could be a value type, there could be cases where x == y would be ill formed - in cases when a type doesn't have an operator == defined. The same will happen for this which is more obvious:

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.

Share:
125,250
Hosam Aly
Author by

Hosam Aly

I'm interested in developing top quality software, optimizing performance, refactoring code, and learning about core computer science topics. For more information about me, please check my LinkedIn profile. I'm currently available for contracts, preferably using Scala or Ruby (outside finance, media, fashion, and advertising).

Updated on August 21, 2021

Comments

  • Hosam Aly
    Hosam Aly about 2 years

    According to the documentation of the == operator in 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.

    So why does this code snippet fail to compile?

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

    I get the error Operator '==' cannot be applied to operands of type 'T' and 'T'. I wonder why, since as far as I understand the == operator is predefined for all types?

    Edit: Thanks, everybody. I didn't notice at first that the statement was about reference types only. I also thought that bit-by-bit comparison is provided for all value types, which I now know is not correct.

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

    Edit 2: Through trial and error, we learned that the == operator will use the predefined reference comparison when using an unrestricted generic type. Actually, the compiler will use the best method it can find for the restricted type argument, but will look no further. For example, the code below will always print true, even when Test.test<B>(new B(), new B()) is called:

    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); } }
    
  • Johannes Schaub - litb
    Johannes Schaub - litb almost 15 years
    u know im a total c# noob. but i think it fails because the compiler doesn't know what to do. since T isn't known yet, what is done depends on the type T if value types would be allowed. for references, the references are just compared regardless of T. if you do .Equals, then .Equal is just called.
  • Johannes Schaub - litb
    Johannes Schaub - litb almost 15 years
    but if you do == on a value type, the value type doesn't have to necassary implement that operator.
  • Jon Limjap
    Jon Limjap almost 15 years
    That'd make sense, litb :) It is possible that user-defined structs do not overload ==, hence the compiler fail.
  • Hosam Aly
    Hosam Aly almost 15 years
    Thanks for the clarification. I didn't know that value types did not support the == operator out of the box.
  • Hosam Aly
    Hosam Aly almost 15 years
    Thanks. I didn't notice that the statement was about reference types only.
  • Hosam Aly
    Hosam Aly almost 15 years
    Thanks. I believe reference types can also override the operator too. But the failure reason is now clear.
  • Johannes Schaub - litb
    Johannes Schaub - litb almost 15 years
    Hosam, i tested with gmcs (mono), and it always compares references. (i.e it doesn't use an optionally defined operator== for T)
  • Hosam Aly
    Hosam Aly almost 15 years
    Very interesting library! :) (Side note: May I suggest using the link to www.yoda.arachsys.com, because the pobox one was blocked by the firewall in my workplace? It's possible that others may face the same issue.)
  • Hosam Aly
    Hosam Aly almost 15 years
    Re: Follow Up 2: Actually the compiler will link it the best method it finds, which is in this case Test.op_Equal. But if you had a class that derives from Test and overrides the operator, then Test's operator will still be called.
  • Hosam Aly
    Hosam Aly almost 15 years
    The problem with pobox.com is that it's a web-based e-mail service (or so the company's firewall says), so it is blocked. That's why I couldn't follow its link.
  • jpbochi
    jpbochi about 14 years
    I good practice that I would like to point out is that you should always do the actual comparison inside an overridden Equals method (not in the == operator).
  • Popplars
    Popplars over 13 years
    There is one caveat with this solution: the operator== cannot be overloaded; see this StackOverflow question.
  • chakrit
    chakrit about 12 years
    Nice tip on using EqualityComparer<T>
  • Jalal Said
    Jalal Said over 11 years
    +1 for pointing out that it can compare to null and for non nullable value type it will be always false
  • Jeppe Stig Nielsen
    Jeppe Stig Nielsen almost 11 years
    Overload resolution happens compile-time. So when we have == between generic types T and T, the best overload is found, given what constraints are carried by T (there's a special rule that it will never box a value-type for this (which would give a meaningless result), hence there must be some constraint guaranteeing it's a reference type). In your Follow Up 2, if you come in with DerivedTest objects, and DerivedTest derives from Test but introduces a new overload of ==, you will have the "problem" again. Which overload is called, is "burned" into the IL at compile-time.
  • nawfal
    nawfal almost 11 years
    but why can't compiler treat them as a generic object? after all == works for all types be it reference types or value types. That should be the question to which I dont think you answered.
  • Ben Voigt
    Ben Voigt almost 11 years
    @nawfal: Actually no, == doesn't work for all value types. More importantly, it doesn't have the same meaning for all types, so the compiler doesn't know what to do with it.
  • nawfal
    nawfal almost 11 years
    Ben, oh yes I missed the custom structs we can create without any ==. Can you include that part too in your answer as I guess that's the main point here
  • JonnyRaa
    JonnyRaa over 10 years
    wierdly this seems to work for general reference types (where you would expect this comparison to be on reference equality) but for strings it seems to also use reference equality - so you can end up comparing 2 identical strings and having == (when in a generic method with the class constraint) say they are different.
  • supercat
    supercat over 10 years
    The == token is used for two different operators. If for the given operand types there exists a compatible overload of the equality operator, that overload will be used. Otherwise if both operands are reference types that are compatible with each other, a reference comparison will be used. Note that in the Compare method above the compiler can't tell that the first meaning applies, but can tell the second meaning applies, so the == token will use the latter even if T overloads the equality-check operator (e.g. if it's type String).
  • supercat
    supercat over 10 years
    The first compare method does not use Object.Equals but instead tests reference equality. For example, Compare("0", 0.ToString()) would return false, since the arguments would be references to distinct strings, both of which have a zero as their only character.
  • SamuelDavis
    SamuelDavis over 10 years
    Ahh I just had this problem. Didn't understand why the == wasn't working. Thanks for the help!
  • Jon Skeet
    Jon Skeet about 8 years
    @BlueRaja: Yes, because there are special rules for comparisons with the null literal. Hence "without any constraints, you can compare with null, but only null". It's in the answer already. So, why exactly can this not be correct?
  • aloisdg
    aloisdg over 7 years
    You can get rid of your booleans and write return ((IComparable)(x)).CompareTo(y) <= 0;
  • Flynn1179
    Flynn1179 over 6 years
    Minor gotcha on that last one- you haven't restricted it to structs, so a NullReferenceException could happen.
  • Edward Brey
    Edward Brey over 6 years
    "If, however, == and Equals are implemented differently for some reason" - Holy smokes! What a however! Maybe I just need to see a use case to the contrary, but a library with divergent equals semantics will likely run into bigger problems than trouble with generics.
  • Marc Gravell
    Marc Gravell over 6 years
    @EdwardBrey you're not wrong; it would be nice if the compiler could enforce that, but...
  • BrainSlugs83
    BrainSlugs83 over 5 years
    It's because the signature doesn't match -- your constraint is for class, which effectively means that, at compile time, these are both instances of type object -- your == operator requires that both operands be of type Test, so, that operator won't be called due to a signature mismatch (remember, method and operator signature matching happens at compile time!) -- Same thing would happen if you boxed an instance of the Test class, btw. (i.e. Test a = new Test(); object b = new Test(); Console.WriteLine(a == b); will always return false.)
  • Hosam Aly
    Hosam Aly about 4 years
    That's x.Id.Equals, not id.Equals. Presumably, the compiler knows something about the type of x.
  • Matt
    Matt almost 3 years
    Great answer, it also answers a question I had (12 years after you posted your answer ;-)) - I needed a generic function to check if a variable contains a default value. It was easy to write one returning a default value: T GetDefault<T>() => (T)default; But I couldn't write a function IsDefault<T>(value) without restricting it to IComparable which didn't work for all types. With your answer it is now simple: bool IsDefault<T>(T value) => Compare((T)default, value); is the answer to my question. Thanks, Jon!
  • MetaFight
    MetaFight about 2 years
    What if T isn't an IComparable? Won't this throw an invalid cast exception? And if T is an IComparable why not just add that to the generic type constraint?
  • Charlie
    Charlie about 2 years
    @MetaFight IComparable is an interface.
  • MetaFight
    MetaFight about 2 years
    @Charlie yes, I'm aware. My concern is that nothing guarantees T actually implements that interface. If it doesn't, then you get an exception. If it does, then it should probably be part of the generic constraint.