Согласно документации оператора == в MSDN,
For predefined value types, the equality operator (==) returns true if the values of its operands are equal, false otherwise. For reference types other than string, == returns true if its two operands refer to the same object. For the string type, == compares the values of the strings. User-defined value types can overload the == operator (see operator). So can user-defined reference types, although by default == behaves as described above for both predefined and user-defined reference types.
Так почему же этот фрагмент кода не компилируется?
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); } }
Было бы полезно понять, что даже без универсальных типов есть некоторые типы, для которых == не допускается между двумя операндами одного и того же типа. Это верно для типов struct (кроме «предопределенных» типов), которые не перегружают operator ==. В качестве простого примера попробуйте следующее: var map = typeof(string).GetInterfaceMap(typeof(ICloneable)); Console.WriteLine(map == map); /* compile-time error */
Продолжая свой старый комментарий. Например (см. другой поток), с var kvp1 = new KeyValuePair<int, int>(); var kvp2 = kvp1; вы не можете проверить kvp1 == kvp2, потому что KeyValuePair<,> - это структура, это не предопределенный тип C#, и он не перегружает operator ==. Тем не менее, var li = new List<int>(); var e1 = li.GetEnumerator(); var e2 = e1; дает пример, с которым вы не можете использовать e1 == e2 (здесь у нас есть вложенная структура List<>.Enumerator (называемая средой выполнения "List`1+Enumerator[T]"), которая не перегружает ==).
RE: «Так почему этот фрагмент кода не компилируется?» - Э ... потому что вы не можете вернуть bool из void ...
@ BrainSlugs83 Спасибо, что обнаружили ошибку 10-летней давности!
Не менее ценный почти дубликат (так что не голосование за закрытие): Сравнение универсального аргумента в 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.
Благодарю за разъяснение. Я не знал, что типы значений не поддерживают оператор == из коробки.
Hosam, я тестировал с gmcs (моно), и он всегда сравнивает ссылки. (т.е. он не использует необязательно определенный оператор == для T)
У этого решения есть одна оговорка: оператор == не может быть перегружен; см. этот вопрос StackOverflow.
bool Compare(T x, T y) where T : class { return x == y; }
Вышеупомянутое будет работать, потому что == учитывается в случае определенных пользователем ссылочных типов. В случае типов значений == можно переопределить. В этом случае также следует определить "! = ".
Я думаю, это может быть причиной того, что он запрещает общее сравнение с использованием «==».
Спасибо. Я считаю, что ссылочные типы также могут переопределять оператор. Но причина неудачи теперь ясна.
Токен == используется двумя разными операторами. Если для данных типов операндов существует совместимая перегрузка оператора равенства, будет использоваться эта перегрузка. В противном случае, если оба операнда являются ссылочными типами, совместимыми друг с другом, будет использоваться ссылочное сравнение. Обратите внимание, что в приведенном выше методе Compare компилятор не может сказать, что применимо первое значение, но может сказать, что применимо второе значение, поэтому токен == будет использовать последнее значение даже если T перегружает оператор проверки равенства (например, если он типа String).
«... по умолчанию == ведет себя, как описано выше, как для предопределенных, так и для определенных пользователем ссылочных типов».
Тип 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);
}
}
}
Выход
В соответствии: Перегружен == называется
Общий:
Нажмите любую клавишу для продолжения . . .
Продолжение 2
Я хочу отметить, что изменение моего метода сравнения на
static bool Compare<T>(T x, T y) where T : Test
{
return x == y;
}
вызывает вызов перегруженного оператора ==. Я предполагаю, что без указания типа (как где) компилятор не может сделать вывод, что он должен использовать перегруженный оператор ... хотя я бы подумал, что у него будет достаточно информации, чтобы принять это решение, даже без указания типа.
Спасибо. Я не заметил, что утверждение касалось только ссылочных типов.
Re: Follow Up 2: На самом деле компилятор свяжет его с лучшим методом, который он найдет, в данном случае Test.op_Equal. Но если у вас был класс, производный от Test и переопределяющий оператор, то оператор Test все равно будет вызываться.
Хорошая практика, на которую я хотел бы обратить внимание, заключается в том, что вы всегда должны проводить фактическое сравнение внутри переопределенного метода Equals (а не в операторе ==).
Разрешение перегрузки происходит во время компиляции. Итак, когда у нас есть == между универсальными типами T и T, будет найдена лучшая перегрузка, учитывая, какие ограничения несет T (есть специальное правило, что он никогда не будет упаковывать тип значения для этого (что даст бессмысленный результат), следовательно, должно быть какое-то ограничение, гарантирующее, что это ссылочный тип). В вашем Продолжение 2, если вы входите с объектами DerivedTest, а DerivedTest является производным от Test, но вводит новую перегрузку ==, у вас снова будет «проблема». Какая перегрузка вызывается, «записывается» в IL во время компиляции.
Как ни странно, это, похоже, работает для общих ссылочных типов (где вы ожидаете, что это сравнение будет на ссылочном равенстве), но для строк, похоже, также используется ссылочное равенство - так что вы можете в конечном итоге сравнить 2 идентичные строки и иметь == (когда в универсальный метод с ограничением класса) говорят, что они разные.
Ах, у меня просто была эта проблема. Не понимаю, почему == не работает. Спасибо за помощь!
Это потому, что подпись не совпадает - ваше ограничение для class, что фактически означает, что во время компиляции это оба экземпляра типа object - ваш оператор == требует, чтобы оба операнда были типа Test, поэтому этот оператор не будет вызываться из-за несоответствия сигнатуры (помните, что соответствие сигнатуры метода и оператора происходит во время компиляции!) - То же самое произойдет, если вы поместите в коробку экземпляр класса Test, кстати. (т.е. Test a = new Test(); object b = new Test(); Console.WriteLine(a == b); всегда будет возвращать false.)
Похоже, что без ограничения класса:
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);
}
Ты знаешь, что я полный C# noob. но я думаю, что это не удается, потому что компилятор не знает, что делать. поскольку T еще не известен, то, что будет сделано, зависит от типа T, если типы значений будут разрешены. для ссылок ссылки просто сравниваются независимо от T. если вы используете .Equals, то просто вызывается .Equal.
но если вы используете == для типа значения, тип значения не обязательно должен реализовывать этот оператор.
В этом есть смысл, litb :) Возможно, пользовательские структуры не перегружают ==, поэтому компилятор не работает.
Первый метод сравнения использует нетObject.Equals, но вместо этого проверяет ссылочное равенство. Например, Compare("0", 0.ToString()) вернет false, поскольку аргументы будут ссылками на отдельные строки, единственными символами которых являются ноль.
Незначительная ошибка с последним - вы не ограничили его структурами, поэтому может произойти NullReferenceException.
Как говорили другие, это будет работать только тогда, когда 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, безусловно, добавило мне ценности. :)
Не считая второстепенного, Джон; Вы можете отметить комментарий re pobox vs yoda к моему посту.
Хороший совет по использованию EqualityComparer <T>
+1 за указание на то, что он может сравниваться с нулевым значением, а для типа значения, не допускающего значения NULL, он всегда будет ложным
@BlueRaja: Да, потому что существуют особые правила сравнения с нулевым литералом. Следовательно, «без каких-либо ограничений вы можете сравнивать с null, но только с null». Это уже в ответе. Итак, почему именно это не может быть правильным?
Отличный ответ, он также отвечает на мой вопрос (через 12 лет после того, как вы опубликовали свой ответ ;-)) - мне нужна была общая функция, чтобы проверить, содержит ли переменная значение по умолчанию. Было легко написать функцию, возвращающую значение по умолчанию: T GetDefault<T>() => (T)default;. Но я не мог написать функцию IsDefault<T>(value), не ограничивая ее только IComparable, которая работает не для всех типов. С вашим ответом теперь все просто: bool IsDefault<T>(T value) => Compare((T)default, value); - это ответ на мой вопрос. Спасибо, Джон!
В общем, EqualityComparer<T>.Default.Equals должен работать со всем, что реализует IEquatable<T> или имеет разумную реализацию Equals.
Если, однако, == и Equals по какой-то причине реализованы по-разному, моя работа над общие операторы должна быть полезной; он поддерживает версии оператор (среди прочего):
Очень интересная библиотека! :) (Примечание: могу ли я предложить использовать ссылку на www.yoda.arachsys.com, потому что первый почтовый ящик был заблокирован брандмауэром на моем рабочем месте? Возможно, другие могут столкнуться с той же проблемой.)
Идея состоит в том, что pobox.com/~skeet будет указывать на мой веб-сайт всегда, даже если он переместится в другое место. Я предпочитаю размещать ссылки через pobox.com ради потомства, но вместо этого вы можете заменить В настоящее время yoda.arachsys.com.
Проблема с pobox.com в том, что это веб-служба электронной почты (по крайней мере, так сказано в брандмауэре компании), поэтому она заблокирована. Вот почему я не мог перейти по его ссылке.
«Если же == и Equals по какой-то причине реализованы по-разному» - Святой дым! Какая же! Возможно, мне просто нужно увидеть противоположный вариант использования, но библиотека с расходящейся семантикой равенства, вероятно, столкнется с более серьезными проблемами, чем проблемы с дженериками.
@EdwardBrey, ты не ошибся; было бы неплохо, если бы компилятор мог это обеспечить, но ...
Для этого здесь есть запись MSDN Connect
Ответ Алекса Тернера начинается с:
Unfortunately, this behavior is by design and there is not an easy solution to enable use of == with type parameters that may contain value types.
Так много ответов, и ни один не объясняет ПОЧЕМУ? (что Джованни недвусмысленно спросил) ...
Дженерики .NET не действуют как шаблоны C++. В шаблонах C++ разрешение перегрузки происходит после того, как известны фактические параметры шаблона.
В универсальных шаблонах .NET (включая C#) разрешение перегрузки происходит без знания фактических универсальных параметров. Единственная информация, которую компилятор может использовать для выбора функции для вызова, исходит из ограничений типа для общих параметров.
но почему компилятор не может рассматривать их как универсальный объект? в конце концов, == работает для всех типов, будь то ссылочные типы или типы значений. Думаю, вы не ответили на этот вопрос.
@nawfal: На самом деле нет, == работает не для всех типов значений. Что еще более важно, он не имеет одинакового значения для всех типов, поэтому компилятор не знает, что с ним делать.
Бен, о да, я пропустил пользовательские структуры, которые мы можем создать без какого-либо ==. Можете ли вы включить и эту часть в свой ответ, поскольку я думаю, что это главный вопрос здесь
Если вы хотите убедиться, что операторы вашего настраиваемого типа вызываются, вы можете сделать это через отражение. Просто получите тип, используя свой общий параметр, и получите 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;
}
Вы можете избавиться от логических значений и написать return ((IComparable)(x)).CompareTo(y) <= 0;
В моем случае я хотел провести модульное тестирование оператора равенства. Мне нужно было вызвать код под операторами равенства без явной установки универсального типа. Советы для 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);
}
.Equals() мне подходит, а TKey - универсальный.
public virtual TOutputDto GetOne(TKey id)
{
var entity =
_unitOfWork.BaseRepository
.FindByCondition(x =>
!x.IsDelete &&
x.Id.Equals(id))
.SingleOrDefault();
// ...
}
Это x.Id.Equals, а не id.Equals. Предположительно, компилятор что-то знает о типе x.
См. Мой ответ еще раз, чтобы получить ответ на следующий вопрос.