Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 90
Текст из файла (страница 90)
Так какой же вид вариантности участвует в присваивании совместимых экземпляров функций высшего порядка друг другу? Давайте рассмотрим новое определение делегата: бе1едасе чо1г) Таэ)г<Т>( Асозоп<Т> ассзоп ) Здесьимеем определение делегата Тазх<Т>.который ссылается на функцию,принимающую другой делегат типа Асс1оп<Т>. на заметку! Ме путайте тип таз к з данном примере с типом таз)г в библиотеке тр1 (тззк Рага((е) ВЬгагу — библиотека параллельного выполнения задач). Если бы понадобилось пометить этот делегат как вариантный, каким ключевым сдовом должен декориваться параметр-тнп — Тп или опс? Исследуем этот вопрос, взглянув на следующий пример: 342 Глава И зсасас с1азз епсгуроапс згагтс чоЫ Маьп() ( Ассаоп<лпаша1> ресАпаша1 = (Аптша1 а) > ( Сопэо1е.власе( "Любимое домашнее животное и его а.зпомлггесеаоп(); // Правило контравариантности в действии! // // Поскольку Род -> Апаша1 и // Ассаоп<Апаша1» - Ассаоп<0од>, // то следуюшее присваивание контравариантно: Астаоп<род> реСРод = реСАпаша1; реСРод( пен Вод() ) ) таей<вод> аозсатттолпод ва11атавк<под>О ) бозсцЕЙТоАРод( репрод )) О По задача, принимающая действие над сод, // таклсе может принимать действие над апаша1 СозсцггтоАРод( реСАп1ша1 )) // Следовательно, вполне логично для Таз)с<Под> О быть неявно преобразуемюс в ТазХ<Апаша1> // Ковариантность в действии! // // Поскольку Род -> Апйаа1 и // тав)с<Род» - тав)с<алйва1>, // во следуавкее лрисваивание ковариантно: таа)С<АПйаа1> С(ОЗЕатттелплпаиа1 и ЗОЗЕатттОАРОд) бовеитйтоллдпйаа1( ретАпйва1 ); с(овсатттовпод ( реть аша1 ) ) реакция: ) зсастс таз)с<т> Вц11с(таз)с<Т>О ноете Т: пен() ( гегцгп (Ассаоп<т> асс1оп) => асстоп( пен Т() ) ) Обратите внимание, что в коде создан обобщенный вспомогательный метод Вц11бтаэ)с<т>, чтобы сделать его несколько более читабельным.
В Ма1п создается экземпляр Таз)с<Род>, который затем присваивается переменной с)озгцтгтоАРод. Переменная боБгцТТТоАРод хранит ссылку на делегат, принимающий экземпляр Асстоп<Род> в качестве параметра. Затем вызывается с(оБсцтттоАРод с передачей ему ре СЭРЯ, представляющего собой акземпляр Асга оп<Род>. Но в предыдущем примере мы выяснили. что тип Асгтоп<Аптша1> неявно преобразуем в Асеаоп<под>, поэтому при втором вызове бозсцЕЕТоАРод можно передать реСАпаша1. Теперь давайте последуем тем же путем, что и в предыдущем примере, в котором выяснилось.
что тип Асстоп<йптша1> контравариантно присваиваемый Асстоп<Род>. В Ма1п создается экземпляр Таз)с<йп1ша1>, который присваивается переменной с)оБгцгйтоАРод. При вызове бозтцТТТоАРод ему определенно можно передать экземпляр Асстоп<йптша1>, но поскольку Асстоп<Аптша1> также может быть передан Таз)<<Род>, зто подразумевает,что экземпляр Таз)<<Род> может быть присвоен Та э я<Ап1ша1>.
В самом деле, именно это и демонстрируется в данном примере. Но что зто — нонтравариантность или ковариантность? На первый взгляд, поскольку Т используется справа в объявлении делегата Таз)с<Т>, можно прийти к заключению, что параметр-тип Т должен декорироваться ключевым словом 1п. Однако давайте проанализируем ситуаплпо.
Поскольку Род неявно преобра- Обобщения 343 зуется в Ап1псз1, а Тези<под> — в Тзвх<Ап1яса1>, присваивание ковариантно, так как направление преобразования в отношении Т в обоих случаях осуществляется в одном направлении. Таким образом. параметр-тип должен быть декорирован ключевым словом оп Г, в результате чего объявление Та в Х<Т> будет выглядеть так: ссе1едасе яо1сс Таэв<оос Т> ( Асггоп<Т> ассгоп ) с Здесь следует понять, что выбирать между ключевыми словами Тп и опг только на основе того, на какой стороне объявления делегата используется обобщенный параметр, нельзя, Необходимо анализировать преобразование, чтобы определить, ковариантное оно или контравариантное, и затем соответственно делать выбор.
Разумеется, если вы ошибетесь с выбором, то компилятор сообщит об этом. Обобщенные системные коллекции Похоже, что наиболее естественное применение обобщений в СЭ и СЬВ относится к типам коллекций. Возмакно, это потому, что за счет применения для хранения типов значений обобщенных контейнеров можно получить огромный прирост эффективности по сравнению с типами коллекций из пространства имен Я у в Гепс. Со11ес Г 1оп в. Естественно, при этом нельзя не заметить дополнительной безопасности типов, которую влечет за собой применение обобщенных коллекций. Всегда, когда возрастает безопасность типов, вы гарантированно получите сокращение количества исключений времени выполнения, поскольку компилятор может перехватывать многие из них на этапе компиляции.
Настоятельно рекомендуется изучить описание пространства имен Бувгеяс. Со11есггопв . Бепег1с в документации по .НЕТ Ргаспеиюгй. Там вы найдете все классы обобщенных коллекций, которые стали доступными в . НЕТ Ргаспекюг1с. В зто пространство имен входят Р1сс1опзгу<ТКеу, ТСсз1пе>, 11пиеЖ1вГ<Т>, 11вс<Т>, Опепе<Т>, БогсессР1сГ1опагу<ТКеу, Туз1ие>, яогсесс11вс<Т>, НавЬБес<Т> и ясзси<Т>.
Если взглянуть на их имена, применение этих типов должно показаться знакомым, поскольку они напоминают имена необобщенных классов из яувсеяс. Со11ессзопв. Хотя набор контейнеров внутри пространства имен Яувсеяс. Со11ессьопв. Бепег1с может показаться недостаточным для существующих потребностей, всегда есть возможность создавать собственные коллекции, особенно на базе расширяемых типов иэ пространства Яувсеяс.Со11есс1опв.ОЬЯесГМос1е1.
При создании собственных типов коллекций часто будет возникать потребность в сравнении объектов, хранящихся в коллекции. При кодировании на СЭ кажется естественным применять для выполнения сравнений встроенные операции равенства и неравенства. Однако лучше воздерживаться от этого, поскольку поддержка таких операций классами и структурами, хоть и возможна, но не является частью спецификации С1В. Некоторые языки имеют слишком медленную поддержку этих операций.
Поэтому контейнер должен быть специально подготовлен к тем случаям. когда содержащиеся в нем типы не поддерживают операций сравнения. Зто одна из причин существования таких интерфейсов, как 1Соясрагег и 1СоясрзгзЬ1е. При создании экземпляра типа яоггесс11вг внутри яувгещ. со11есг1опв имеется возможность предоставить экземпляр объекта, поддерживающего 1Соясрагег. Тип ЯогсесП.1вс будет использовать этот объект, когда возникнет необходимость в сравнении двух экземпляров ключей, содержащихся в нем. Если не предоставить объекта, подцержнвающего 1Соясрагег, то Богсесс11вс для проведения сравнения будет искать реализации интерфейса 1СовсрагзЬ1е в содержзпцсхся объектах ключей.
Естественно, если содержащиеся в коллекции объекты ключей не поддерживают 1Соясрз гэЬ1е, понадобится предоставить явный компаратор. Перегруженные версии конструктора, принимающие тип 1Соясрагег, предназначены специально для этого. 344 Глава (( Обобщенная версия сортироваиного списка Ботгес(1.1вь<ткеу, тча1пе> следует тому же шаблону в отношении сортировки.
при создании экземпляра Ботгес(11зт<ткеу, Т()а 1 се> есть возможность предоставить объект, реализующий интерфейс 1с пира тес < Т>, чтобы он сравнивал два ключа. Если этого не сделать, то яотгес(11вг<ткеу, тча1ие> по умолчанию воспользуется так называемым обобщенным компарапюром. Обобщенный компаратор — это просто объект. унаследованный от абстрактного класса Сотратет<Т>, который может быть получен через статическое свойство Соспрзтет<т>.
Регап1т. Исходя из необобщенного варианта яотгес(11вг, может показаться, что если создатель объекта Яот ес(11вг<ткеу, тчз1пе> не предусмотрел компаратор, он можно просто поискать реализацию 1СоиратаЬ1е<Т> в типе содержащегося ключа. Такой подход привел бы к проблемам, поскольку тип содержащегося ключа может поддерживать как 1СопратаЬ1е<Т>, так и необобщенный 1СоспратаЬ1е. Поэтому обобщенный компаратор действует в качестве дополнительного промежуточного уровня.
Компаратор по умолчанию проверяет, реализует ли параметр типа 1СоасратаЬ1е<Т>, и если нет, то проверяет факт поддержки 1СовратаЬ1е, используя затем первое, что найдет. Рассмотрим пример, иллюстрирующий сказанное выше: пагод Бузтетп пз1пд Бузтет.со11естгопз.5епетгс) риЬ11с с1авз Епттугогпт вгзт1с тогс( мвтп() ( Боттес(ьгзт<1пт, вттгпо> 1твт1 = пвк Яоттес(11зт<1пт, вттгпБ>()) Ботсепг Тзт<тпт, зтт1пд> 11вт2 = пек БоттеЖ1зт<1пт, зттгпд>( Соврвтет<1пт>.оетап1Г ); 11вт1.пс(П( 1, "опе" )) 11вт1.ьсЫ( 2, "тко" 11вт2.ьсЫ( 3, "тптее" )) 11вт2.АПП( 4, "Гост" )) Здесь объявлены два экземпляра яотгес(11вг<ткеу, т()з1ие>.
В первом экаемпляре использовался конструктор без параметров, а во втором явно предоставлен компаратор для целых чисел. В обоих случаях получается один и тот же результат, потому что в конструкторе 11вг2 был передан обобщенный компаратор по умолчанию. Это сделано главным образом для того. чтобы можно было увидеть синтаксис, применяемый для передачи обобщенного компаратора по умолчанию. Столь же легко можно предоставить любой другой тип в списке параметров-типов для Соиратет, при условии, что он поддерживаетлибо 1СосаратаЬ1е,либо 1СовратаЬ1е<т>. Обобщенные системные интерфейсы Учитывая тот факт, что библиотека времени выполнения предлагает обобщенные версии контейнерных типов, не должно быть сюрпризом, что она также предоставляет обобщенные версии часто используемых интерфейсов.












