简体   繁体   中英

Why can't the operator '==' be applied to a struct and default(struct)?

I'm seeing some odd behaviour after using FirstOrDefault() on a collection of structs. I've isolated it into this reproduction case. This program won't compile

using System;
using System.Linq;

namespace MyProgram {
    public class Program {
        static void Main() {

            var users = new User[] {
            new User() { UserGuid = Guid.NewGuid(), Username = "user01" },
            new User() { UserGuid = Guid.NewGuid(), Username = "user02" }
        };

            var user = users.FirstOrDefault(u => u.Username == "user01");
            Console.WriteLine(user == default(User) ? "not found" : "found");
        }

    }

    public struct User {
        public Guid UserGuid;
        public string Username;
    }
}

The compiler error is the rather cryptic:

Operator '==' cannot be applied to operands of type 'MyProgram.User' and 'MyProgram.User'

Changing the struct to a class works fine - but I'm at a loss as to why I can't compare a struct 'instance' to a default?

For classes, the == operator uses reference equality. Of course, structs are value types, so they can't be compared by reference. There is no default implementation of == for structs because memberwise comparison isn't always a valid comparison, depending on the type.

You can instead use the Object.Equals method, which does compare memberwise:

Console.WriteLine(user.Equals(default(User)) ? "not found" : "found");

Or you could just implement == to call Object.Equals :

public static bool operator ==(User lhs, User rhs)
{
    return lhs.Equals(rhs);
}

However, the default implementation of Equals for structs uses reflection, and so is very slow. It would be better to implement Equals yourself, along with == and != (and possibly GetHashCode too):

public override bool Equals(Object obj)
{
    return obj is User && Equals((User)obj);
}

public bool Equals(User other)
{
    return UserGuid == other.UserGuid && Username == other.Username;
}

public static bool operator ==(User lhs, User rhs)
{
    return lhs.Equals(rhs);
}

public static bool operator !=(User lhs, User rhs)
{
    return !lhs.Equals(rhs);
}

You just have to implement it:

public static bool operator == (User u1, User u2) 
{
   return u1.Equals(u2);  // use ValueType.Equals() which compares field-by-field.
}

In C#, the == token is used to represent two different operators (not all languages use the same token for the two operators; VB.NET uses the tokens = and Is ). One of the operators is an overloadable equality test, and is only usable in cases where either an overload is defined for both operand types, or an overload is defined for one operand type and a type to which the other operand is implicitly convertible. The other operator represents a reference-equality test, and is usable in cases where the equality-test operator would be unusable, and where one operand is a class type which derives from the other, one operand is a class type and the other is an interface type, or both operands are interface types.

The first equality-test operator cannot be used with any type (class, interface, or struct) that does not provide an explicit override for it. If the == token is used in cases where the first equality-test operator is not usable, however, C# will try to use the second operator [note that other languages like VB.NET would not do this; in VB.NET, an attempt to to use = to compare two things that don't define an equality-test overload will be an error, even if the things could be compared using the Is operator]. That second operator may be used to compare any reference type to another reference of the same type, but is not usable with structures. Since neither type of equality operator is defined for structures, the comparison is disallowed.

If one is wondering why == doesn't simply fall back upon Equals(Object) , which is usable with all types, the reason is that both operands of == are subject to type coercion in ways that would prevent its behavior from matching Equals . For example, 1.0f==1.0, and 1.0==1.0f, both cast the float operand to double , but given an expression like (1.0f).Equals(1.0) the first operand can't be evaluated as anything but float . Further, if == were mapped to Equals , then it would have been necessary for C# to use a different token to represent a reference-equality test [something the language should have done anyway, but apparently didn't want to do].

You can overload the == operator if you want to do this

public static bool operator ==(User u1, User u2) 
   {
        return u1.Equals(u2)
   }

You should also override the Equals and GetHashCode()

Also if you override the == , you will probably want to override != as well.

public static bool operator !=(User u1, User u2) 
   {
        return !u1.Equals(u2)
   }

When you compare two reference types, you're checking whether the references point to the same type.

But if you're dealing with value types, there are no references to compare.

You have to implement the operator yourself, and (probably) check if the value type's fields match.

Use:

public static bool IsDefault<TValue>(TValue value) => 
   EqualityComparer<TValue>.Default.Equals(value, default(TValue));

or in C# 7.1+:

public static bool IsDefault<TValue>(TValue value) =>
   EqualityComparer<TValue>.Default.Equals(value, default);

and consider implementing IEquatable<T> .

Explanation
EquityComparer<T>.Default first attempts to use the IEquatable<T> interface gradually whittling down to object.Equals . This both resolves the compiler issue as well as avoid costly reflection based struct member comparisons in cases where IEquatable<T> is implemented.

Caution
Avoid implementing an == operator with the default object.Equals method, because under the hood it will use reflection as well as box your instance depending on how its called. When using EqualityComparer<T>.Default.Equals make sure your struct implements IEquatable<T> otherwise this approach will also result in reflection under the hood.

Details
The == operator is implemented for objects and .Net types, so a custom struct will not have a default == operator implementation.

As a result of this nuance, when compiling generic equality tests, such as:

bool IsDefault<TValue> : where TValue : struct => value == default(TValue)

the compiler cannot determine the IL instruction to generate because the correct equality operator implementation cannot be determined until the generic type is resolved; however, in C# generics are resolved at run-time. So even when you do implement the == operator for a custom struct, you might still run into the issue when generics are involved.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM