Не может ли оператор == быть применен к универсальным типам в C #? - PullRequest
298 голосов
/ 24 декабря 2008

Согласно документации оператора == в MSDN ,

Для предопределенных типов значений Оператор равенства (==) возвращает true, если значения его операндов равны, ложь в противном случае. Для справочных типов кроме строки, == возвращает 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); } }

Ответы [ 11 ]

277 голосов
/ 24 декабря 2008

Как уже говорили другие, он будет работать только тогда, когда 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);
}

Помимо всего прочего, это позволяет избежать бокса / кастинга.

130 голосов
/ 24 декабря 2008

"... по умолчанию == ведет себя так, как описано выше как для предопределенных, так и для пользовательских типов ссылок."

Тип 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 2

Я хочу отметить, что изменение моего метода сравнения на

    static bool Compare<T>(T x, T y) where T : Test
    {
        return x == y;
    }

вызывает перегруженный оператор ==. Я предполагаю, что без указания типа (например, , где ) компилятор не может сделать вывод, что он должен использовать перегруженный оператор ... хотя я думаю, что у него будет достаточно информации, чтобы принять это решение даже без указания типа.

39 голосов
/ 24 декабря 2008

Как правило, 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)
26 голосов
/ 12 декабря 2010

Так много ответов, и ни один не объясняет ПОЧЕМУ? (который прямо спросил Джованни) ...

.NET дженерики не действуют как шаблоны C ++. В шаблонах C ++ разрешение перегрузки происходит после того, как фактические параметры шаблона известны.

В обобщениях .NET (включая C #) разрешение перегрузки происходит без знания фактических обобщенных параметров. Единственная информация, которую компилятор может использовать для выбора вызываемой функции, связана с ограничениями типов общих параметров.

12 голосов
/ 24 декабря 2008

Компиляция не может знать, что 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.

8 голосов
/ 24 декабря 2008

Похоже, что без ограничения класса:

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);
}
4 голосов
/ 03 сентября 2016

Ну, в моем случае я хотел провести модульное тестирование оператора равенства. Мне нужно было вызывать код под операторами равенства без явной установки универсального типа. Советы для 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);
}
4 голосов
/ 18 февраля 2011

Если вы хотите убедиться, что операторы вашего пользовательского типа вызываются, вы можете сделать это с помощью отражения. Просто получите тип, используя ваш универсальный параметр, и получите 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});

Это вызовет ваш перегруженный оператор, а не тот, который определен ограничениями, примененными к универсальному параметру. Может быть непрактичным, но может пригодиться для модульного тестирования ваших операторов при использовании базового базового класса, который содержит пару тестов.

4 голосов
/ 19 августа 2009

Для этого есть запись MSDN Connect

Ответ Алекса Тернера начинается с:

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

2 голосов
/ 20 июня 2013

Я написал следующую функцию, глядя на последнюю версию msdn. Он может легко сравнить два объекта x и y:

static bool IsLessThan(T x, T y) 
{
    return ((IComparable)(x)).CompareTo(y) <= 0;
}
...