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

8

34

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?

Sucrase answered 15/11, 2013 at 15:43 Comment(4)
Because for classes it'll perform pointer comparison. For struct it's not defined (because it should do an expansive reflection comparison unless it has been defined by implementer). Think if the struct contains classes (it's possible). In that case how comparison should be done (just by reference? calling comparison operator for each class?). Think moreover if struct contains big data arrays (bad bad practice but it's allowed and under your responsability). What compiler should emit for that? For that reason IMO even a call to Equals() is pretty pretty inefficient.Nutpick
possible duplicate of Comparing two structs using ==Serval
Why are you using a struct instead of a class anyways? (I'm not questioning your decision, just wondering if you've thought about it and understand the differences.)Aalesund
@AdrianoRepetti: An array is a reference type. So, if the structs contain arrays, the == operator would just do a reference comparison on them.Edge
F
39

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);
}
Falcongentle answered 15/11, 2013 at 16:1 Comment(4)
Just to add to what you said, if you override Equals, you get a compiler warning that you should also override GetHashCode.Latakia
But int is a value type as well and we still use == to compare two ints.Coonskin
@Coonskin - the int struct is a primitive type, therefor equality is implemented by the compiler (which usually delegates to cpu integer comparison instructions), and does not depend on the class library to provide an operator== implementation. Even still, every struct is free to implement operator==, in which case, OP's problem goes away. The problem is just that structs have no default implementation, while classes do.Noeminoesis
What you could do and what you should do are usually two different things. The only correct because reasonable solution is to implement IEquatable<T> (and of course also override oject.Equals and object.GetHashCode and overload ==and !=) as this also fixes other behavioral issues, for example when the type is used with collections.Synagogue
A
3

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.
}
Aalesund answered 15/11, 2013 at 15:48 Comment(5)
This is a bad solution as it involves boxing and uses the default object.Equals implementation. This implementation uses reflection to get all fields of both compared types to compare their values one by one. The only reasonable solution is to implement IEquatable<T> as this also fixes other behavioral issues, for example when the type is used with collections.Synagogue
Boxing and reflection are not bad in all cases. It's perfectly fine for line of business apps where performance is not critical. (And technically it uses ValueType.Equals, not object.Equals, which uses reference equality)Aalesund
There are cleaner and better solutions that you can use carelessly. Your solution must at least provide an adequate Equals override to accept a User. Your answer should contain a hint that the presented solution can be extremely costly (boxing + reflection).ValueType.Equals is an override of object.Equals. The method we are talking about is defined by object. Hence, I was referring to it as object.Equals to highlight that ValueType does not define its own. The override provided by ValueType uses performance critical reflection to implement equality comparison (as I had mentioned).Synagogue
User is a struct, which is a ValueType, so User.Equals(User) would fall back to ValueType.Equals. I agree it uses reflection and is not as performance as one that does type-safe checking, I'm refuting your claim that it's objectively "bad" and that I must overload User.Equals. If I'm writing a line-of-business app I can simply call ValueType.Equals and move on to more valuable parts of the app. If I find that reflection is a bottleneck then I can address it. It's not always necessary to micro-optimize everything.Aalesund
This is not about micro optimizations. And the problem is solved without any relevant effort. You know that. My point here is that if those details had been mentioned in this answer, then people that come across actually have a choice. They would be enabled to make that decision that you are talking about. I was just leaving a comment here so that people that read this post get this valuable information so that they have a choice. My intention was to add value to your post. I came here only because this thread was referenced, so it's still relevant after 10 yrs.Synagogue
T
2

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

Traditor answered 15/11, 2013 at 18:15 Comment(22)
The simple reason why the equality operator is not synonymous to the object.Equals method or why it does not simply call object.Equals is that the compiler or the language needs a default definition for equality. This is reference equality. Reference equality is evaluated by the compiler. object. Equals and ReferenceEquals internally use the equality operator == to provide a default implementation.Synagogue
To change the default implementation of the virtual object.Equals method you provide an override. This override is meant to define or implement more specialized rules for the equality of a particular object. The reference equality is still provided by the == operator until the type explicitly defines an overload. This way you can separate object equality from reference equality. It all makes perfect sense. It's extremely useful that this differentiation is made.Synagogue
This answer is wrong as it assumes that the equality operator == and object.Equals are synonymous. They are meant to provide two different definitions of equality. Depending on the operator overloads, the definition of equality can be the same. This is generally true for value types but generally wrong for reference types. If it is not clear whether a type overloads the equality operator and therefore potentially eliminates the reference equality comparison, we can always fallback to ReferenceEquals.Synagogue
To avoid such unexpected behavior, the equality operator of reference types should normally not be overloaded (in that it eliminates reference equality comparison).Synagogue
== is the token for the equality operator. This operator is well defined by the language specification. == does not represent two operators. The following statement of this answer is wrong: "One of the operators is an overloadable equality test, and is only usable in cases where either an overload is defined for both operand type[...]. 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 [...]."Synagogue
== (equality operator) is defined as an equality comparison that compares the references of both operands for equality. The default definition of ==is internally used by the default implementation of the virtual object.Equals (unless overridden) and always by object.ReferenceEquals. Because reference equality basically compares two memory objects, it's obvious that both operands must be of compatible types in order to be able to point to the same memory object i.e. to be able to be equal.Synagogue
@BionicCode: I think you are misinderstanding my point. In languages better designed operators related to equality, there are syntactically different operators for reference-equality testing and semantic equality testing. In VB.NET, for example, the comparison expressions X = Y and X <> Y require that the type of X contain an explicit definition of what the operator should do, but is agnostic as to whether it is a reference or value type. The expressions X Is Y and X IsNot Y generally requires that both X and Y be reference types, and always performs non-overridable...Traditor
...reference equality testing. The behavior of the == token is consistent with the compiler trying to map it to one operator (overloadable type-specific equality) if possible, and if that fails trying to map it to a different operator (non-overridable reference equality), and in cases where the C# language spec contradicts the way things actually behave, I think it's more useful to describe things in the latter terms.Traditor
In OO languages that use a similar type/memory model like Java or C# value comparison of anonymous types imposes significant costs (it will be used by< programmers without further evaluation of the complexity of the compared types in terms of costs). Hence, by design you must explicitly define such an implementation if you want value comparison of reference or value types. I guess this helps to make such languages perform well. It's just ridiculously slow to inspect anonymous memory to yield values for comparison.Synagogue
Not speaking about whether such a comparison makes general sense (obviously not - for extremely rare exceptions, usually immutable types like string, a specialized overload is defined). I also believe that VB.NET is a bad example if you want to prove that the equality operators provided by C# are too limited. VB is an extremely/unnecessarily verbose language. It definitely helps to ramp up the learning curve as the programmer hasn't to deal with such language details. But I don't think that the value of such an operator would make the performance costs acceptable.Synagogue
If there had been a default equality-by-value comparison for value or reference types then this operator would be heavily used as nobody would expect a language primitive like an operator to behave extremely slow. To me, it just makes perfect sense.Synagogue
However, my main concern was that there is only a single equality == operator not multiple (you were talking about two operators). You wrote "In C#, the == token is used to represent two different operators" - that's definitely wrong. You can overload this operator to define a different behavior - for the same operator. Your sentence had been true when talking about for example the VB.NET = operator: "In VB.NET, the = token is used to represent two different operators, namely equality operator and assignment operator".Synagogue
You continued " One of the operators is an overloadable equality test, [...]", which implies that "the other" is not overloadable. You went on "The other operator represents a reference-equality". And that's not correct. There is a single == operator that by default compares the references of two operands. You can overload it to provide an alternative behavior e.g., compare for value equality. You continue to refer to the equality operators as two independent operators that happen to use the same token (like assignment and equality operator in VB.NET). It's not inaccurate, it's wrong.Synagogue
Even in your latest comment you refer to one being overloadable while the other isn't ("overridable"?). But you always need a base definition in order to be able to overload it. Of course, a definition cannot be overloadable if there isn't a base definition. And of course, that base definition is always overloadable otherwise there won't be an overload. Same operator two overloads. And it also seems that you were confusing overloading and overriding as you were using those terms interchangeably. But they describe two different types of member declaration.Synagogue
@BionicCode: I meant overload. And there are plenty of situations where operators are overloadable without a default definition. What does the + operator mean, for example, in the absence of a type-specific overload?Traditor
There is always a default. The C# language i.e. compiler defines e.g. a unary + (or add) operator (another example for the same token being used for two different operators, unary plus and addition). Now types must overload it but not all do it. It's the exact same situation as with the equality operator. It's defined at language/compiler level, but not all types provide an overload. Now, reference equality is semantically restrictive (there must be reference types to compare) and so are other operators. It must make sense.Synagogue
In fact, value types do have a reference, so the equality operator could technically work on value types, but by definition the reference of a value type will never be the same -->no sense, no equality operator overload, no bug potential. If you have a type where such an operator makes sense then you can overload it. For example, string is not numeric but can make sense out of the addition operator so it overloads it.Synagogue
"What does the + operator mean, for example, in the absence of a type-specific overload?" - Now it should make sense that the compiler will throw like it does when using the equality operator with a value type. Still there is the definition otherwise you won't be able to overload it. In other words, the compiler understands the operator but has no valid operation defined (except throw an exception).Synagogue
Consider a language operator to be an empty virtual method that only contains a throw statement. All operators are well defined. That's why you can overload some and other you can't. That's why you can't add custom operators. You can't define " as an operator because there is no base definition declared by the compiler.Synagogue
@BionicCode: If the C# compiler can't identify an overload for non-reference types, it will refuse compilation. If it can't identify an overload for reference types at compile time, it will generate code that tests reference equality without regard for whether an overload might exist at run time. For example, if T is a generic type parameter that's constrained to be a class type which does not override ==, applying the == operator between two storage locations of type T will compare them for reference equality, without regard for whether the specific type of T has an overload.Traditor
@BionicCode: It's also worth noting that in the specifications for the ECMA platform on which C# code runs, every definition of a value type is treated a defining two types: a value type for storage locations, and a class type for a objects which hold boxed values of that type. While the C# specification may not recognize a distinction, the behavior of interface method invocation differs. For example, given type T constrained to IDoSomething, an interface with method IDoSomething that modifies a field f of this, and declarations T x,y;, x.doSomething() will modify...Traditor
...y.f if T happens to be a class type or interface type, but won't modify y.f if T is a value type.Traditor
M
2

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.

Mihalco answered 4/3, 2019 at 4:57 Comment(1)
If the type implements IEquatable<T> using EqualityComparer<T>.Default is useless and only adds overhead (type checking). You can simply reduce your example to: bool isDefault = value.Equals(default).Synagogue
B
0

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)
   }
Breathe answered 15/11, 2013 at 15:48 Comment(0)
C
0

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.

Cottrell answered 15/11, 2013 at 15:48 Comment(0)
S
0

It's highly recommended to not simply add specialized Equals overloads to your value type.
Always implement IEquatable<T>.

Implementing IEquatable<T> also enables other behavioral improvements as this interface is heavily used by other .NET types like generic collections.
For example, methods like Contains, Remove or IndexOf all rely on IEquatable<T>. Same applies to EqualityComparer<T>.Default references in general (EqualityComparer<T>.Default is also internally used in collections).
If you want your value type to behave properly when used in a common manner (e.g., in collections and tables like Dictionayr<K, V>) you must implement IEquatable<T>.


The default equality operator definition checks for reference equality (compiler level). This means if the instances are the same, or in other words if two variables/operands point to the same memory object, the == operator returns true.

Because value types are always copied by value, the variable will never point to the same memory object (as copying by value means the creatin of a new memory object with a copy of each value of the old memory object).
Equality of a value type is therefore defined by comparing the values of an instance (value equality).
In this context it should be noted that it is not recommended to use object.Equals to compare for equality.
Although ValueType (the base class of e.g., struct or enum) overrides object.Equals to perform a basic value comparison for the passed in value type, object.Equals performs very bad:

  • because the parameter is defined as being of type object there incurs the cost of boxing (conversion of a value type to a reference type).
  • because the equality comparison is as general as possible, the override must use reflection to get all fields - of both participating value types (the current and the instance to compare to) to compare their values one by one.

There is no default equality operator overload for value types. Such an overload must be very slow as it had to use reflection to get all field values of a type for equality comparison (like the override of object.Equals provided by ValueType does). Given the frequency of how often the equality operator is used it makes perfect sense to let each value type define its own specialized equality comparison. This eliminates boxing and the use of reflection.

For this reason, it is recommended best practice to always let your value type implement IEquatable<T> if it qualifies for equality comparison.
This solution also avoids the expensive boxing conversion as the IEquatable<T>.Equals is now able to accept the concrete value type as argument.

When implementing IEquatable<T> it's also best practice to override object.Equals, object.GetHashCode (those overrides always go in tandem) and to overload the equality and inequality operators.
This way consistent behavior is guaranteed across the API.
Note: in the wake of overriding the default object behavior it's also best practice to override object.ToString.

An example implementation of IEquatable<T> to enable all equality comparison features could look as follows:

User.cs

public readonly struct User : IEquatable<User>
{
  public Guid UserGuid { get; }
  public string Username { get; }

  public User(Guid userGuid, string username)
  {
    this.UserGuid = userGuid;
    this.Username = username;
  }

  #region IEquatable<User> implementation

  // Use ValueTuple to simplify the equality comparison implementation
  public bool Equals(User other)
    => (other.UserGuid, other.Username) == (this.UserGuid, this. Username);

  #endregion IEquatable<User> implementation
  
  #region object overrides

  public override bool Equals(object obj)
    => obj is User other && Equals(other);

  public override int GetHashCode()
    => HashCode.Combine(this.UserGuid, this. Username);

  // For the sake of completeness
  public override string ToString()
    => $"User: {this. Username} (ID: {this.UserGuid})";

  #endregion object overrides

  #region Operator overloads

  public static bool operator ==(User left, User right)
    => left.Equals(right);

  public static bool operator !=(User left, User right)
    => !(left == right);

  #endregion Operator overloads
}

Usage example

User user = new User();

// Using the equality operator
bool isDefault = user == default;

// Using the IEquatable.Equals method
bool isDefault = user.Equals(default);
Synagogue answered 27/1 at 13:53 Comment(0)
P
0

Per this Equality of classes and structs in C#

In either case, and in both classes and structs, your implementation should follow the five guarantees of equivalence (for the following rules, assume that x, y and z are not null):

  1. The reflexive property: x.Equals(x) returns true.

  2. The symmetric property: x.Equals(y) returns the same value as y.Equals(x).

  3. The transitive property: if (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true.

  4. Successive invocations of x.Equals(y) return the same value as long as the objects referenced by x and y aren't modified.

  5. Any non-null value isn't equal to null. However, x.Equals(y) throws an exception when x is null. That breaks rules 1 or 2, depending on the argument to Equals.

Any struct that you define already has a default implementation of value equality that it inherits from the System.ValueType override of the Object.Equals(Object) method. This implementation uses reflection to examine all the fields and properties in the type. Although this implementation produces correct results, it is relatively slow compared to a custom implementation that you write specifically for the type.

The implementation details for value equality are different for classes and structs. However, both classes and structs require the same basic steps for implementing equality:

  1. Override the virtual Object.Equals(Object) method. In most cases, your implementation of bool Equals( object obj ) should just call into the type-specific Equals method that is the implementation of the System.IEquatable interface. (See step 2.)
  2. Implement the System.IEquatable interface by providing a type-specific Equals method. This is where the actual equivalence comparison is performed. For example, you might decide to define equality by comparing only one or two fields in your type. Don't throw exceptions from Equals. For classes that are related by inheritance:
  • This method should examine only fields that are declared in the class. It should call base.Equals to examine fields that are in the base class. (Don't call base.Equals if the type inherits directly from Object, because the Object implementation of Object.Equals(Object) performs a reference equality check.)
  • Two variables should be deemed equal only if the run-time types of the variables being compared are the same. Also, make sure that the IEquatable implementation of the Equals method for the run-time type is used if the run-time and compile-time types of a variable are different. One strategy for making sure run-time types are always compared correctly is to implement IEquatable only in sealed classes. For more information, see the class example later in this article.
  1. Optional but recommended: Overload the == and != operators.
  2. Override Object.GetHashCode so that two objects that have value equality produce the same hash code.
  3. Optional: To support definitions for "greater than" or "less than," implement the IComparable interface for your type, and also overload the <= and >= operators.
Prairial answered 18/6 at 20:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.