Почему оператор '==' не может применяться к структуре и по умолчанию (struct)?

Я вижу некоторое нечетное поведение после использования FirstOrDefault() в коллекции структур. Я изолировал его в этом случае. Эта программа не будет компилировать

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

Ошибка компилятора является довольно загадочной:

Оператор '==' не может применяться к операндам типа "MyProgram.User" и "MyProgram.User"

Изменение структуры для класса отлично работает, но я не понимаю, почему я не могу сравнить экземпляр struct с экземпляром по умолчанию?

Ответ 1

Для классов оператор == использует ссылочное равенство. Конечно, структуры - это типы значений, поэтому их нельзя сравнивать по ссылке. Для структур нет реализации по умолчанию ==, потому что сравнение пополам не всегда является допустимым сравнением, в зависимости от типа.

Вместо этого вы можете использовать метод Object.Equals, который выполняет сравнение по порядку:

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

Или вы могли бы просто реализовать == для вызова Object.Equals:

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

Однако реализация по умолчанию Equals для структур использует отражение, и поэтому происходит очень медленно. Было бы лучше реализовать Equals самостоятельно, а также == и != (и, возможно, GetHashCode):

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

Ответ 2

Вам просто нужно его реализовать:

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

Ответ 3

В С# токен == используется для представления двух разных операторов (не все языки используют один и тот же токен для двух операторов, VB.NET использует токены = и Is). Один из операторов является перегружаемым тестом на равенство и доступен только в случаях, когда для обоих типов операндов определяется либо перегрузка, либо перегрузка определена для одного типа операнда и типа, к которому другой операнд неявно конвертируется. Другой оператор представляет собой критерий равенства ссылок и может использоваться в случаях, когда оператор проверки равенства будет непригодным, а один операнд - это тип класса, который происходит от другого, один операнд - тип класса, а другой - тип интерфейса или оба операнда являются типами интерфейсов.

Первый оператор проверки равенства не может использоваться с любым типом (классом, интерфейсом или структурой), который не предоставляет явного переопределения для него. Если токен == используется в случаях, когда первый оператор проверки равенства не используется, С# будет пытаться использовать второй оператор [обратите внимание, что другие языки, такие как VB.NET, не будут этого делать; в VB.NET попытка использовать = для сравнения двух вещей, которые не определяют перегрузку проверки равенства, будет ошибкой, даже если вещи можно было бы сравнить с помощью оператора Is]. Этот второй оператор может использоваться для сравнения любого ссылочного типа с другой ссылкой того же типа, но не может использоваться со структурами. Поскольку ни один тип оператора равенства не определен для структур, сравнение не разрешено.

Если вам интересно, почему == не просто отбрасывается на Equals(Object), который можно использовать для всех типов, причина в том, что оба операнда == подвержены типу принуждения способами, которые предотвратили бы его поведение от соответствия Equals. Например, 1.0f == 1.0 и 1.0 == 1.0f, оба передают операнд float на double, но с учетом выражения типа (1.0f).Equals(1.0) первый операнд не может быть оценен как что-либо, кроме float, Кроме того, если == были сопоставлены с Equals, тогда было бы необходимо, чтобы С# использовал другой токен для представления теста ссылочного равенства [то, что язык должен был сделать в любом случае, но, по-видимому, не хотел этого делать ].

Ответ 4

Вы можете перегрузить оператор ==, если вы хотите сделать это

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

Вы также должны переопределить Equals и GetHashCode()

Также, если вы переопределите ==, вы, вероятно, захотите также переопределить !=.

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

Ответ 5

Когда вы сравниваете два типа ссылок, вы проверяете, ссылаются ли ссылки на один и тот же тип.

Но если вы имеете дело со типами значений, ссылок на сравнение нет.

Вам нужно реализовать оператор самостоятельно и (возможно) проверить, соответствуют ли поля типа значений.