Невозможно ли применить оператор == к родовым типам в С#?
Согласно документации оператора ==
в 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); } }
Ответы
Ответ 1
"... по умолчанию == ведет себя как описано выше как для предопределенных, так и для пользовательских типов ссылок."
Тип 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);
}
}
}
Выход
Инлайн:
Перегруженный == называется
Generic:
Нажмите любую клавишу, чтобы продолжить.,.
Follow Up 2
Я хочу отметить, что изменение метода сравнения на
static bool Compare<T>(T x, T y) where T : Test
{
return x == y;
}
вызывает вызов перегруженного == оператора. Я предполагаю, что без указания типа (как где) компилятор не может сделать вывод о том, что он должен использовать перегруженный оператор... хотя я бы подумал, что у него будет достаточно информации, чтобы принять это решение даже без указания типа.
Ответ 2
Как говорили другие, он будет работать только тогда, когда T ограниченным ссылочным типом. Без каких-либо ограничений вы можете сравнить с нулевым, но только с нулевым значением, и это сравнение всегда будет ложным для типов с нулевыми значениями.
Вместо вызова Equals лучше использовать IComparer<T>
- и если у вас больше нет информации, EqualityComparer<T>.Default
- хороший выбор:
public bool Compare<T>(T x, T y)
{
return EqualityComparer<T>.Default.Equals(x, y);
}
Помимо всего прочего, это позволяет избежать бокса/кастинга.
Ответ 3
Как правило, EqualityComparer<T>.Default.Equals
должен выполнять работу со всем, что реализует IEquatable<T>
, или имеет разумную реализацию Equals
.
Если, однако, ==
и Equals
по какой-то причине выполняются по-разному, то моя работа над родовыми операторами должна быть полезна; он поддерживает версии оператора (среди прочих):
- Равно (значение T1, значение T2)
- NotEqual (T value1, T value2)
- GreaterThan (значение T1, значение T2)
- LessThan (T value1, T value2)
- GreaterThanOrEqual (значение T1, значение T2)
- LessThanOrEqual (значение T1, значение T2)
Ответ 4
Так много ответов, и ни один не объясняет ПОЧЕМУ? (о котором Джованни прямо спросил)...
Генераторы .NET не действуют как шаблоны С++. В шаблонах С++ разрешение перегрузки происходит после того, как известны фактические параметры шаблона.
В .NET generics (включая С#) разрешение перегрузки происходит, не зная фактических общих параметров. Единственная информация, которую компилятор может использовать для выбора функции для вызова, поступает из ограничений типа для общих параметров.
Ответ 5
Компиляция не может знать, что 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. С# заставляет вас убедиться, что все возможные типы всегда имеют функцию foo. Это делается с помощью предложения where.
Ответ 6
Похоже, что без ограничения класса:
bool Compare<T> (T x, T y) where T: class
{
return x == y;
}
Следует понимать, что в то время как class
constrained 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);
}
Ответ 7
Здесь есть запись MSDN Connect для здесь
Ответ Alex Turner начинается с:
К сожалению, это поведение дизайн и нелегко решение, позволяющее использовать == с типом параметры, которые могут содержать значение типы.
Ответ 8
Если вы хотите убедиться, что операторы вашего пользовательского типа вызывают, вы можете сделать это через отражение. Просто введите тип, используя свой общий параметр, и извлеките MethodInfo для желаемого оператора (например, op_Equality, op_Inequality, op_LessThan...).
var methodInfo = typeof (T).GetMethod("op_Equality",
BindingFlags.Static | BindingFlags.Public);
Затем выполните оператор, используя метод MethodInfo Invoke, и передайте объекты в качестве параметров.
var result = (bool) methodInfo.Invoke(null, new object[] { object1, object2});
Это вызовет ваш перегруженный оператор, а не тот, который определяется ограничениями, применяемыми к общему параметру. Возможно, это не практично, но может пригодиться для модульного тестирования ваших операторов при использовании базового базового класса, который содержит пару тестов.
Ответ 9
Ну, в моем случае я хотел бы выполнить единичный тест оператора равенства. Мне нужно было вызвать код под операторами равенства, явно не задав общий тип. Рекомендации для 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);
}
Ответ 10
Я написал следующую функцию, смотрящую на последнюю версию msdn. Он может легко сравнить два объекта x
и y
:
static bool IsLessThan(T x, T y)
{
return ((IComparable)(x)).CompareTo(y) <= 0;
}
Ответ 11
bool Compare(T x, T y) where T : class { return x == y; }
Вышеупомянутое будет работать, потому что == заботится в случае пользовательских ссылочных типов.
В случае типов значений == может быть переопределено. В этом случае также необходимо определить значение "! =".
Я думаю, что это может быть причиной, он запрещает общее сравнение, используя "==".
Ответ 12
Отрезанный код не может быть скомпилирован, потому что не каждый тип значения реализует оператор ==, и для них недоступно альтернативное поведение (в отличие от ссылочных типов).
Для вашего конкретного примера это скомпилирует:
bool Equals <T> ( T a , T b )
{
if( typeof(T).IsValueType )
{
return a.GetHashCode() == b.GetHashCode();
}
else
{
return (System.Object)a == (System.Object)b;
}
}
Ответ 13
Отрезанный код не может быть скомпилирован, потому что не каждый тип значения реализует оператор ==, и для них недоступно альтернативное поведение (в отличие от ссылочных типов).
Ответ 14
.Equals()
работал для меня, в то время как TOutputDto
и TKey
являются общими типами.
public virtual TOutputDto GetOne(TKey id)
{
var entity =
_unitOfWork.BaseRepository
.FindByCondition(x =>
!x.IsDelete &&
x.Id.Equals(id))
.SingleOrDefault();
// ...
}