Далее в классе GenIntfDemo объявляются три метода увеличения на два для объек тов типа int, double и ThreeD. Все эти методы передаются конструктору класса ByTwos при создании объектов соответствующих типов. Обратите особое внимание на приведенный ниже метод ThreeDPlusTwo(). // Определить метод увеличения на два каждого // последующего значения координат объекта типа ThreeD. static ThreeD ThreeDPlusTwo(ThreeD v) { if(v==null) return new ThreeD(0, 0, 0); else return new ThreeD(v.x + 2, v.y + 2, v.z + 2); }
В этом методе сначала проверяется, содержит ли переменная экземпляра v пустое значение (null). Если она содержит это значение, то метод возвращает новый объект типа ThreeD со всеми обнуленными полями координат. Ведь дело в том, что перемен ной v по умолчанию присваивается значение типа default(Т) в конструкторе класса ByTwos. Это значение оказывается по умолчанию нулевым для типов значений и пу стым для типов ссылок на объекты. Поэтому если предварительно не был вызван ме тод SetStart(), то перед первым увеличением на два переменная v будет содержать пустое значение вместо ссылки на объект. Это означает, что для первого увеличения на два требуется новый объект.
На параметр типа в обобщенном интерфейсе могут накладываться ограничения таким же образом, как и в обобщенном классе. В качестве примера ниже приведен вариант объявления интерфейса ISeries с ограничением на использование только ссылочных типов. public interface ISeries<T> where T : class {
Если реализуется именно такой вариант интерфейса ISeries, в реализующем его классе следует указать то же самое ограничение на параметр типа Т, как показано ниже. class ByTwos<T> : ISeries<T> where T : class {
В силу ограничения ссылочного типа этот вариант интерфейса ISeries нельзя применять к типам значений. Поэтому если реализовать его в рассматриваемом здесь примере программы, то допустимым окажется только объявление ByTwos, но не объявления ByTwos и ByTwos. Сравнение экземпляров параметра типа
Иногда возникает потребность сравнить два экземпляра параметра типа. Допу стим, что требуется написать обобщенный метод 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 и/или интерфейс IEquatable. В обоих вариантах интерфейса IComparable для этой цели определен метод CompareTo(), а в интерфейсе IEquatable — метод Equals(). Разновидности интерфейса IComparable предназначены для применения в тех случаях, когда тре буется определить относительный порядок следования двух объектов. А интерфейс IEquatable служит для определения равенства двух объектов. Все эти интерфейсы определены в пространстве имен System и реализованы во встроенных в C# типах дан ных, включая int, string и double. Но их нетрудно реализовать и для собственных создаваемых классов. Итак, начнем с обобщенного интерфейса IEquatable.
Интерфейс IEquatable объявляется следующим образом. public interface IEquatable<T>
Сравниваемый тип данных передается ему в качестве аргумента типа Т. В этом ин терфейсе определяется метод Equals(), как показано ниже. bool Equals(Т other)
В этом методе сравниваются взывающий объект и другой объект, определяемый параметром other. В итоге возвращается логическое значение true, если оба объекта равны, а иначе — логическое значение false.
В ходе реализации интерфейса IEquatable обычно требуется также переопре делять методы 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(), который определяет равенство одного объекта другому.
Для определения относительного порядка следования двух элементов применяется интерфейс IComparable. У этого интерфейса имеются две формы: обобщенная и не обобщенная. Обобщенная форма данного интерфейса обладает преимуществом обе спечения типовой безопасности, и поэтому мы рассмотрим здесь именно ее. Обоб щенный интерфейс IComparable объявляется следующим образом. public interface IComparable<T>
Сравниваемый тип данных передается ему в качестве аргумента типа Т. В этом ин терфейсе определяется метод CompareTo(), как показано ниже. int CompareTo(Т other)
В этом методе сравниваются вызывающий объект и другой объект, определяемый параметром other. В итоге возвращается нуль, если вызывающий объект оказывается больше, чем объект other; и отрицательное значение, если вызывающий объект ока зывается меньше, чем объект other.
Для того чтобы воспользоваться методом CompareTo(), необходимо указать огра ничение, которое требуется наложить на аргумент типа для реализации обобщенного интерфейса IComparable. А затем достаточно вызвать метод CompareTo(), чтобы сравнить два экземпляра параметра типа.
Ниже приведен пример применения обобщенного интерфейса IComparable. В этом примере вызывается метод 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(ob?[obs.Length-1]) > 0) return false; return true; } // Продемонстрировать операции сравнения. static void Main() { // Применить метод IsIn() к данным типа int. int[] nums = { 1, 2, 3, 4, 5 }; if(IsIn(2, nums)) Console.WriteLine("Найдено значение 2."); if(IsIn(99, nums)) Console.WriteLine("He подлежит выводу."); // Применить метод IsIn() к объектам класса MyClass. MyClass[] mcs = { new MyClass(1), new MyClass(2), new MyClass(3), new MyClass(4) ); if(IsIn(new MyClass()), mcs)) Console.WriteLine("Найден объект MyClass())."); 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.