Ведь метод Reflect () принимает аргумент типа string и возвращает результат типа string, а следовательно, он несовместим с целочисленным экземпляром делегата SomeOp.

Обобщенные интерфейсы

Помимо обобщенных классов и методов, в C# допускаются обобщенные интерфейсы. Такие интерфейсы указываются аналогично обобщенным классам. Ниже приведен измененный вариант примера из главы 12, демонстрирующего интерфейс ISeries. (Напомним, что ISeries является интерфейсом для класса, генерирующего последовательный ряд числовых значений.) Тип данных, которым оперирует этот интерфейс, теперь определяется параметром типа.

// Продемонстрировать применение обобщенного интерфейса, using System;

public interface ISeries<T> {

T GetNext();// возвратить следующее по порядку число

void Reset(); // генерировать ряд последовательных чисел с самого начала void SetStart(T v); // задать начальное значение

}

//Реализовать интерфейс ISeries, class ByTwos<T> : ISeries<T> {

T start;

T val;

// Этот делегат определяет форму метода, вызываемого для генерирования // очередного элемента в ряду последовательных значений, public delegate Т IncByTwo(Т v);

// Этой ссылке на делегат будет присвоен метод,

// передаваемый конструктору класса ByTwos.

IncByTwo incr;

public ByTwos(IncByTwo incrMeth) { start = default(T); val = default(T); incr = incrMeth;

}

public T GetNext() { val = incr(val); return val;

}

public void Reset() {

val = start;

}

public void SetStart(T v) { start = v; val = start;

}

}

class ThreeD {

public int x/ч у, z;

public ThreeD(int a, int b, int c) { x = a;

У = b;

z = c;

}

}

class GenlntfDemo {

// Определить метод увеличения на два каждого // последующего значения типа int. static int IntPlusTwo(int v) { return v + 2;

}

// Определить метод увеличения на два каждого // последующего значения типа double, static double DoublePlusTwo(double v) { return v + 2.0;

}

// Определить метод увеличения на два каждого // последующего значения координат объекта типа 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);

}

static void Main() {

// Продемонстрировать генерирование // последовательного ряда значений типа int. ByTwos<int> intBT = new ByTwos<int>(IntPlusTwo);

for(int i=0; i < 5; i++)

Console.Write(intBT.GetNext() + " ") ;

Console.WriteLine();

// Продемонстрировать генерирование // последовательного ряда значений типа double. ByTwos<double> dblBT =

new ByTwos<double>(DoublePlusTwo); dblBT.SetStart(11.4);

for(int i=0; i < 5; i++)

Console.Write(dblBT.GetNext() + " ");

Console.WriteLine();

// Продемонстрировать генерирование последовательного ряда // значений координат объекта типа ThreeD.

ByTwos<ThreeD> ThrDBT = new ByTwos<ThreeD>(ThreeDPlusTwo);

ThreeD coord;

coord.z + " ");

}

Console.WriteLine();

}

}

Этот код выдает следующий результат.

2 4 6 8 10

13.4 15.4 17.4 19.4 21.4 0, 0, 0 2,2,2 4, 4, 4 6, 6, 6 8, 8,8

В данном примере кода имеется ряд любопытных моментов. Прежде всего обратите внимание на объявление интерфейса ISeries в следующей строке кода.

public interface ISeries<T> {

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

А теперь обратите внимание на следующее объявление класса ByTwos, реализующего интерфейс I series.

class ByTwos<T> : ISeries<T> {

Параметр типа Т указывается не только при объявлении класса ByTwos, но и при объявлении интерфейса ISeries. И это очень важно. Ведь класс, реализующий обобщенный вариант интерфейса, сам должен быть обобщенным. Так, приведенное ниже объявление недопустимо, поскольку параметр типа Т не определен.

class ByTwos : ISeries<T> { // Неверно!

Аргумент типа, требующийся для интерфейса ISeries, должен быть передан классу ByTwos. В противном случае интерфейс никак не сможет получить аргумент типа.

Далее переменные, хранящие текущее значение в последовательном ряду (val) и его начальное значение (start), объявляются как объекты обобщенного типа Т. После этого объявляется делегат IncByTwo. Этот делегат определяет форму метода, используемого для увеличения на два значения, хранящегося в объекте типа Т. Для того чтобы в классе ByTwos могли обрабатываться данные любого типа, необходимо каким-то образом определить порядок увеличения на два значения каждого типа данных. Для этого конструктору класса ByTwos передается ссылка на метод, выполняющий увеличение на два. Эта ссылка хранится в переменной экземпляра делегата incr. Когда требуется сгенерировать следующий элемент в последовательном ряду, этот метод вызывается с помощью делегата incr.

А теперь обратите внимание на класс ThreeD. В этом классе инкапсулируются координаты трехмерного пространства (X,Z,Y). Его назначение — продемонстрировать обработку данных типа класса в классе ByTwos.

Далее в классе Genlntf Demo объявляются три метода увеличения на два для объектов типа 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(),TO перед первым увеличением на два переменная v будет содержать пустое значение вместо ссылки на объект. Это означает, что для первого увеличения на два требуется новый объект.

На параметр типа в обобщенном интерфейсе могут накладываться ограничения таким же образом, как и в обобщенном классе. В качестве примера ниже приведен вариант объявления интерфейса ISeries с ограничением на использование только ссылочных типов.

public interface ISeries<T> where T : class {

Если реализуется именно такой вариант интерфейса ISeries, в реализующем его классе следует указать то же самое ограничение на параметр типа Т, как показано ниже.

class ByTwos<T> : ISeries<T> where T : class {

В силу ограничения ссылочного типа этот вариант интерфейса ISeries нельзя применять к типам значений. Поэтому если реализовать его в рассматриваемом здесь примере программы, то допустимым окажется только объявление ByTwos<ThreeD>, но не объявления ByTwos<int> и ByTwos<double>.

 

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

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

public interface IEquatable<T>