Г. Шилдт - С# 3.0 Полное руководство. 2010 (1160798), страница 109
Текст из файла (страница 109)
Например, следующая строка кода считается вполне допустимой: Тнобеп<всгтпч, всг1пп> к = пен Тнобеп<всгъпч, всгтпч>("Не11о", "босс(Ьуе")( В этом случае оба типа, т и ч, заменяются одним и тем же типом зсг1по. Ясно, что если бы аргументы были одного и того же типа, то два параметра типа были бы не нужны. Общая форма обобщенного класса Синтаксис обобщений, представленных в предыдущих примерах, может быть сведен к общей форме. Ниже приведена общая форма объявления обобщенного класса. с1авв имя класса<список параметров типа> ( // ... А вот как выглядит синтаксис обьявления ссылки на обобщенный класс имя класса<список аргументов типа> имя переменной = пен нмя класса<список параметров типа> (список аргументов конструктора) Глава 18.
Обобщения 567 Ограниченные типы В предыдуших примерах параметры типа можно было заменить любым типом данных. Например, в следующей строке кода объявляется любой тип, обозначаемый как т: с1аяя цеп<т> ( Это означает, что вполне допустимо создавать объекты класса Оеп, в которых тип т заменяется типом 1пс, г(оп(>1е, ясг1пгоь Г11езсгеащ или любым другим типом данных. Во многих случаях отсутствие ограничений на указание аргументов типа считается вполне приемлемым, но иногда оказывается полезно ограничить круг типов, которые могут быть указаны в качестве аргумента типа.
Допустим, что требуется создать метод, оперирующий содержимым потока, включая объекты типа е11езггеащ или Мещогузггеапь На первый взгляд, такая ситуация идеально подходит для применения обобщений, но при этом нужно каким-то образом гарантировать, что в качестве аргументов типа будут использованы только типы потоков, но не ъпс или любой другой тип. Кроме того, необходимо как-то уведомить компилятор о том, что методы, определяемые в классе потока, будут доступны для применения.
Так, в обобщенном коде должно быть каким-то образом известно, что в нем может быть вызван метод неа<((). Для выхода из подобных ситуаций в С(( предусмотрены ограниченные типы. Указывая параметр типа, можно наложить определенное ограничение на этот параметр. Это делается с помощью оператора моете при указании параметра типа: с1аяя нмя класса<параметр типа> ниеге параметр типа : ограничения ( // где ограничения указываются списком через запятую.
В языке С(( предусмотрен ряд ограничений на типы данных. ° Ограничение на базовый класс, требующее наличия определенного базового класса в аргументе типа Это ограничение накладывается указанием имени требуемого базового класса. Разновидностью этого ограничения является неприкрытое ограничение типа, при котором на базовый класс указывает параметр типа, а не конкретный тип. Благодаря этому устанавливается взаимосвязь между двумя параметрами типа. ° Ограничение на интерфейс, требующее реализации одного или более интерфейса аргументом типа. Это ограничение накладывается указанием имени требуемого интерфейса. ° Ограничение на консаруюлор, требующее предоставить конструктор без параметров в аргументе типа.
Это ограничение накладывается с помощью оператора пем () . ° Ограничение ссылочного явила, требующее указывать аргумент ссылочного типа с помощью оператора с1аяя. ° Ограничение типа значения, требующее указывать аргумент типа значения с помощью оператора ясгисс. Среди всех этих ограничений чаще всего применяются ограничения на базовый класс и интерфейс, хотя все они важны в равной степени. Каждое из этих ограничений рассматривается далее по порядку. 568 Часть!, язык Сз Применение ограничения на базовый класс Ограничение на базовый класс позволяет указывать базовый класс, который должен наследоваться аргументом типа.
Ограничение на базовый класс служит двум главным целям. Во-первых, оно позволяет использовать в обобщенном классе те члены базового класса, на которые указывает данное ограничение. Это дает, например, возможность вызвать метод или обратиться к свойству базового класса. В отсутствие ограничения на базовый класс компилятору ничего не известно о типе членов, которые может иметь аргумент типа. Накладывая ограничение на базовый класс, вы тем самым даете компилятору знать, что все аргументы типа будут иметь члены, определенные в этом базовом классе. И во-вторых, ограничение на базовый класс гарантирует использование только тех аргументов типа, которые поддерживают указанный базовый класс.
Это означает, что для любого ограничения, накладываемого на базовый класс, аргумент типа должен обозначать сам базовый класс или производный от него класс. Если же попытаться использовать аргумент типа, не соответствующий указанному базовому классу или не наследующий его, то в результате возникнет ошибка во время компиляции. Ниже приведена общая форма наложения ограничения на базовый класс, в которой используется оператор ыбеге.
мнете Т: имх базового класса где т обозначает имя параметра типа, а имя базового класса — конкретное имя ограничиваемого базового класса. Одновременно'в этой форме ограничения может быть указан только один базовый класс. В приведенном ниже простом примере демонстрируется механизм наложения ограничения на базовый класс. // Простой пример, демонстрирующий механизм наложения // ограничения на базовый класа. пя1пс Буягегы с1аяя А ( рпЬ11с тоаб Ве11о() ( Сопяо1е.иг1геЬТпе("Ие11о"); ) ) // Класс В наследует класс А. с1яяя В: А ( ) // Класа С не наследует класс А. с1аяя С ( ) // В силу ограничения на базовый класс во всех // аргументах типа, передаваемых классу Теяп, должен // присутствовать базовый класс А. с1аяя Теят<Т> ыпеге Т: А ( Т оЬ): рпбгас Теят(Т о) ( оЬ1 о) ) Глава! 8. Обобщения 569 роЬ11с нона ВауНе11о() ( // метод Не11о() вызывается, поскольку он объявлен // в базовом классе А.
оь),Не11о()) ) ) с1ава ВаяеС1аввСопвева1пьоешо ( вяао1с тоаб Ма1п() ( А а пенА()г ВЬ=пенВ(); Сс=пенС(); // Следукяяий код вполне допустим, поскольку класс А // Указан как базовый. тело<А> с1 = пеи тевс<А>(а) 1 С1.5ауНе11о() я // Следующий код вполне допустим, поскольку класс В // наследует от класса А, Таво<В> С2 = пен Тево<В>(Ь)) С2.5ауне11о()г // Следующий код недопустим, поскольку класс С не // наследует от класса А. // Тенг<С> СЗ = пен Теап<С>(с)г // Ошибка! // С3.5ауНе11о()) // Ошибка! ) В данном примере кода класс А наследуется классом В, но не наследуется классом С. Обратите также внимание на то, что в классе А объявляется метод не11о ( ), а класс Те вт объявляется как обобщенный следующим образом: с1ава Теая<т> янеке Т: А ( Оператор ийете в этом объявлении накладывает следующее ограничение: любой аргумент, указываемый для типа т, должен иметь класс А в качестве базового. А теперь обратите внимание на то, что в классе тезс объявляется метод Науне11о (), как показано ниже.
рпЬ11с уоьб ВауНе11о() ( // Метод Не11о() вызывается, поскольку он объявлен // в базовом классе А. оЬ).Не11о()г Этот метод вызывает в свою очередь метод не11о() для объекта ОЬ3 типа Т. Любопытно, что единственным основанием для вызова метода не11о() служит следующее требование ограничения на базовый класс; любой аргумент типа, привязанный к типу т, должен относитъся к классу А или наследовать от класса А, в котором объявлен метод Не11о () .
Следовательно, любой допустимый тип т будет также определять метод 570 Часть ). язык Сз Не11о () . Если бы данное ограничение на базовый класс не было наложено, то компилятору ничего не было бы известно о том, что метод Не11о () может быть вызван для объекта типа т. Убедитесь в этом сами, удалив оператор ыпеге из объявления обобщенного класса теаш В этом случае программа не подлежит компиляции, поскольку теперь метод не11о() неизвестен. Помимо разрешения доступа к членам базового класса, ограничение на базовый класс гарантирует, что в качестве аргументов типа могут быть переданы только те типы данных, которые наследуют базовый класс.
Именно поэтому приведенные ниже строки кода закомментированы. // теас<с> сз = пеи теас<с>(с)т // Ошибка! // сэ.зауне11о()т // Ошибка! Класс с не наследует от класса А, и поэтому он не может использоваться в качестве аргумента типа при создании объекта типа т. Убедитесь в этом сами, удалив символы комментария и попытавшись перекомпилировать этот код. Прежде чем продолжить изложение дальше, рассмотрим вкратце два последствия наложения ограничения на базовый класс. Во-первых, это ограничение разрешает доступ к членам базового класса из обобщенного класса. И во-вторых, оно гарантирует допустимость только тех аргументов типа, которые удовлетворяют данному ограничению, обеспечивая тем самым типовую безопасность. В предыдущем примере показано, как накладывается ограничение на базовый класс, но из него не совсем ясно, зачем это вообще нужно.
Для того чтобы особое значение ограничения на базовый класс стало понятнее, рассмотрим еще один, более практический пример. Допустим, что требуется реализовать механизм управления списками телефонных номеров, чтобы пользоваться разными категориями таких списков, в частности отдельными списками для друзей, поставщиков, клиентов и т.д. Для этой цели можно сначала создать класс РпопеновЬег, в котором будут храниться имя абонента и номер его телефона.
Такой класс может иметь следующий вид; // Базовый класс, в котором хранятся имя абонента и // номер его телефона. с1аяа Рпопеипвпег ( рпЬ11с РпопеипвЬег(аксьпс и, аксьпс пив) ( паве = пт Ипвпег = ппсп ) // Автоматически реализуемые свойства, в которых // хранятся имя абонента и номер его телефона. рпвгъс акс1пч ИшпЬес ( сект ает) риЬ11с акг1пч паве ( Сект аект ) ) Далее создадим классы, наследующие класс РЬопеновЬег: Рг1епб и Бирр11ег. Эти классы приведены ниже.
// Класс ддя телефонных номеров друзей. с1ааа Рс1епб: РпопеншпЬес ( рсЬ11с Гг1епб(зкг1пс и, акгапс ппв, Ьоо1 ик) Ьаае(п, ппв) ( Глава! 8. Обобщения 571 1злогхнпюпег = ния ) рпЫТс Ьоо1 тзногхипипег ( Неся ргъчале зес; ) // ... ) // Класс для телефонных номеров поставщиков. с1азз зпрр11ег: РпопеншпЬег ( рпЫТс зирр11ег(зсг1пд и, злг1пч ппи) Ьазе(п, ппю) ( ) Обратите внимание иа то, что в класс Ргаепб введено свойство 1зког)<ишпЬег, возвращающее логическое значение сгпе, если номер телефона является рабочим.