Книга: C# 4.0: полное руководство

Сравнение экземпляров параметра типа

Сравнение экземпляров параметра типа

Иногда возникает потребность сравнить два экземпляра параметра типа. Допустим, что требуется написать обобщенный метод IsIn(), возвращающий логическое значение true, если в массиве содержится некоторое значение. Для этой цели сначала можно попробовать сделать следующее.

// Не годится!
public static bool IsIn<T>(T what, T[] obs) {
  foreach(T v in obs)
    if(v == what) // Ошибка! return true;
  return false;
}

К сожалению, эта попытка не пройдет. Ведь параметр Т относится к обобщенному типу, и поэтому компилятору не удастся выяснить, как сравнивать два объекта. Требуется ли для этого поразрядное сравнение или же только сравнение отдельных полей? А возможно, сравнение ссылок? Вряд ли компилятор сможет найти ответы на эти вопросы. Правда, из этого положения все же имеется выход.

Для сравнения двух объектов параметра обобщенного типа они должны реализовывать интерфейс IComparable или IComparable<T> и/или интерфейс IEquatable<T>. В обоих вариантах интерфейса IComparable для этой цели определен метод СоmрагеТо(), а в интерфейсе IEquatable<T> — метод Equals(). Разновидности интерфейса IComparable предназначены для применения в тех случаях, когда требуется определить относительный порядок следования двух объектов. А интерфейс IEquatable служит для определения равенства двух объектов. Все эти интерфейсы определены в пространстве имен System и реализованы во встроенных в C# типах данных, включая int, string и double. Но их нетрудно реализовать и для собственных создаваемых классов. Итак, начнем с обобщенного интерфейса IEquatable<T>. Интерфейс IEquatable<T> объявляется следующим образом.

public interface IEquatable<T>

Сравниваемый тип данных передается ему в качестве аргумента типа Т. В этом интерфейсе определяется метод Equals(), как показано ниже.

bool Equals(Т other)

В этом методе сравниваются вызывающий объект и другой объект, определяемый параметром other. В итоге возвращается логическое значение true, если оба объекта равны, а иначе — логическое значение false.

В ходе реализации интерфейса IEquatable<T> обычно требуется также переопределять методы GetHashCode() и Equals(Object), определенные в классе Object, чтобы они оказались совместимыми с конкретной реализацией метода Equals(). Ниже приведен пример программы, в которой демонстрируется исправленный вариант упоминавшегося ранее метода IsIn().

// Требуется обобщенный интерфейс IEquatable<T>.
public static bool IsIn<T>(T what, T[] obs) where T : IEquatable<T> {
  foreach(T v in obs)
  if(v.Equals(what)) // Применяется метод Equals().
    return true;
  return false;
}

Обратите внимание в приведенном выше примере на применение следующега ограничения.

where Т : IEquatable<T>

Это ограничение гарантирует, что только те типы, в которых реализован интерфейс IEquatable, являются действительными аргументами типа для метода IsIn(). Внутри этого метода применяется метод Equals(), который определяет равенство одного объекта другому.

Для определения относительного порядка следования двух элементов применяется интерфейс ICompагable. У этого интерфейса имеются две формы: обобщенная и необобщенная. Обобщенная форма данного интерфейса обладает преимуществом обеспечения типовой безопасности, и поэтому мы рассмотрим здесь именно ее. Обобщенный интерфейс IComparable<T> объявляется следующим образом.

public interface IComparable<T>

Сравниваемый тип данных передается ему в качестве аргумента типа Т. В этом интерфейсе определяется метод CompareTo(), как показано ниже.

int CompareTo(Т other)

В этом методе сравниваются вызывающий объект и другой объект, определяемый параметром other. В итоге возвращается нуль, если вызывающий объект оказывается больше, чем объект other; и отрицательное значение, если вызывающий объект оказывается меньше, чем объект other.

Для того чтобы воспользоваться методом CompareTo(), необходимо указать ограничение, которое требуется наложить на аргумент типа для реализации обобщенного интерфейса IComparable<T>. А затем достаточно вызвать метод CompareTo(), чтобы сравнить два экземпляра параметра типа.

Ниже приведен пример применения обобщенного интерфейса IComparable<T>. В этом примере вызывается метод InRange(), возвращающий логическое значение true, если объект оказывается среди элементов отсортированного массива.

// Требуется обобщенный интерфейс IComparable<T>. В данном методе
// предполагается, что массив отсортирован. Он возвращает логическое
// значение true, если значение параметра what оказывается среди элементов
// массива, передаваемых параметру obs.
public static bool InRange<T>(T what, T[] obs) where T : IComparable<T> {
  if(what.CompareTo(obs[0]) < 0 ||
              what.CompareTo(obs[obs.Length-1]) > 0) return false;
  return true;
}

В приведенном ниже примере программы демонстрируется применение обоих методов IsIn() и InRange() на практике.

// Продемонстрировать применение обобщенных
// интерфейсов IComparable<T> и IEquatable<T>.
using System;
// Теперь в классе MyClass реализуются обобщенные
// интерфейсы IComparable<T> и IEquatable<T>.
class MyClass : IComparable<MyClass>, IEquatable<MyClass> {
  public int Val;
  public MyClass(int x) {
    Val = x;
  }
  // Реализовать обобщенный интерфейс IComparable<T>.
  public int CompareTo(MyClass other) {
    return Val - other.Val; // Now, no cast is needed.
  }
  // Реализовать обобщенный интерфейс IEquatable<T>.
  public bool Equals(MyClass other) {
    return Val == other.Val;
  }
  // Переопределить метод Equals(Object).
  public override bool Equals(Object obj) {
    if (obj is MyClass)
      return Equals((MyClass)obj);
    return false;
  }
  // Переопределить метод GetHashCode().
  public override int GetHashCode() {
    return Val.GetHashCode();
  }
}
class CompareDemo {
  // Требуется обобщенный интерфейс IEquatable<T>.
  public static bool IsIn<T>(T what, T[] obs)
         where T : IEquatable<T> {
    foreach (T v in obs)
      if (v.Equals(what)) // Применяется метод Equals()
        return true;
    return false;
  }
  // Требуется обобщенный интерфейс IComparable<T>. В данном методе
  // предполагается, что массив отсортирован. Он возвращает логическое
  // значение true, если значение параметра what оказывается среди элементов
  // массива, передаваемых параметру obs.
  public static bool InRange<T>(T what, T[] obs) where T : IComparable<T> {
    if (what.CompareTo(obs[0]) < 0 ||
          what.CompareTo(obs[obs.Length - 1]) > 0)
      return false;
    return true;
  }
  // Продемонстрировать операции сравнения,
  static void Main() {
    // Применить метод Isln() к данным типа int.
    int[] nums = { 1, 2, 3, 4, 5 };
    if (IsIn(2, nums))
      Console.WriteLine("Найдено значение 2.");
    if (IsIn(99, nums))
      Console.WriteLine("He подлежит выводу.");
    // Применить метод Isln() к объектам класса MyClass.
    MyClass[] mcs = { new MyClass(1), new MyClass(2),
                      new MyClass(3), new MyClass(4) };
    if (IsIn(new MyClass(3), mcs))
      Console.WriteLine("Найден объект MyClass(3).");
    if (IsIn(new MyClass(99), mcs))
      Console.WriteLine("He подлежит выводу.");
    // Применить метод InRange() к данным типа int.
    if (InRange(2, nums))
      Console.WriteLine("Значение 2 находится в границах массива nums.");
    if (InRange(1, nums))
      Console.WriteLine("Значение 1 находится в границах массива nums.");
    if (InRange(5, nums))
      Console.WriteLine("Значение 5 находится в границах массива nums.");
    if (!InRange(0, nums))
      Console.WriteLine("Значение 0 HE находится в границах массива nums.");
    if (!InRange(6, nums))
      Console.WriteLine("Значение 6 HE находится в границах массива nums.");
    // Применить метод InRange() к объектам класса MyClass.
    if (InRange(new MyClass(2), mcs))
      Console.WriteLine("Объект MyClass(2) находится в границах массива nums.");
    if (InRange(new MyClass(1), mcs))
      Console.WriteLine("Объект MyClass(1) находится " +
                   "в границах массива nums.");
    if (InRange(new MyClass(4), mcs))
      Console.WriteLine("Объект MyClass(4) находится " +
                   "в границах массива nums.");
    if (!InRange(new MyClass(0), mcs))
      Console.WriteLine("Объект MyClass(0) HE " +
                   "находится в границах массива nums.");
    if (!InRange(new MyClass(5), mcs))
      Console.WriteLine("Объект MyClass(5) HE " +
                   "находится в границах массива nums.");
  }
}

Выполнение этой программы приводит к следующему результату.

Найдено значение 2.
Найден объект MyClass (3) .
Значение 2 находится в границах массива nums.
Значение 1 находится в границах массива nums.
Значение 5 находится в границах массива nums.
Значение 0 НЕ находится в границах массива nums
Значение 6 НЕ находится в границах массива nums
Объект MyClass(2) находится в границах массива nums.
Объект MyClass(1) находится в границах массива nums.
Объект MyClass(4) находится в границах массива nums.
Объект MyClass(0) НЕ находится в границах массива nums.
Объект MyClass(5) НЕ находится в границах массива nums.

-----------------------------------

ПРИМЕЧАНИЕ

Если параметр типа обозначает ссылку или ограничение на базовый класс, то к экземплярам объектов, определяемых таким параметром типа, можно применять операторы == и ! =, хотя они проверяют на равенство только ссылки. А для сравнения значений придется реализовать интерфейс IComparable или же обобщенные интерфейсы IComparable<T> и IEquatable<T>.

-----------------------------------

Оглавление книги


Генерация: 1.225. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз