Ограничение интерфейса для IComparable - PullRequest
10 голосов
/ 27 мая 2009

Когда я хочу ограничить тип T для сопоставимости, я должен использовать:

where T : IComparable

или

where T : IComparable<T>

Я не могу разобраться, если # 2 имеет смысл. Кто-нибудь может объяснить, в чем будет разница?

Ответы [ 5 ]

5 голосов
/ 27 мая 2009

Вы можете захотеть оба ограничения, как в:

where T : IComparable, IComparable<T>

Это сделает ваш тип совместимым с большим количеством пользователей IComparable интерфейсов. Общая версия IComparable, IComparable<T> поможет избежать бокса, когда T является типом значения, и допускает более строго типизированные реализации методов интерфейса. Поддержка обоих гарантирует, что независимо от того, какой интерфейс запрашивает какой-либо другой объект, ваш объект может соответствовать требованиям и, следовательно, прекрасно взаимодействовать.

Например, Array.Sort и ArrayList.Sort использовать IComparable, а не IComparable<T>.

5 голосов
/ 27 мая 2009

Основное различие между IComparable и IComparable <> заключается в том, что первый из них является pre-generics, поэтому позволяет вызывать метод сравнения с любым объектом, тогда как второй требует, чтобы он имел общий тип:

IComparable - CompareTo(object other);
IComparable<T> - CompareTo(T other);

Я бы выбрал второй вариант при условии, что вы не собираетесь использовать какие-либо старые библиотеки .net 1.0, где типы могут не реализовывать современное универсальное решение. Вы получите повышение производительности, так как будете избегать бокса, и при сравнении не нужно будет проверять соответствие типов, а также вы почувствуете теплое чувство, возникающее при выполнении самых передовых задач ...


В связи с очень хорошим и уместным замечанием Джеффа я бы сказал, что хорошей практикой является наложение на общее число стольких ограничений, сколько требуется для выполнения задачи. Поскольку вы полностью контролируете код внутри обобщенного кода, вы знаете, используете ли вы какие-либо методы, требующие базового типа IComparable. Итак, принимая во внимание его комментарий, я лично буду придерживаться следующих правил:

  • Если вы не ожидаете, что универсальный объект будет использовать какие-либо типы, которые только реализуют IComparable (то есть устаревший код 1.0), и вы не вызываете какие-либо методы из универсального, которые полагаются на параметр IComparable затем используйте только ограничение IComparable <>.

  • Если вы используете типы, которые реализуют только IComparable, используйте только это ограничение

  • Если вы используете методы, для которых требуется параметр IComparable, но не используете типы, которые реализуют только IComparable, то использование обоих ограничений, как в ответе Джеффа, повысит производительность при использовании методов, которые принимают универсальный тип.

Чтобы расширить третье правило, давайте предположим, что класс, который вы пишете, выглядит следующим образом:

public class StrangeExample<T> where ... //to be decided
{
    public void SortArray(T[] input)
    {
         Array.Sort(input);
    }

    public bool AreEqual(T a, T b)
    {
        return a.CompareTo(b) == 0;
    }
}

И нам нужно решить, какие ограничения на него наложить. Метод SortArray вызывает Array.Sort, для которого требуется, чтобы передаваемый массив содержал объекты, реализующие IComparable. Поэтому мы должны иметь ограничение IComparable:

public class StrangeExample<T> where T : IComparable

Теперь класс будет скомпилирован и работать правильно, так как массив T допустим для Array.Sort, и в интерфейсе определен допустимый метод .CompareTo. Однако, если вы уверены, что не захотите использовать свой класс с типом, который также не реализует интерфейс IComparable <>, вы можете расширить свое ограничение до:

public class StrangeExample<T> where T : IComparable, IComparable<T>

Это означает, что при вызове AreEqual будет использоваться более быстрый общий метод CompareTo, и вы увидите выигрыш в производительности за счет невозможности использовать его со старыми типами .NET 1.0.

С другой стороны, если у вас не было метода AreEqual, тогда ограничение IComparable <> не дает никаких преимуществ, поэтому вы можете его также отбросить - вы все равно используете реализации IComparable.

2 голосов
/ 27 мая 2009

Это два разных интерфейса. До .NET 2.0 не было дженериков, поэтому было просто IComparable. С .NET 2.0 пришли дженерики и стало возможным сделать IComparable<T>. Они делают то же самое. По сути, IComparable устарел, хотя большинство библиотек распознают и то, и другое.

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

2 голосов
/ 27 мая 2009

IComparable<T> позволяет строго типизировать компаратор .

Вы можете иметь

public int CompareTo(MyType other)
{
    // logic
}

в отличие от

public int CompareTo(object other)
{
    if (other is MyType)
        // logic
}

Возьмем, к примеру, следующий пример, в котором реализованы оба интерфейса:

public class MyType : IComparable<MyType>, IComparable
{
    public MyType(string name, int id)
    { Name = name; Id = id; }

    public string Name { get; set; }
    public int Id { get; set; }

    public int CompareTo(MyType other)
    {
        if (null == other)
            throw new ArgumentNullException("other");
        return (Id - other.Id > 0 ? 1 : 0);
    }

    public int CompareTo(object other)
    {
        if (null == other)
            throw new ArgumentNullException("other");
        if (other is MyType)
            return (Id - (other as MyType).Id > 0 ? 1 : 0);
        else
            throw new InvalidOperationException("Bad type");
    }
}


MyType t1 = new MyType("a", 1);
MyType t2 = new MyType("b", 2);
object someObj = new object();

// calls the strongly typed method: CompareTo(MyType other)
t1.CompareTo(t2);
// calls the *weakly* typed method: CompareTo(object other)
t1.CompareTo(someObj);

Если MyType был реализован только с IComparable<MyType>, вторая compareTo(someObj) является ошибкой времени компиляции. Это одно из преимуществ строго типизированных генериков .

С другой стороны, в каркасе есть методы, которые требуют не универсального IComparable типа Array.Sort. В этих случаях вы должны рассмотреть возможность реализации обоих интерфейсов, как в этом примере.

1 голос
/ 27 мая 2009

Я бы использовал второе ограничение, поскольку оно позволит вам ссылаться на строго типизированные элементы интерфейса. Если вы выберете первый вариант, вам придется привести в действие тип интерфейса.

...