Г. Шилдт - С#4.0 Полное руководство (1160795), страница 117
Текст из файла (страница 117)
В силу присущей обобщениям типовой безопасности обобщенным делегатам нельзя присваивать несовместимые методы. Так, следующая строка кода оказалась бы ошибочной в рассматриваемой здесь программе. Яопеор<1пс> гпсое1 = РеЕ1есс; )/ Ошибка! 612 Часть!. Язык С() Ведь метод ке()еаг () принимает аргумент типа ясг1пс и возвращает результат типа я сггпс1, а следовательно, он несовместим с целочисленным экземпляром делегата Бовеор. Обобщенные интерфейсы Помимо обобщенных классов и методов, в СФ допускаются обобщенные интерфейсы.
Такие интерфейсы указываются аналогично обобщенным классам. Ниже приведен измененный вариант примера из главы 12, демонстрирующего интерфейс 1Бег1ея. (Напомним, что 1Бег1ея является интерфейсом для класса, генерирующего последовательный ряд числовых значений.) Тип данных, которым оперирует этот интерфейс, теперь определяется параметром типа. О Продемонстрировать применение обобщенного интерфейса. пя1пс Буягепп раЬ11с гпсегтасе 1Яеггея<Т> ( т сегнехг();О возвратить следукшее по порядку чиало чогб Веаеа(); // генерировать ряд последовательных чисел с аамого начала чо1б Яеазаага(т ч)Г УУ задать начальное значение ) О Реализовать интерфейс 1Яегьея.
с1аяя ВуТмоя<Т> : 1Яеггея<Т> ( Т яаага; Т ча1; !! Этот делегат определяет форму метода, вызываемого для генерирования Уу очередного злемента в ряду последовательных значений. рпЬ11с бе1еяаае Т 1псВуТмо(Т ч); // Этой ссылке на делегат будет присвоен метод, !/ передаваемый конструктору класаа ВуТмоя. 1псВуТмо 1псЮ рытье Вутмоя(1псВуТио гпсгМеаЬ) ( яаага = бетап1Г(Т)," ча1 = бетаи1С (Т) ," ьпаг = 1псгМеГЬ; ) рпЬ1гс т Оеаиехс() ( ча1 = гпсг(ча1)) гегпгп ча1; ) рпЬ1гс чоьб Веяеа() ( ча1 = ягагг; рпЬ1га чозб Яеазаага(Т ч) агата = ч; ча1 = ягагг; Глава 18. Обобщения 613 с1аяя ТьгееО ( рпЫтс 1пг х„ у, г; рпЫ1с ТьгееР(1пс а, гпс Ь, Тпс с) ( х= а; у = ьс г = сс ) ) с1аяя Оеп1пГТОеио ( с'с' Определить метод увеличения на дяа каждого последуялсего значения типа 1пг. ясастс 1пс 1пкр1пятио(1пс ч) ( гегпгп ч я 21 ) с'с' Определить метод увеличения на дяа каждого с'с' послед>телего значения типа с(опЬ1е.
ясастс бопЬ1е ОопЫер1сяТио(боиЬ1е ч) гегпгп ч е 2.01 ) О Определить метод увеличения на дяа каждого последуицего значения координат объекта типа Тьгее0. ясас1с ТьгееО ТьгееРР1ияТио(ТЬгееО ч) ( 11(ч==пп11) гегогп пеи Тьгеер(0, О, О) 1 е1яе гегпгп пеи ТьгееО (ч .х + 2, ч. у + 2, ч. г Я 2) 1 ) ясаг1с чо1с1 Маго() с'сс Продемонстрировать генерирование с'с' последовательного ряда значений типа гп ВуТиоя<гпс> 1пГВТ = пеи Вутиоя<1пс>(1пГР1пятио) Тот(гпс 1=0; 1 < 5; 1++) Сопяо1е.иг1ге(1пГВТ.Оесиехс() е " "); Соя яо1е.
Иг1се11пе О с'сс Продемонстрировать генерираяание 11 последовательного ряда значений типа бопЬ1е. Вут <С Ые> ОЫВТ = пеи ВуТиоя<бопЬ1е>(ОопЫер1пяТио) 1 ОЬ1ВТ.Яесвбагс(11.4) 1 Тот(1пг г=о; 1 < 51 1я+) Сопяо1е.нггье(с)Ь1ВТ.Оеснехс() е " "); Сопяо1е.нггсеьгпе()1 614 Часть (. Язык С№ Продемонстрировать генерирование последовательного ряда значений координат объекта типа Тпгеео. Вутнов<тйгеео> тйгОВт = пен Вутноя<тпгееп>(тпгееор1оятно)г Твгееп соогд; бог(1пг 1=04 1 < 5; 1++) ( соогд = ТпгОВТ.бегнехс()1 Сопяо1е.нг1се(соогд.х + "," е соогд.г + " "); ) Сопяо1е .Иг1сеп1пе () г ) Этот код выдает следующий результат. 2 4 Ь 8 10 13.4 15.4 17.4 19.4 21.4 0,0,0 2,2,2 4,4,4 б,б,б 8,8,8 В данном примере кода имеется ряд любопытных моментов.
Прежде всего обратите внимание на объявление интерфейса 15ег1ея в следующей строке кода. ров11с 1псегсасе 18еггея<Т> ( Как упоминалось выше, для объявления обобщенного интерфейса используется такой же синтаксис, что и для объявления обобщенного класса. А теперь обратите внимание на следующее объявление класса вутиоя, реализующего интерфейс Тяег1ез.
с1аяя ВуТиоя<Т> ". 18еггея<Т> ( Параметр типа т указывается не только при объявлении класса Вутиоя, но и при объявлении интерфейса 18еггея. И это очень важно. Ведь класс, реализующий обобщенный вариант интерфейса, сам должен быть обобщенным. Так, приведенное ниже объявление недопустимо, поскольку параметр типа т не определен.
с1аяя ВуТнов : 15ег1ев<Т> ( уу Неверно! Аргумент типа, требующийся для интерфейса ТВег1ея, должен быть передан классу ВуТио з. В противном случае интерфейс никак не сможет получить аргумент типа. Далее переменные, хранящие текущее значение в последовательном ряду (ча1) и его начальное значение (я загс), объявляются как объекты обобщенного типа т. После этого объявляется делегат ТпсВуТио. Этот делегат определяет форму метода, используемого для увеличения на два значения, хранящегося в объекте типа Т.
Для того чтобы в классе Вутно я могли обрабатываться данные любого типа, необходимо какимто образом определить порядок увеличения на два значения каждого типа данных. Для этого конструктору класса ВуТиоя передается ссылка на метод, выполняющий увеличение на два. Эта ссылка хранится в переменной экземпляра делегата гпсг. Когда требуется сгенерировать следующий элемент в последовательном ряду, этот метод вызывается с помощью делегата гпсг. Глава 18.
Обобщения 615 А теперь обратите внимание на класс ТЬгее О. В этом классе инкапсулируются координаты трехмерного пространства (Х,г.,'г'). Его назначение — продемонстрировать обработку данных типа класса в классе Вутиоя. Далее в классе Веп1 пс ТОещо объявляются три метода увеличения на два для объектов типа 1пс, аоиЬ1е и ТЬгееО. Все эти методы передаются конструктору класса Вутиоя при создании объектов соответствующих типов.
Обратите особое внимание на приведенный ниже метод Тбгее ОР1пяТио ( ) . О Определить метод увеличения на два каждого О последующего значения координат объекта типа Тпгееп. ясасьс Тпгееп ТЬгееОР1овТмо(ТЬгееО ч) ( 11(ч==пп11) геспгп лен ТЬгееп(0, О, О); е1ве геспгп пем ТЬгееО(ч.х + 2, ч.у + 2, ч.г + 2); В этом методе сначала проверяется, содержит ли переменная экземпляра ч пустое значение (пп11). Если она содержит это значение, то метод возвращает новый объект типа тьгееО со всеми обнуленными полями координат.
Ведь дело в том, что переменной ч по умолчанию присваивается значение типа <]е Тап1Г (Т) в конструкторе класса ВуТмоя. Это значение оказывается по умолчанию нулевым для типов значений и пустым для типов ссылок на объекты. Поэтому если предварительно не был вызван метод Бесэсагс (), то перед первым увеличением на два переменная ч будет содержать пустое значение вместо ссылки на объект.
Это означает, что для первого увеличения на два требуется новый объект. На параметр типа в обобщенном интерфейсе могут накладываться ограничения таким же образом, как и в обобщенном классе. В качестве примера ниже приведен вариант объявления интерфейса 1Беггея с ограничением на использование только ссылочных типов. рпЬ1гс 1псегтасе 1яеггев<Т> мЬеге Т : с1авв ( Если реализуется именно такой вариант интерфейса 1Беггея, в реализующем его классе следует указать то же самое ограничение на параметр типа Т, как показано ниже.
с1аяя ВуТиоя<Т> : 1яеггев<Т> ипеге Т : с1авв ( В силу ограничения ссылочного типа этот вариант интерфейса 1Бег1ея нельзя применять к типам значений. Поэтому если реализовать его в рассматриваемом здесь примере программы, то допустимым окажется только объявление ВуТмоя<ТЬгееО>, но не объявления ВуТиоя<1пс> и Вутиоя<г(ООЬ1е>.
Сравнение экземпляров параметра типа Иногда возникает потребность сравнить два экземпляра параметра типа. Допустим, что требуется написать обобщенный метод 1я1п (), возвращающий логическое значение Г где, если в массиве содержится некоторое значение. Для этой цели сначала можно попробовать сделать следующее. О Не годится! рппггс ясасгс Ьоо1 1я1п<Т>(Т ипас, Т(] оЬя) ( Гогеасп(Т ч гп оЬв) 616 Часть!.
Язык С() Ту (я == ньас) ?у Ошибка! гегнгп ггие; гегсгп уа1яе; ) К сожалению, эта попытка не пройдет. Ведь параметр т относится к обобщенному типу, и поэтому компилятору не удастся выяснить, как сравнивать два объекта. Требуется ли для этого поразрядное сравнение или же только сравнение отдельных полей? А возможно, сравнение ссылок? Вряд ли компилятор сможет найти ответы на эти вопросы.
Правда, из этого положения все же имеется выход. Для сравнения двух объектов параметра обобщенного типа они должны реализовывать интерфейс 1СоврагаЫе или 1СоврагаЬ1е<Т> и/или интерфейс 1Ес)пабаЫе<Т>. В обоих вариантах интерфейса 1СоврагаЬ1е для этой цели определен метод СоврагеТО (), а в интерфейсе 1Ес(сабаЬ1е<Т> — метод Ециа1я () . Разновидности интерфейса 1СоврагаЬ1е предназначены для применения в тех случаях, когда требуется определить относительный порядок следования двух объектов.
А интерфейс 1ес(сасаЫе служит для определения равенства двух объектов. Все эти интерфейсы определены в пространстве имен Бу я сев и реализованы во встроенных в СФ типах данных, включая Тнс, я бгьпс и с)ООЬ1е. Но их нетрудно реализовать и для собственных создаваемых классов.