Какую цель выполняет класс Comparer<T>
, если указанный тип уже реализует IComparable
?
Если я укажу Comparer.Default, а Customer уже реализует IComparable, то почему я должен использовать класс Comparer?
Какую цель выполняет класс Comparer<T>
, если указанный тип уже реализует IComparable
?
Если я укажу Comparer.Default, а Customer уже реализует IComparable, то почему я должен использовать класс Comparer?
Я думаю, что ваш вопрос в том, почему есть базовый класс, который, по-видимому, имеет только один полезный метод, который оказывается тем же методом, который вы бы реализовали, если бы вы внедрили интерфейс напрямую. Если бы я понял, что правильно, я думаю, вы правы, что нет никакой выгоды для получения Comparer<T>
, а не для реализации IComparer<T>
напрямую, за исключением того, что базовый класс предоставляет вам общую и не-общую реализацию с единственным переопределенным методом.
Но если ваш вопрос, почему есть как IComparer<T>
, так и IComparable<T>
, тогда
как указывали другие, Comparer<T>
позволяет вам определить различные способы выполнения сравнения. Это реализация шаблона проектирования стратегии. Отличным примером этого могут быть различные свойства StringComparer
, такие как StringComparer.Ordinal, StringComparer.OrdinalIgnoreCase и т.д. Это позволяет вам сортировать строки по-разному в разных обстоятельствах, которые интерфейс IComparable<T>
просто не может предвидеть.
Но помимо возможности переформулировать способ сравнения, иногда предоставление внешнего компаратора - единственный способ сделать это. Например, класс ListView Windows Forms позволяет указать IComparer для его логики сортировки. Но ListViewItem не реализует IComparable. Таким образом, без стратегии сравнения, которая знает, как это сделать, ListViewItem не сортируется, потому что у него нет стандартной реализации IComparable.
Итак, в конце концов, это просто еще одна точка расширяемости, которая позволяет вам больше гибкости, если вы не являетесь автором типа, который должен быть отсортирован (или вам нужны несколько стратегий сортировки.)
Возможно, пример поможет. Скажем, вы пишете метод расширения, который проверяет, существует ли заданное значение между диапазоном.
public static bool Between<T>(this T value, T minValue, T maxValue) {
var comparer = Comparer<T>.Default;
int c1 = comparer.Compare(value, minValue);
int c2 = comparer.Compare(value, maxValue);
return (c1 >= 0 && c2 <= 0);
}
В этом случае я ничего не знаю о типе T. Он может реализовать IComparable
или может реализовать IComparable<T>
, или он не может реализовать ни одно из них, и будет выведено исключение. Это также позволяет мне легко добавить перегрузку для этого метода, который позволяет вызывающему перейти в свой собственный компаратор. Но здесь полезен Comparer, потому что он позволяет мне получить сопоставление по умолчанию для неизвестного типа, который может или не может реализовывать общий или не общий интерфейс IComparable.
Так как вам иногда нужно поддерживать упорядоченные порядки/упорядоченные очереди, то "естественный" порядок или более, то существует один естественный порядок.
Например, если у вас есть плоские линии, вы можете отсортировать их по:
Задачи на компьютере могут быть запланированы:
Таким образом, даже в одном приложении вам может понадобиться отсортировать объекты по различным свойствам. Вы не можете сделать это с помощью метода int compareTo(Object)
, поскольку он не может различать между контекстами. Однако вы можете добавить контекст, то есть реализовать CompareByPriority
.
Тип не должен реализовывать IComparable, он может быть любого типа - нет ограничений на T
:
public abstract class Comparer<T> : IComparer, IComparer<T>
Новый Comparer
, который вы создаете, реализует IComparer<T>
и не общий IComparer
, и может использоваться для сравнения и сортировки коллекций.
Вы правы: если ваш тип Customer
реализует IComparable
, и вам не нужно другое сравнение, Comparer
вам не пригодится. Большинство классов в инфраструктуре .net могут принимать как IComparable<T>
, так и Comparer<T>
, поэтому вы можете использовать один из них.
Однако вы ошибаетесь, полагая, что это всегда так. Очень возможно создать Comparer
для не сравнимого типа. Обратите внимание, что требуется не:
public abstract class Comparer<T> : IComparer, IComparer<T>
where T : IComparable, IComparable<T>
Предположим, что у вас есть простой класс Person
, и вы хотите отсортировать список Persons
, лучше всего его написать для сравнения:
public class Person
{
string Name { get; set; }
}
Здесь есть несколько тонких моментов:
IComparable<T>
- он также поддерживает более старый (не общий) IComparable
как резерв. Это означает, что он не может быть выражен так же, как (например) общее ограничениеNullable<T>
, где T
сопоставим, хотя Nullable<T>
явно не IComparable
или IComparable<T>
List<T>
может предоставить Sort
, даже если он не настаивает на том, что все T
являются сортируемыми; вы будете удивлены, как быстро общие ограничения будут накапливаться иначе.public class Person
{
public string LastName;
public string FirstName;
}
public class Class2
{
public void test()
{
List<Person> classList = new List<Person>();
//add some data to the list
PersonComparer comp = new PersonComparer();
classList.Sort(comp);
}
}
public class PersonComparer : Comparer<Person>
{
public override int Compare(Person x, Person y)
{
int val = x.LastName.CompareTo(y.LastName);
if (val == 0)
{
val = x.FirstName.CompareTo(y.FirstName);
}
return val;
}
}
Если тип реализует IComparable<T>
, почти наверняка лучше использовать его, чем IComparable
. С типами значений производительность IComparable<T>
часто намного лучше, чем производительность не общего IComparable
. С наследуемыми ссылочными типами IComparable<T>
может предлагать лучшую семантику, чем IComparable
, позволяя ранжирование на основе полей производного типа.
Для примера последнего преимущества предположим, что у одного есть абстрактный базовый класс ScheduleEvent
с свойством EventTime
, который реализует IComparable<ScheduleEvent>
путем сортировки EventTime
. Производные типы включают ScheduledPopupMessageEvent
с строкой сообщения, a ScheduledGongEvent
с параметром GongVolume
. Несколько ScheduleEvent
с тем же EventTime
должны сообщать ноль для IComparable<ScheduleEvent>.CompareTo
, потому что нет безопасного и последовательного способа ранжирования ScheduleEvent
разных типов, а также потому, что два ScheduleEvent
, которые оба сообщают о себе как неоскверненные относительно третье должно, для согласованности, сообщать о себе как о неприкосновенности относительно друг друга. С другой стороны, не было бы проблем с тем, чтобы ScheduledGongEvent
реализовать IComparable<ScheduledGongEvent>
принять GongVolume
во внимание, а также EventTime
или с помощью ScheduledPopupMessageEvent
делать то же самое со своим параметром Message.
Поэтому полезно иметь такие вещи, как процедуры сортировки, использовать IComparable<T>
, если он существует, но иметь возможность вернуться к IComparable
, если IComparable<T>
не существует. Однако проверка того, реализует ли класс IComparable<T>
, и выбор подходящей реализации, однако, немного дороже. К счастью, как только тип определяется как реализация IComparable<T>
, на него можно положиться, чтобы всегда иметь его; Аналогично, если тип не имеет такой реализации, он никогда не будет. Кроме того, если общий класс имеет какие-либо статические поля, каждая комбинация параметров типа даст другой класс с разными полями. Таким образом, первый раз Comparer<T>.Default
запускается с определенным типом параметра T, он будет хранить процедуру Comparer, которую он возвращает в статическое поле. Если Comparer<T>
снова запускается с тем же типом, он вернет ту же самую процедуру сравнения. Хотя может показаться странным иметь статический класс Comparer<T>
только с одним методом, создание отдельного класса Comparer<T>
для каждого типа T
предоставляет место для хранения созданной процедуры сравнения.
Comparer<T>
содержит фактический метод сравнения. Его можно использовать, если вы хотите сравнить объект по-другому, чем в реализации IComparable
.