Учебное пособие (1077022), страница 12
Текст из файла (страница 12)
Далеебудут рассмотрены примеры коллекций на основе обобщенных типов.98Пример создания объектов класса GenericClass1 на основе типов int иstring:GenericClass1<int> g1 = new GenericClass1<int>();g1.SetValue(333);Console.WriteLine("Обобщенный класс 1: " + g1);GenericClass1<string> g1Str = new GenericClass1<string>();g1Str.SetValue("строка");Console.WriteLine("Обобщенный класс 1: " + g1);Здесь объект g1 создан в результате подстановки в обобщенный типтипа int, а объект g1Str – в результате подстановки в обобщенный тип типаstring.ВязыкеC#реализованинтересныймеханизмограничений,позволяющий накладывать их на обобщенный тип Т (табл.
5).Таблица 5Список ограничений, поддерживаемых обобщениямиОграничениеОписаниеwhere T: structТип T должен быть типом-значениемwhere T: classТип T должен быть ссылочным типомwhere T: Class1Тип T должен наследоваться от класса Class1where T: Interface1Тип T должен реализовывать интерфейсInterface1where T1: T2При подстановке реальных типов вобобщенные типы T1 и T2, типподставленный в T1 должен наследоваться оттипа, подставленного в T2where T: new()Тип T должен иметь конструктор безпараметровРассмотрим пример ограничения на основе реализации интерфейса.Создадим интерфейс:interface I1{99string I1_method();}Создадим обобщенный класс, содержащий ограничение:class GenericClass2<T> where T : I1{private T i;//Конструкторpublic GenericClass2(T param) { this.i = param; }//Приведение к строкеpublic override string ToString(){return i.I1_method();}}Ограничение задается непосредственно при объявлении класса:class GenericClass2<T> where T : I1Для того чтобы создать объект класса GenericClass2 необходимосначала создать класс, реализующий интерфейс I1:class I1_class : I1{string str;public I1_class(string param){this.str = param;}public string I1_method() { return this.str; }}Теперь можно создать объект класса GenericClass2:GenericClass2<I1_class> g2 =new GenericClass2<I1_class>(new I1_class("Обобщенный класс с ограничением"));Кроме обобщенных классов в языке C# можно создавать обобщенныеметоды.Пример обобщенного метода с ограничением:static void GenericMethod1<T>(T param) where T : I1{Console.WriteLine(param.I1_method());100}Пример вызова обобщенного метода:GenericMethod1<I1_class>(new I1_class("Вызов обобщенного метода"));Обобщенныеклассыиметодымогутсодержатьнесколькообобщенных типов.Пример использования нескольких обобщенных типов:static void GenericMethod2<T, Q>(T param1, Q param2){Console.WriteLine("Обобщенный метод с двумя типами");}Пример вызова метода с несколькими обобщенными типами:GenericMethod2<I1_class, int>(new I1_class("строка"), 333);Обобщения являются важным и часто используемым механизмом впрограммах на C#.
Наряду с обобщенными классами и методами можносоздавать другие обобщенные структуры, например интерфейсы иделегаты.5.4 ДелегатыКонцепцияделегатаможетпоказатьсянескольконезнакомойпрограммисту на языке С++. Пожалуй, наиболее близким аналогом в С иС++ являются указатели на функцию.Однако эта концепция будет понятна программисту на языке Паскаль.Это практически точная копия процедурных типов языка Паскаль.Преимущество процедурных типов Паскаля и делегатов языка C#перед указателями на функции языка C++ состоит в том, что корректностьпроцедурных типов и делегатов проверяется компилятором, который, вчастности, способен выявить ошибки, связанные с несоответствиемпараметров. В языке С++ при работе с указателем на функцию подобныепроверки не выполняются, что создает возможность для возникновенияошибок.101В языке Java нет языковой конструкции, подобной делегатам в языкеC#.Если кратко описывать сущность делегата, то он является аналогомтипа данных для методов.
Класс является типом данных, экземпляромкласса является объект, содержащий конкретные данные. Делегат являетсятипом методов, экземпляром делегата является метод, соответствующийделегату.Рассмотрим работу с делегатами, групповыми делегатами и лямбдавыражениями на основе фрагментов примера 8.Пример объявления делегата:delegate int PlusOrMinus(int p1, int p2);Данноеописаниеоченьнапоминаетописаниеметода(безреализации). В его начале ставится ключевое слово delegate, далееуказывается тип возвращаемого значения, затем имя делегата, потом вкруглых скобках указываются входные параметры. Имена входныхпараметров могут быть произвольными, и при работе с делегатами они неиспользуются.Пример методов, соответствующих рассмотренному делегату:class Program{static int Plus(int p1, int p2){return p1 + p2;}static int Minus(int p1, int p2){return p1 – p2;}}Методы Plus и Minus соответствуют делегату PlusOrMinus, потому чтоих сигнатуры совпадают с сигнатурой делегата.
То есть они имеют наборпараметров тех же типов, и тот же тип возвращаемого значения, которыеобъявлены в делегате. Для входных параметров важно, чтобы именно их102типы совпадали с типами входных параметров делегата. Имена входныхпараметров делегата могут быть другими, но это не имеет значения припроверке.Однако при создании объектов классов с помощью ключевого словаnew программист явно указывает, экземпляр какого класса он создает, тоесть при создании объектов явно указывается имя класса.При объявлении методов Plus и Minus не задается никаких ссылок наделегат PlusOrMinus. Как же компилятор определяет, что методысоответствуют делегату? Это делается с помощью так называемой неявнойтипизации.Компилятор сравнивает сигнатуры (типы входных параметров и типвозвращаемого значения) метода и делегата.
Если они совпадают, тосчитается, что метод соответствует делегату.В некоторых языках программирования, например в Ruby, неявнаятипизация используется также для классов и объектов. Ее иногда называют«утиной» в соответствии с высказыванием, именуемым «утиным тестом»:«если это выглядит как утка, плавает как утка, и крякает как утка, товероятно это утка». То есть если экземпляр имеет те же признаки что итип, то он подобен этому типу и может считаться экземпляром этого типа.Следующий вопрос состоит в том, где можно применять делегаты.Особенность языка C# состоит в том, что делегатный тип может бытьиспользован в качестве параметра метода.Пример объявления метода с делегатным параметром:static void PlusOrMinusMethod(string str,int i1,int i2,PlusOrMinus PlusOrMinusParam){int Result = PlusOrMinusParam(i1, i2);Console.WriteLine(str + Result.ToString());}103УметодаPlusOrMinusMethodестьнесколькоинтересныхособенностей.
Его последний параметр – это параметр делегатного типа. Втеле метода имя этого параметра используется как функция, производитсяее вызов:int Result = PlusOrMinusParam(i1, i2);Однако метода PlusOrMinusParam в программе нет. Реально будетвызванметод,переданныйвкачествепараметравфункциюPlusOrMinusMethod.Таким образом, параметр делегатного типа вызывается как еще неизвестный метод, который в будущем будет передан в качестве параметра.СигнатуравызоваPlusOrMinusParamсоответствуетсигнатуределегата: передаются два входных параметра целого типа, возвращаетсязначение целого типа.Пример вызова метода с делегатным параметром:int i1 = 3;int i2 = 2;PlusOrMinusMethod("Плюс: ", i1, i2, Plus);PlusOrMinusMethod("Минус: ", i1, i2, Minus);Результат вывода в консоль:Плюс: 5Минус: 1ВданномпримеревкачествепоследнегопараметраPlusOrMinusMethod передаются имена функций Plus и Minus.Рассмотрим способы создания переменных делегатного типа. Сиспользованием конструктора делегатного типа:PlusOrMinus pm1 = new PlusOrMinus(Plus);Или в сокращенной форме, которую называют «предположениемделегата»:PlusOrMinus pm2 = Plus;Создание анонимного метода на основе делегата:PlusOrMinus pm3 = delegate(int param1, int param2){104return param1 + param2;};Создание анонимных методов применялось в языке C# до версии 3.0,хотя рассмотренный код будет корректно компилироваться и в текущихверсиях.
Но в языке C# версии 3.0 для работы с делегатными типамипоявилось новое средство – лямбда-выражения.5.5 Лямбда-выраженияЛямбда-выражения или лямбда-функции пришли в язык C# изфункционального программирования. Потребность в них появилась всвязи с реализацией технологии LINQ.Если описывать суть лямбда-выражений кратко, то это «одноразовыефункции». Казалось бы, что такое определение противоречиво.
Ведь влюбом классическом учебнике информатики можно прочитать, что методы(процедуры, функции) пишутся один раз и многократно вызываются поимени, что обеспечивает повторное использование кода. Зачем же могутпотребоваться одноразовые функции? И почему функцию нельзя объявитьтрадиционным способом и вызывать только один раз, а если потребуется,то и большее количество раз?Традиционный метод обладает двумя важными свойствами: именованностью – имеет имя, по которому метод может бытьвызван; сигнатурностью – имеет сигнатуру, то есть набор входныхпараметров с указанием их типов и тип возвращаемогозначения.Лямбда-выражение обладает свойством сигнатурности, но у него нетсвойства именованности.
То есть у лямбда-выражения есть набор входныхпараметров и тип возвращаемого значения, но нет имени. Онозаписывается в том месте, где должно вызываться.105Пример объявления лямбда-выражения (лямбда-выражение выделеноподчеркиванием):PlusOrMinus pm4 = (int x, int y) =>{int z = x + y;return z;};Лямбда-выражение напоминает по объявлению обыкновенный метод.Объявлениеначинаетсясоспискавходныхпараметров,которыезаписываются в скобках.
Имя метода в случае лямбда-выражения неуказывается. Тип возвращаемого значения автоматически определяетсякомпилятором, используется так называемый механизм вывода типов(англ. type inference).После скобок указывается стрелка « => », которая отделяет списокпараметров от тела лямбда-выражения. Стрелка является характернымсинтаксическим элементом, по которому всегда можно определить лямбдавыражение. Необходимо отметить, что оператор сравнения «больше-илиравно» записывается как « >= » и его синтаксис не совпадает ссинтаксисом лямбда-выражения.За стрелкой записывается тело метода, которое в данном примереничем не отличается от тела обычного метода.После такого объявления лямбда-выражение может быть вызвано какметод через определенную переменную pm4:int test = pm4(1, 2);Однако чаще всего лямбда-выражение передают в качестве параметраделегатного типа.Пример передачи лямбда-выражения в качестве параметра (лямбдавыражение выделено подчеркиванием):PlusOrMinusMethod("Создание экземпляра делегата на основе лямбда-выражения 1: ",i1,i2,106(int x, int y) =>{int z = x + y;return z;});В примере лямбда-выражение передается в качестве параметраделегатного типа PlusOrMinus.Соответствие сигнатуры делегата и сигнатуры лямбда-выраженияпроверяется путем неявной типизации.Например, зададим в лямбда-выражении третий входной параметр,что нарушит соответствие между сигнатурой лямбда-выражения исигнатурой делегата (третий параметр подчеркнут):PlusOrMinusMethod("Создание экземпляра делегата на основе лямбда-выражения 1: ",i1,i2,(int x, int y, int k) =>{int z = x + y ;return z;});При этом компилятор выдаст в строке определения лямбдавыражения следующую ошибку: «Делегат "Delegates.PlusOrMinus" непринимает "3" аргументов».Язык C# предоставляет возможность упростить синтаксис лямбдавыражений.
Например, вместо исходного лямбда-выражения:(int x, int y) =>{int z = x + y;return z;}можно записать:(x, y) =>{return x + y;}107В данном случае у входных параметров не указываются типы,компилятор определяет их автоматически с помощью механизма выводатипов. Возвращаемое значение вычисляется в операторе return. В томслучае, когда результат лямбда-функции может быть задан в виде единоговыражения, синтаксис можно еще более упростить:(x, y) => x + yТогда компилятор автоматически определяет, что выражение x + yявляется возвращаемым значением лямбда-функции.Еще одна интересная особенность лямбда-выражений – возможностьиспользования в них внешних переменных.















