Учебное пособие (1077022), страница 13
Текст из файла (страница 13)
Такая особенность в языкахпрограммирования называется замыканием (closure). Суть замыканиясостоит в том, что в каком-то блоке кода (чаще всего это обычная функцияили лямбда-выражение) используются ссылки на переменные, которыеобъявлены снаружи этого блока кода, при этом такие внешние переменныене передаются явным образом в блок кода как параметры. То есть блоккода непосредственно «видит» переменные из внешнего контекста.Пример замыкания на основе лямбда-выражения:int outer = 100;PlusOrMinus pm5 = (int x, int y) =>{int z = x + y + outer;return z;};Переменная outer, объявленная на уровне внешней функции, видна влямбда-выражении, и она может быть использована в вычислениях.Такимобразом,лямбда-выражениеможнорассматриватькакфрагмент кода, которым прикладной программист может дополнитьфункциональность какого-либо библиотечного метода. При этом данныйкод не пишется произвольно, а имеет интерфейс с библиотечным методомв виде соответствующего делегатного типа.1085.6 Локальные функцииГоворя об использовании замыканий в лямбда-выражениях, хочетсяотметить одну из новых особенностей, которая появилась в языке C#версии 7.
Это локальные функции, то есть функции, которые могут бытьобъявлены внутри других функций.Пример локальной функции:/// <summary>/// Объявление и вызов локальной функции/// </summary>static void LocalFunctionExample(){//Переменная, используемая в замыканииint p0 = 1;//Объявление локальной функцииint Sum2(int p1, int p2){return p0 + p1 + p2;}//Вызов локальной функцииint sum2 = Sum2(1, 2);Console.WriteLine(sum2);Console.ReadLine();}В данном примере локальная функция Sum2 объявлена внутрифункции LocalFunctionExample.При вызове функции LocalFunctionExample в консоль выводитсячисло «4».Как видно из примера, локальные функции, как и лямбда-выражения,поддерживают замыкания.Следует отметить, что локальные функции используется во многихязыках программирования, особенно популярны они в языке JavaScript.Теперь эта возможность доступна также и в языке C#.1095.7 Члены класса, основанные на выраженияхСинтаксис лямбда-выражений настолько прижился в сообществепрограммистов на языке C#, что разработчики языка решили добавитьвозможность объявления элементов класса (конструкторов, методов,свойств)сиспользованиемлямбда-подобногосинтаксиса.Этувозможность назвали «члены класса, основанные на выражениях»(expression-bodied members).
Данная возможность появилась в версии C# 6и была расширена в версии 7.Пример класса, использующего члены класса, основанные навыражениях:class ExpressionBodiedClass{private int i;/// <summary>/// Конструктор класса/// </summary>public ExpressionBodiedClass(int iParam) => this.i = iParam;/// <summary>/// Метод класса/// </summary>public int Sum(int a, int b) => a + b;/// <summary>/// Свойство/// </summary>public int Property1{get => this.i;set => this.i = value;}}Рассмотренный пример содержит объявление конструктора, метода исвойства. Вместо фигурных скобок используется стрелка, котораянапоминает синтаксис лямбда-выражения.Члены класса, основанные на выражениях, делают код болеекомпактным, и удобным для восприятия.1105.8 Строковая интерполяцияСтроковая интерполяция (string interpolation) – это еще однавозможность упрощения синтаксиса, которая появилась в версии C# 6.Строковая интерполяция позволяет указывать в строке шаблон для ееформатирования на основе переменных.
Необходимо отметить, чтопохожие возможности есть в других языках программирования, вчастности в Python и PHP.Пример класса, использующего строковую интерполяцию:class StringInterpolation{private string City = "Moscow";private string Country = "Russia";/// <summary>/// Использование старого синтаксиса для форматирования строк/// </summary>public string Address1{get{return string.Format("{0}, {1}", City, Country);}}/// <summary>/// Использование строковой интерполяции/// </summary>public string Address2{get => $"{City}, {Country}";}}Данный класс содержит два вычисляемых свойства Address1 иAddress2. Данные свойства возвращают одинаковые значения, содержащиефрагмент адреса.
Первое свойство реализовано с помощью традиционногосинтаксиса, второе с использованием строковой интерполяции. Второесвойство также использует синтаксис членов класса, основанных навыражениях.111Признаком использования строковой интерполяции является то, чтопри объявлении строки перед кавычками ставится символ доллара. Если втакойстрокеуказановыражениевфигурныхскобках,тооноавтоматически вычисляется.Пример создания объекта класса и вывода свойств:StringInterpolation si1 = new StringInterpolation();Console.WriteLine(si1.Address1);Console.WriteLine(si1.Address2);Результаты вывода в консоль:Moscow, RussiaMoscow, Russia5.9 Обобщенные делегаты Func и ActionПоявление лямбда-выражения в языке C# облегчило созданиепараметров делегатного типа.
Однако сами делегаты при этом попрежнему необходимо создавать отдельно. Нельзя ли облегчить решение иэтой задачи? Это сделано в версии 3.5 языка C# с использованиемобобщенных делегатов Func и Action.Ранее был объявлен обычный делегат:delegate int PlusOrMinus(int p1, int p2);Ему соответствует такое объявление делегата Func:Func<int, int, int>Рассмотренный ранее метод:static void PlusOrMinusMethod(string str,int i1,int i2,PlusOrMinus PlusOrMinusParam){int Result = PlusOrMinusParam(i1, i2);Console.WriteLine(str + Result.ToString());}112может быть переписан с использованием обобщенного делегата Funcследующим образом (параметры делегатного типа подчеркнуты впримерах):static void PlusOrMinusMethodFunc(string str,int i1,int i2,Func<int, int, int> PlusOrMinusParam){int Result = PlusOrMinusParam(i1, i2);Console.WriteLine(str + Result.ToString());}Вызов делегатного параметра в теле обоих методов производитсясовершенно одинаково: параметр вызывается как функция, которойпередаются два целочисленных параметра и возвращается целочисленныйпараметр.В качестве значения параметра, соответствующего обобщенномуделегату, может быть использовано имя обычного метода (в примереприменяется ранее рассмотренный метод Plus):PlusOrMinusMethodFunc("Создание экземпляра делегата на основе метода: ",i1,i2,Plus);Однако чаще для этих целей используется лямбда-выражение(подчеркнуто в примере):PlusOrMinusMethodFunc("Создание экземпляра делегата на основе лямбда-выражения 3:",i1,i2,(x, y) => x + y);113Таким образом, применение обычного делегата и обобщенногоделегата Func позволяет получить одинаковые результаты.
Однакопреимущество использования Func состоит в том, что его, в отличие отобычного делегата, не нужно объявлять в тексте программы, он ужеобъявлен в библиотечных классах языка C#.Если нажать правую кнопку мыши на ключевом слове Func и выбратьв контекстном меню пункт «перейти к определению», то Visual Studioосуществит переход к следующему определению, объявленному впространстве имен System:public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);В языке C# делегаты могут быть обобщенными, и Func – имяобобщенногоделегата.Приобъявленииобобщенногоделегатаобобщенные типы в треугольных скобках задаются после его имени. Вданном случае используются три обобщенных типа: два первых типа – этотипы входных параметров, а последний – тип возвращаемого значения.Таким образом, в объявлении Func<int, int, int> последний параметрзадает тип возвращаемого значения, а предыдущие – задают входныетипы.
Общее правило при использовании обобщенного делегата Func –последний параметр определяет тип выходного значения.Если перейти к определению аналогичного делегата ActionAction<int, int, int>то оно будет следующим:public delegate void Action<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3);В отличие от делегата Func, у которого последний обобщенныйпараметр задает тип выходного значения, все обобщенные параметрыделегата Action задают типы входных параметров. Обобщенный делегатAction возвращает значение типа void.По сравнению с разбиравшимися выше примерами обобщенныхклассов и методов у данных обобщенных делегатов есть еще одна114особенность – перед обобщенными типами указаны ключевые слова in иout.Данная особенность обобщенных делегатов, появившаяся в версии 4.0языка C#, и называется ковариантностью и контрвариантностью.
Кромеобобщенных делегатов эта особенность может также применяться вобобщенных интерфейсах.Обобщенный делегат или интерфейс ковариантен, если обобщенныйтип аннотирован ключевым словом out. Обобщенный тип T ковариантен,если для подставленного в него конкретного типа t, в параметр типа Tможет быть передано значение типа t или любого типа, производного от t,то есть данные более конкретного типа, чем изначально заданный.Использование ключевого слова out также означает, что тип Т разрешентолько в качестве типа возвращаемого значения.Обобщенныйделегатилиинтерфейсконтрвариантен,еслиобобщенный тип аннотирован ключевым словом in.
Обобщенный тип Tконтрвариантен, если для подставленного в него конкретного типа t, впараметр типа T может быть передано значение типа t или любого типа,базового для t, то есть данные более общего типа, чем изначальнозаданный. Использование ключевого слова in также означает, что тип Тразрешен лишь в качестве типа входного параметра.Если ключевые слова in или out не заданы, то обобщенный тип Tинвариантен, то есть для подставленного в него конкретного типа t, впараметр типа T может быть передано значение только типа t и никакогодругого типа.Использованиековариантныхиконтрвариантныхинтерфейсов детально рассмотрено в работах [1, 2].делегатови1155.10 Групповые делегатыЕсли делегат имеет тип возвращаемого значения void, то на его основеможет быть создан групповой делегат, которому соответствуют сразунесколькометодов.Вчастностиэтомуусловиюудовлетворяетобобщенный делегат Action.Рассмотрим пример использования групповых делегатов. Определимметоды в виде лямбда-выражений, соответствующих делегату Action<int,int>, то есть принимающих два входных параметра типа int и типвозвращаемого значения void:Action<int, int> a1 =(x, y) =>{ Console.WriteLine("{0} + {1} = {2}", x, y, x + y); };Action<int, int> a2 =(x, y) =>{ Console.WriteLine("{0} - {1} = {2}", x, y, x - y); };Теперь на основе данных методов можно определить групповойделегат:Action<int, int> group = a1 + a2;Если вызвать данный групповой делегат:group(5, 3);то будут вызваны методы a1 и a2.















