Не может ли оператор == быть применен к универсальным типам в C#?
Согласно документации ==
оператор в MSDN,
Для предопределенных типов значений оператор равенства (==) возвращает true, если значения его операндов равны, в противном случае - false. Для ссылочных типов, отличных от string, == возвращает true, если два его операнда ссылаются на один и тот же объект. Для типа строки == сравнивает значения строк. Пользовательские типы значений могут перегружать оператор == (см. Оператор). То же самое можно сказать и о пользовательских ссылочных типах, хотя по умолчанию == ведет себя так, как описано выше, для стандартных и пользовательских ссылочных типов.
Так почему этот фрагмент кода не компилируется?
bool Compare<T>(T x, T y) { return x == y; }
Я получаю сообщение об ошибке. Оператор '==' не может быть применен к операндам типа 'T' и '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); } }
14 ответов
"... по умолчанию == ведет себя так, как описано выше для стандартных и пользовательских типов ссылок."
Тип T не обязательно является ссылочным типом, поэтому компилятор не может сделать такое предположение.
Тем не менее, это скомпилируется, потому что это более явно:
bool Compare<T>(T x, T y) where T : class
{
return x == y;
}
Ответьте на дополнительный вопрос: "Но если я использую ссылочный тип, будет ли оператор == использовать предопределенное сравнение ссылок, или он будет использовать перегруженную версию оператора, если тип определен?"
Я бы подумал, что == на Generics будет использовать перегруженную версию, но следующий тест демонстрирует обратное. Интересно... Я хотел бы знать, почему! Если кто-то знает, пожалуйста, поделитесь.
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);
}
}
}
Выход
Встроенный: перегружен == называется
Общий:
Нажмите любую клавишу для продолжения.,,
Follow Up 2
Я хочу отметить, что изменение моего метода сравнения на
static bool Compare<T>(T x, T y) where T : Test
{
return x == y;
}
вызывает перегруженный оператор ==. Я предполагаю, что без указания типа (как где) компилятор не может сделать вывод, что он должен использовать перегруженный оператор... хотя я думаю, что у него будет достаточно информации, чтобы принять это решение, даже не указав тип.
Как уже говорили другие, он будет работать только тогда, когда T ограничен ссылочным типом. Без каких-либо ограничений вы можете сравнивать со значением NULL, но только со значением NULL - и это сравнение всегда будет ложным для типов значений, не допускающих значения NULL.
Вместо вызова Equals лучше использовать IComparer<T>
- и если у вас нет больше информации, EqualityComparer<T>.Default
хороший выбор:
public bool Compare<T>(T x, T y)
{
return EqualityComparer<T>.Default.Equals(x, y);
}
Помимо всего прочего, это позволяет избежать бокса / кастинга.
В общем, EqualityComparer<T>.Default.Equals
должен делать работу со всем, что реализует IEquatable<T>
или это имеет смысл Equals
реализация.
Если, однако, ==
а также Equals
по какой-то причине реализованы по-разному, тогда моя работа над общими операторами должна быть полезной; он поддерживает операторские версии (среди прочих):
- Равен (значение T1, значение T2)
- NotEqual(значение T1, значение T2)
- GreaterThan (значение T1, значение T2)
- LessThan (значение T1, значение T2)
- GreaterThanOrEqual(значение T1, значение T2)
- LessThanOrEqual(значение T1, значение T2)
Так много ответов, и ни один не объясняет ПОЧЕМУ? (который прямо спросил Джованни)...
Обобщения.NET не действуют как шаблоны C++. В шаблонах C++ разрешение перегрузки происходит после того, как фактические параметры шаблона известны.
В универсальных.NET (в том числе C#) разрешение перегрузки происходит без знания фактических универсальных параметров. Единственная информация, которую компилятор может использовать для выбора вызываемой функции, связана с ограничениями типов общих параметров.
Компиляция не может знать, что T не может быть структурой (типом значения). Так что вы должны сказать, что это может быть только ссылочный тип, я думаю:
bool Compare<T>(T x, T y) where T : class { return x == y; }
Это потому, что если T может быть типом значения, могут быть случаи, когда x == y
будет плохо сформирован - в тех случаях, когда тип не имеет оператора ==. То же самое произойдет для этого, что более очевидно:
void CallFoo<T>(T x) { x.foo(); }
Это тоже не помогает, потому что вы можете передать тип T, у которого не будет функции foo. C# заставляет вас убедиться, что все возможные типы всегда имеют функцию foo. Это делается с помощью предложения where.
Похоже, что без ограничения класса:
bool Compare<T> (T x, T y) where T: class
{
return x == y;
}
Следует понимать, что в то время как class
ограничен Equals
в ==
оператор наследует от Object.Equals
в то время как структура переопределяет ValueType.Equals
,
Обратите внимание, что:
bool Compare<T> (T x, T y) where T: struct
{
return x == y;
}
также выдает ту же ошибку компилятора.
Пока я не понимаю, почему компилятор отвергает сравнение операторов равенства типов значений. Я точно знаю, что это работает:
bool Compare<T> (T x, T y)
{
return x.Equals(y);
}
Вы можете сделать это с помощью С# 11 и .NET 7+:
static void Main()
{
Console.WriteLine(Compare(2, 2));
Console.WriteLine(Compare(2, 3));
}
static bool Compare<T>(T x, T y) where T : IEqualityOperators<T, T, bool>
{
return x == y;
}
(вы можете предпочесть использоватьwhere T : INumber<T>
, который охватывает этот сценарий и многое другое, но это зависит от ваших конкретных потребностей; не все приравниваемые типы являются числами)
Ну, в моем случае я хотел провести модульное тестирование оператора равенства. Мне нужно было вызывать код под операторами равенства без явной установки универсального типа. Консультирует для EqualityComparer
не были полезны как EqualityComparer
называется Equals
метод, но не оператор равенства.
Вот как у меня это работает с универсальными типами путем создания LINQ
, Он вызывает правильный код для ==
а также !=
операторы:
/// <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);
}
Для этого есть запись MSDN Connect
Ответ Алекса Тернера начинается с:
К сожалению, такое поведение разработано специально, и не существует простого решения, позволяющего использовать == с параметрами типа, которые могут содержать типы значений.
Если вы хотите убедиться, что операторы вашего пользовательского типа вызываются, вы можете сделать это с помощью отражения. Просто получите тип, используя ваш универсальный параметр, и получите MethodInfo для нужного оператора (например, op_Equality, op_Inequality, op_LessThan...).
var methodInfo = typeof (T).GetMethod("op_Equality",
BindingFlags.Static | BindingFlags.Public);
Затем выполните оператор, используя метод Invoke для MethodInfo, и передайте объекты в качестве параметров.
var result = (bool) methodInfo.Invoke(null, new object[] { object1, object2});
Это вызовет ваш перегруженный оператор, а не тот, который определен ограничениями, примененными к универсальному параметру. Может быть непрактичным, но может пригодиться для модульного тестирования ваших операторов при использовании общего базового класса, который содержит пару тестов.
Я написал следующую функцию, глядя на последний MSDN. Он может легко сравнить два объекта x
а также y
:
static bool IsLessThan(T x, T y)
{
return ((IComparable)(x)).CompareTo(y) <= 0;
}
bool Compare(T x, T y) where T : class { return x == y; }
Вышесказанное будет работать, потому что == позаботится в случае пользовательских типов ссылок.
В случае типов значений == может быть переопределено. В этом случае "!=" Также должно быть определено.
Я думаю, что это может быть причиной, это запрещает общее сравнение, используя "==".
В .Equals()
работает у меня пока TKey
является универсальным типом.
public virtual TOutputDto GetOne(TKey id)
{
var entity =
_unitOfWork.BaseRepository
.FindByCondition(x =>
!x.IsDelete &&
x.Id.Equals(id))
.SingleOrDefault();
// ...
}
У меня есть 2 решения, и они очень простые.
Решение 1. Приведите общую типизированную переменную к
object
и использовать
==
оператор.
Пример:
void Foo<T>(T t1, T t2)
{
object o1 = t1;
object o2 = t2;
if (o1 == o2)
{
// ...
// ..
// .
}
}
Решение 2: используйте
object.Equals(object, object)
метод.
Пример:
void Foo<T>(T t1, T t2)
{
if (object.Equals(t1, t2)
{
// ...
// ..
// .
}
}