Г. Шилдт - С#4.0 Полное руководство (1160795), страница 110
Текст из файла (страница 110)
С помощью параметра типа Т можно также указывать тип, возвращаемый методом, как показано ниже на примере метода ОеЬОЬ ( ) . роЬ11с Т ОеСОЬ() геготп оЬ; ) Переменная оЬ также относится к типу Т, поэтому ее тип совпадает с типом, возвращаемым методом ОеСОЬ () .
Глава 18. Обобщения 579 Метод БЬоытуре () отображает тип параметра Т, передавая его оператору Ьуреоб. Но поскольку реальный тип подставляется вместо Т при создании объекта класса Оеп, то оператор гурео1 получит необходимую информацию о конкретном типе. В классе бепегьсзоешо демонстрируется применение обобщенного класса 6еп.
Сначала в нем создается вариант класса 6еп для типа 1пь. Оеп<шс> тоь; Внимательно проанализируем это объявление. Прежде всего обратите внимание на то, что тип Тпг указывается в угловых скобках после имени класса беп. В этом случае 1пс служит аргуменагом п)ипа, привязанным к параметру типа Т в классе 6еп. В данном объявлении создается вариант класса Оеп, в котором тип Т заменяется типом Тпг везде, где он встречается. Следовательно, после этого объявления 1п Ь становится типом переменной оЬ и возвращаемым типом метода беЬОЬ () .
В следующей строке кода переменной 1ОЬ присваивается ссылка на экземпляр объекта класса 6еп для варианта типа 1пг. 1ОЬ = пен Оеп<ьпс>(102) Обратите внимание на то, что при вызове конструктора класса 6еп указывается также аргумент типа Тпг. Это необходимо потому, что переменная (в данном случае— 16ь), которой присваивается ссьглка, относится к типу Оеп<1пь>.
Поэтому ссылка, возвращаемая оператором пеы, также должна относиться к типу беп<1пг>. В противном случае во время компиляции возникнет огпибка. Например, приведенное ниже присваивание станет причиной ошибки во время компиляции. 10Ь = пеы 6еп<бопь1е>(118.12)г г'г' Ошибка! Переменная ТОЬ относится к типу Оеп<ьпс> и поэтому не может использоваться для ссылки на объект типа 6еп<с)опь1е>. Такой контроль типов относится к одним из главных преимуществ обобщений, поскольку он обеспечивает типовую безопасность. Затем в программе отображается тип переменной ОЬ в объекте 1ОЬ вЂ” тип Яувсеш. 1пс3 2. Это структура .)х)ЕТ, соответствующая типу 1пг. Далее значение переменной ОЬ получается в следующей строке кода. 1пс т = 10Ь.ОеСОЬ (); Возвращаемым для метода бесоь () является тип Т, который был заменен на тип 1пс при объявлении переменной ТОЬ, и поэтому метод ОеСОЬ () возвращает значение того же типа Тпс.
Следовательно, данное значение может быть присвоено переменной ч типа 1пг. Далее в классе Оепегьсвоешо объявляется объект типа Оеп<вьг1по>. Оеп<вгг1пп> всгоь = пен Оеп<вгггпэ>("Обобшення повышают эффективность.") В этом объявлении указывается аргумент типа в стыло, поэтому в объекте класса 6еп вместо Т подставляется тип в С г1 по. В итоге создаегся вариант класса бе и для типа а Г г з по, как демонстрируют остальные строки кода рассматриваемой здесь программы. Прежде чем продолжить изложение, следует дать определение некоторым терминам.
Когда для класса беп указывается аргумент типа, например Тпь или и ьг1пп, то создается так называемый в С)) закрыто сконструироаанныи щип. В частности, 6еп<1пс> является закрыто сконструированным типом. Ведь, по существу, такой обобщенный тип, как беп<Т>, является абстракцией. И только после того, как будет сконструирован конкретный вариант, например Оеп<1пс>, создается конкретный тип.
А конструк- 580 Часть (. язык С() ция, подобная беп<Т>, называется в С(( оглкрыто сконструированным я(илом, поскольку в ней указывается параметр типа Т, но не такой конкретный тип, как Тпг. В С(( чаще определяются такие понятия, как опгкрытыи и закрыглыи глины. Открытым типом считается такой параметр типа или любой обобщенный тип, для которого аргумент типа является параметром типа или же включает его в себя. А любой тип, не относящийсл к открытому, считается закрытым. Сконструированным лгипом считается такой обобщенный тип, для которого предоставлены все аргументы типов.
Если все эти аргументы относятся к закрытым типам, то такой тип считается закрыто сконструированным. А если один или несколько аргументов типа относятся к открытым типам, то такой тип считается открыто сконструированным. Различение обобщенных типов по аргументам типа Что касается обобщенных типов, то следует иметь в виду, что ссылка на один конкретный вариант обобщенного типа не совпадает по типу с другим вариантом того же самого обобщенного типа.
Так, если ввести в приведенную выше программу следующую строку кода, то она не будет скомпилирована. ЬОЬ = всгОЬ; // Неверно! Несмотря на то что обе переменные, ТОЬ и вггОЬ, относятся к типу Сеп<т>, они ссылаются на разные типы, поскольку у них разные аргументы. Повышение типовой безопасности с помощью обобщений В связи с изложенным выше возникает следующий резонный вопрос если аналогичные функциональные возможности обобщенного класса беп можно получить и без обобщений, просто указав объект как тип данных и выполнив надлежащее приведение типов, то какая польза от того, что класс оеп делается обобщенным? Ответ на этот вопрос заключается в том, что обобщения автоматически обеспечивают типовую безопааюсгь всех операций, затрагивающих класс реп. В ходе выполнения этих операций обобщения исключают необходимость обращаться к приведению типов и проверять соответствие типов в коде вручную. Для того чтобы стали более понятными преимущества обобщений, рассмотрим сначала программу, в которой создается необобщенный аналог класса Пеп.
Класс Моппеп является полным функпиональннм аналогом ?/ класса Оеп, но без обобщений. овгпд зувсещг сгавв Нопбеп ( оЬЗесс оЬ| // переменная оЬ теперь относится к типу оЬ)есс /? Передать конструктору ссылку на объект типа оЬТесю риЫГс Мопцеп(ЬЬЗесг о) ( оЬ = ог ) Возвратить объект типа оЬЗесс. роЫгс оЬЗесс ОеСОЬ() ( геспгп оЬг Глава 18. Обобщения 581 // Показать тип переменной оЬ. рцЬ11с ноге ЯЬоиТуре() ( Сопво1е .Иггоепвпе ("Тип переменной оЬ: " + оЬ.оегтуре () ); ) ) // Продемонстрировать применение необобщенного класса.
с1азз ИопоепОешо ( зсасгс ноьб Магп() ( Иопоеп 10Ьг Создать. объект класса Иопоеп. 10Ь = пеи Иопоеп(102)г О Показать тип данных, хранящихая в переменной 10Ь. 10Ь.БЬоиТуре(); // Получить значение переменной 10Ь. На этот раз потребуетая приведение типов. Тпг н = (гпС) 10Ь.оеСОЬ()г Сопво1е.Иг1Сесспе("Значение: " т н)г Сопзо1е.нг1гестпе(); // Создать еше один объект класса Иопоеп и сохранить строку в переменной гС. Иопоеп зггОЬ = пеи Иопоеп("Тест на необобщенность") // Показать тип данных, хранящихся в переменной вггОЬ.
зсгОЬ.БЬоиТуре(): О Получить значение переменной вггОЬ. // И в этом случае требуется приведение типов. Яггспо згг = (згг1по) загОЬ.ОеСОЬ(); Сопзо1е.иггаесгпе("Значение: " + вот); О Этот код компилируется, но он принципиально неверный! ТОЬ = вагОЬ; О Следуггцая строка кола приводит к исклшчительной // ситуации во время выполнения. — (гпг) 10Ь.ЯеСОЬ()) // Ошибка при выполнении! При выполнении этой программы получается следующий результат.
Тип переменной оЬ: Яузгеш.1пг32 Значение: 102 Тип переменной оЬ: Яузаеш.згг1пд Значение: Теат на необобшенноать 582 Часть (. Язык С(г Как видите, результат выполнения этой программы такой же, как и у предыдущей программы. В этой программе обращает на себя внимание ряд любопытных моментов. Прежде всего, тип т заменен везде, где он встречается в классе ЫопОеп. Благодаря этому в классе ЫопОеп может храниться объект любого типа, как и в обобщенном варианте этого класса. Но такой подход оказывается непригодным по двум причинам. Во-первых, для извлечения хранящихся данных требуется явное приведение типов.
И во-вторых, многие ошибки несоответствия типов не могут быть обнаружены вплоть до момента выполнения программы. Рассмотрим каждую из этих причин более подробно. Начнем со следующей строки кода. 1пд ч = (3пт) 1ОЬ.ОеСОЬ() ! Теперь возвращаемым типом метода ОеЬОЬ () является оЬз еос, а следовательно, для распаковки значения, возвращаемого методом Оегоь (), и его последующего сохранения в переменной у требуется явное приведение к типу 1пс. Если исключить приведение типов, программа не будет скомпилирована. В обобщенной версии этой программы приведение типов не требовалось, поскольку тип Тпг указывался в качестве аргумента типа при создании объекта 1ОЬ.
А в необобщенной версии этой программы потребовалось явное приведение типов. Но это не только неудобно, но и чревато ошибками. А теперь рассмотрим следующую последовательносп кода в конце анализируемой здесь программы. /!' Этот код компилируется, но он принципиально неверный! гОЬ = вгтОЬ! Следушыая строка кода приводит к исключительной !'! ситуации во время выполнения.
— (ьпд) 10Ь.ОеГОЬ О ! !'!' Ошибка при выполнении! В этом коде значение переменной зсгОЬ присваивается переменной 1ОЬ. Но переменная з ЬгОЬ ссылается на обьект, содержащий символьную строку, а не целое значение. Такое присваивание оказывается верным с точки зрения синтаксиса, поскольку все ссылки на объекты класса ЫопОеп одинаковы, а значит, по ссылке на один объект класса )(опОеп можно обращаться к любому другому объекту класса нопОеп.
Тем не менее такое присваивание неверно с точки зрения семантики, как показывает следующая далее закомментированная строка кода. В этой строке тип, возвращаемый методом ОеЬОЬ (), приводится к типу 1пс, а затем предпринимается попытка присвоить полученное в итоге значение переменной 1пс. К сожалению, в отсутствие обобщений компилятор не сможет выявить подобную ошибку.