
8_C#_делегаты_события.pptx
- Количество слайдов: 25
Тип delegate ü Объявление делегата определяет ссылочный тип, который является оболочкой для метода с заданной сигнатурой. Возвращаемое значение и параметры хранимого в делегате метода public delegate void Alarm. Handler(int code); Имя нового типа ü Объект-делегат может инкапсулировать статический или экземплярный метод. ü Для экземплярного метода делегат содержит пару <объект, метод>. ü Делегаты в С# - это объектно-ориентированная реализация указателей на функцию. ü Делегаты поддерживают тип event.
Делегаты. Пример class Abc { delegate string My. Delegate (int j); int dt; class Program { public Abc( int dt) { this. dt = dt; } public string FI (int j) { return "FI " + j + " " + dt; } public static string FS (int j) { return "FS " + j; } static void Main(string[] args) { My. Delegate myd 1 = new My. Delegate (Abc. FS); F_in_Main (myd 1, 1); Abc abc 1 = new Abc(55); My. Delegate myd 2 = abc 1. FI; // 2. 0 и выше F_in_Main (myd 2, 2); } Abc abc 2 = new Abc(77); My. Delegate myd 3 = abc 2. FI; Вывод: FS 1 FI 2 55 FI 89 77 My. Delegate[] arr = new My. Delegate[2] {myd 2, myd 3}; foreach ( My. Delegate f in arr) F_in. M_ain (f, 89); } static void F_in_Main (My. Delegate dlgt, int j) { Console. Write. Line ( dlgt(j)); } }
Класс делегата ü При компиляции для делегата создается класс с именем типа-делегата и методами для синхронного и асинхронного вызова делегата. ü Для делегата My. Delegate из примера создается класс рrivate sealed class My. Delegate : Multicast. Delegate { public string Invoke (int j) { … } public System. IAsync. Result Begin. Invoke (int j, System. Async. Callback dlg, object obj) { … } public string End. Invoke( System. IAsync. Result obj) { … } } ü Метод-делегат может быть вызван как синхронно ( возврат не произойдет до того, как метод-делегат завершит свою работу) , так и асинхронно ( метод сразу возвращает управление). ü Program Files \. . . \ ildasm. exe ü Дж. Рихтер - Программирование на платформе Microsoft. NET Framework, гл. 17
Классы System. Delegate и System. Multicast. Delegate ü Класс, который создается при компиляции кода для делегата, является производным от System. Multicast. Delegate. System. Object System. Delegate System. Multicast. Delegate ü Некоторые методы класса System. Delegate: public abstract class Delegate : ICloneable, ISerializable {. . . public Method. Info Method {get; } public object Target {get; } // объект, для которого делегат (объект// делегат) вызывает экземплярный метод, null для статического метода }
Цепочки делегатов ü В классе System. Multicast. Delegate поддерживается список вызовов (invocation list), который дает возможность создавать цепочки делегатов. ü Цепочки делегатов обычно используются при обработке событий. ü В классе System. Delegate есть методы для добавления и удаления делегатов из списка: public static Delegate Combine( Delegate a, Delegate b ); public static Delegate Remove( Delegate source, Delegate value ); ü В C# определены операции для += и -= для добавления и удаления делегатов из цепочки (вызывают Delegate. Combine и Delegate. Remove).
Цепочки делегатов. Пример class Abc { int dt; public Abc( int dt) { this. dt = dt; } public string FI (int j) { Console. Write. Line(“FI”); return "FI " + j + " " + dt; } public static string FS (int j) { Console. Write. Line(“FS”); return "FS " + j; } public override string To. String() { return "Abc dt = " + dt; } } Вывод: FS FI FI FI 555 22 Before F 2_in_Main call Abc dt = 22 delegate string My. Delegate ( int j); class Program { static void Main(string[] args) { My. Delegate myd 1 = Abc. FS; Abc abc 1 = new Abc(11); My. Delegate myd 2 = abc 1. FI; Abc abc 2 = new Abc(22); My. Delegate myd 3 = abc 2. FI; myd 1 += myd 2; myd 1 += myd 3; F 1_in_Main(myd 1, 555); Console. Write. Line("Before F 2_in_Main call"); F 2_in_Main (myd 1, 555); } static void F_in_Main (My. Delegate dlgt, int j) { Console. Write. Line ( dlgt(j)); } static void F 2_in_Main (My. Delegate dlgt, int j) { Console. Write. Line ( dlgt. Target); } }
Covariance and Contravariance в делегатах ü В . NET Framework 2. 0 и выше метод, который используется при создании делегата, может иметь сигнатуру, не полностью совпадающую с сигнатурой делегата: • тип возвращаемого значения метода может быть производным от типа, указанного при объявлении делегата ( covariance); • тип параметра(ов) может быть базовым для типа параметра, указанного при объявлении делегата (сontravariance). class Bs {} class Dr : Bs {} delegate Bs Delegate_1(); delegate void Delegate_2( Dr dr ); class Test { public static Bs F 1() { return new Bs(); } public static Dr F 2() { return new Dr(); } public static void F 3(Bs bs) { Console. Write. Line(“F 3"); } } class Program { static void Main() { // Covariance Delegate_1 dlg 1 = Test. F 2; // Contravariance Delegate_2 dlg 2 = Test. F 3; . . . } }
public delegate TOutput Converter<TInput, TOutput> ( TInput input ) Обобщенные делегаты ü Можно определять обобщенные делегаты. ü Пример. В пространстве имен System определены обобщенные делегаты public delegate bool Predicate<T> (T obj); public delegate TOutput Converter<TInput, TOutput> ( TInput input ); ü В классе System. Array определены методы, проверяющие выполнение условий, определенных в заданном предикате, для элементов массива. public static bool Exists<T> ( T[] array, Predicate<T> match ); public static bool True. For. All<T> ( T[] array, Predicate<T> match ); ü Обобщенный метод Convert. All в классе System. Array преобразует массива одного типа в массив другого типа. public static TOutput[] Convert. All <TInput, TOutput> ( TInput[] array, Converter<TInput, TOutput> converter ) ;
Обобщенные делегаты. Пример ü В примере проверяется наличие символа ‘a’ в элементах массива string_array и выполняется преобразование массива типа string[] в массив типа int[]. static void Main(string[] args) { string[] string_array = new string[] { "abc", "efgh", "asdf" }; Predicate<string> pred = Contains. A; Console. Write. Line(System. Array. Exists<string>(string_array, pred )); // true Console. Write. Line(System. Array. True. For. All<string>(string_array, Contains. A)); // false int[] int_array = Array. Convert. All<string, int>(string_array, String. To. Length. Converter); for (int j = 0; j < int_array. Length; j++) Console. Write. Line(int_array[j]); // 3 4 4 } static int String. To. Length. Converter(string str) { return str. Length; } static bool Contains. A(string str) { return str. Contains("a"); }
Лямбда-выражения ü В. NET Framewok 3. 0 и выше поддерживаются лямбда-выражения. ü Лямбда-выражения предоставляют простой наглядный синтаксис для объявления анонимных методов – неименованных блоков операторов, которые могут выполняться при вызове делегатов. ü Лямбда-выражение имеет вид аргументы => оператор ü Левая часть лямбда-выражения содержит список параметров, правая - выражение или блок операторов, которые выполняются для параметров лямбда-выражения. ü Примеры лямбда-выражений x => x/2 (x, y) => x+y (x, y) => y[x] (x, y) => y. Contains(x) ü Те же самые выражения в виде лямбда-операторов x => { return x/2; } (x, y) => { return x+y; } (x, y) => { return y[x]; }
Лямбда-выражения как анонимный метод. Пример ü Лямбда-выражения могут не содержать информации о типах параметров и результата, компилятор определяет их типы из контекста использования. ü В примере проверяется наличие символа ‘a’ в элементах массива string_array и выполняются преобразование массива типа string[] в массив типа int[] и преобразование массива типа int[] в массив типа bool[]. static void Main(string[] args) { string[] string_array = new string[] { "abc", "efgh", "asdf" }; Console. Write. Line(System. Array. Exists<string>(string_array, x => x. Contains("a"))); // x имеет тип string int[] int_array_1 = Array. Convert. All<string, int>(string_array, x => 10 * x. Length); for (int j = 0; j < int_array_1. Length; j++) Console. Write. Line(int_array_1[j]); // x имеет тип string bool[] bool_array = Array. Convert. All<int, bool>(int_array_1, x => x > 30 ); // x имеет тип int for (int j = 0; j < bool_array. Length; j++) Console. Write. Line(bool_array[j ]); }
Лямбда-выражения. Пример ü В примере лямбда-выражения присваиваются делегатам и выполняются. Func<int, string[], string> expr_1 = (x, y) => y[x]; try { string[] s_array = new string[] { "abc", "efg" }; Console. Write. Line(expr_1(1, s_array)); // efg Console. Write. Line(expr_1(3, s_array)); } // Index was outside the bounds of the array catch(Exception ex) { Console. Write. Line (ex. Message); } Func<string, bool> expr_2 = (x, y) => x. Contains(y); // true Console. Write. Line(expr_2("abc", "ab")); Func<Array. List, object, bool> expr_3 = (x, y) => x. Contains(y); ü В стандартной библиотеке определен набор универсальных делегатов для использования их c лямбда-выражениями. public delegate TResult Func<TResult>(); public delegate TResult Func<T, TResult>( T arg ); public delegate TResult Func<T 1, T 2, TResult>( T 1 arg 1, T 2 arg 2 ); public delegate TResult Func<T 1, T 2, T 3, TResult>( T 1 arg 1, T 2 arg 2, T 3 arg 3 ); … до 10 параметров
Лямбда-выражения. Захват переменной. Пример ü В примере в лямбда-выражении используется переменная external_str из метода, в котором определено это выражение. string external_str = "ab"; Func<string, int> expr = (x) => { external_str += '_' + x; return external_str. Length; }; Console. Write. Line(expr("cd")); Console. Write. Line(external_str); Console. Write. Line(expr( "efg")); Console. Write. Line(external_str); ü Вывод : 5 ab_cd 9 ab_cd_efg
Анонимные методы ü. NET Framewok 2. 0 и выше поддерживают анонимные методы. ü Анонимный метод позволяет передать блок кода объемлющего метода как параметр делегата. delegate string My. Delegate ( int j); class Program { static void Main(string[] args) { int j_main = 111; Console. Write. Line(“_1"); My. Delegate anm_1 = delegate(int j) { Console. Write. Line("My. Delegate anm_1"); j_main += 222; return "anm_1 j= " + j + " j_main=" + j_main; }; Console. Write. Line(“_2"); Console. Write. Line(anm_1(123)); Console. Write. Line(anm_1(456)); My. Delegate anm_2 = delegate(int j) { Console. Write. Line("My. Delegate anm_2"); return "anm_2 j= " + j + " j_main=" + j_main; }; Console. Write. Line(anm_2(789)); Console. Write. Line("j_main={0}", j_main); }} Вывод: _1 _2 My. Delegate anm_1 j= 123 j_main=333 My. Delegate anm_1 j= 456 j_main=555 My. Delegate anm_2 j= 789 j_main=555
Анонимные методы. Ограничения ü Анонимные методы упрощают определение (instantiating ) делегатов для событий. ü Ограничения: • Нельзя передавать управление из объемлющего метода в блок анонимного метода (или наоборот) с помощью операторов goto, continue или break. • В блоке анонимного метода можно использовать переменные объемлющего метода. Нельзя использовать значения параметров объемлющего метода с модификаторами ref и out. • В анонимном методе нельзя использовать небезопасный (unsafe) код.
События ü В классе могут быть объявлены события – поля типа delegate. Тип события определяет сигнатуру методов, которые могут быть вызваны как обработчики события. ü В примере в классе Teacher объявлены два события – одно событие имеет тип System. Event. Handler, другое – тип Add. Handler (определенный в приложении). public class Teacher : Person { public event System. Event. Handler Get. List. Event; public static event Add. Handler Add. Student. Event; . . . List<Student> stdlist = new List<Student>(); } ü Другие классы ( объекты других классов) могут подписаться на событие, при подписке на событие указывается метод (обработчик события), который будет вызываться, когда произойдет событие. ü Метод-обработчик должен иметь такую же сигнатуру, что и тип (делегат) события.
Делегаты Event. Handler и Event. Handler<TEvent. Args> ü В базовой библиотеке классов. NET Framework определены делегаты, которые используются как типы событий в классах базовой библиотеки. ü Делегаты для событий имеют два параметра: • первый параметр содержит ссылку на объект – источник события; • второй параметр является производным от класса Event. Args и содержит информацию (связанную с событием), которая передается в обработчик события. ü Делегат System. Event. Handler используется для событий, которые не передают дополнительной информации в обработчик public delegate void Event. Handler( object sender, Event. Args e ); public class Event. Args { public Event. Args(); public static readonly Event. Args Empty; . . . } ü В. NET Framework 2. 0 и выше для событий определен обобщенный делегат public delegate void Event. Handler<TEvent. Args> ( Object sender, TEvent. Args e ) where TEvent. Args : Event. Args
События. Пример ü Объявление пользовательского типа Add. Handler – делегата, который используется для события, и пользовательского типа Add. Handler. Args для второго параметра делегата. public delegate void Add. Handler( object obj, Add. Handler. Args h); public class Add. Handler. Args : Event. Args { Student st; Teacher tch; public Add. Handler. Args( Student st, Teacher tch) { this. st = st; this. tch = tch; } public Teacher { get { return tch; } set { tch = value; } } public Student { get { return st; } set { st = value; } } }
Вызов события ü Вызвать(raise) событие можно только из метода (свойства, индексатора) класса, в котором находится событие. ü Событие Add. Student. Event происходит при добавлении объекта типа Student в список public class Teacher : Person { … public void Add( Student st) { stdlist. Add(st); if (Add. Student. Event != null) Add. Student. Event(this, new Add. Handler. Args(st, this)); } … } ü Событие Get. List. Event происходит при вызове get в свойстве Student. List public class Teacher : Person { … public List<Student> Student. List { get { if (Get. List. Event != null) Get. List. Event(this, Event. Args. Empty); return stdlist; } } … }
Подписка на события ü Любые объекты могут подписаться на событие. ü На статическое событие Add. Student. Event (пользовательский тип Add. Handler ) из класса Teacher подписывается объект типа Students. List list 1 = new Students. List(); Teacher. Add. Student. Event += list 1. Add. Student. Handler; ü В классе Students. List есть метод Add. Student. Handler с сигнатурой делегата Add. Handler public class Students. List { List<Student> stlist = new List<Student>(); public void Add. Student. Handler( object obj, Add. Handler. Args args) { if( !stlist. Contains(args. Student)) stlist. Add(args. Student); } … }
Подписка на события ü На событие Get. List. Event ( тип Event. Handler) из класса Teacher подписывается класс Program tc 1. Get. List. Event += Program. Get. List. Handler; ü В классе Program есть статический метод Get. List. Handler с сигнатурой делегата Event. Handler public static void Get. List. Handler(object obj, Event. Args args) { Teacher tch = obj as Teacher; Console. Write. Line("n Get. List. Handler " + tch. To. Short. String()); }
Inside event ü При компиляции класса, содержащего поле event, в классе создаются : • закрытое поле с соответствующим типом делегата - ссылка на начало списка делегатов, которые будут вызваны как реакция на событие; • метод add_. . . , вызывающий метод Combine() из класса Delegate, для добавления нового делегата в список; • метод remove_. . . , вызывающий метод Remove() из класса Delegate, для удаления делегата из списка. public class Teacher : Person { public event Event. Handler Get. List. Event; public static event Add. Handler Add. Student. Event; . . . } ü В класс Teacher при компиляции будут добавлены поля private Event. Handler Get. List. Event = null; private static Add. Handler Add. Student. Event = null; и методы add_Get. List. Event( Event. Handler handler) {…} remove_Get. List. Event(Event. Handler handler) {…} add_Add. Student. Event( Add. Handler handler) {…} remove_Add. Student. Event(Add. Handler handler) {…}
Свойства события (event properties) ü C# позволяет реализовать событие “вручную”. Для этого необходимо • явно определить поле-делегат для поддержки события; • реализовать методы-аксессоры add и remove. ü Метод add выполняется каждый раз, когда добавляется делегат в цепочку обработчиков для события (подписка на событие), remove – при удалении делегата из цепочки (отказ от подписки). Должны быть определены оба метода add и remove. public delegate void My. Event. Handler(object src, Event. Args e); class My. Control { private My. Event. Handler meh; public event My. Event. Handler my. Control. Event { add { // Console. Write. Line("my. Control. Event add"); meh += value; } remove { // Console. Write. Line("my. Control. Event remove"); meh -= value; } } public void Raise. Event() { if (meh != null) meh (this, Event. Args. Empty); } }
Свойства события. Пример static void Main(string[] args) { My. Control cnt = new My. Control(); cnt. my. Control. Event += new My. Event. Handler(cnt_Event. A); cnt. my. Control. Event += new My. Event. Handler(cnt_Event. B); cnt. Raise. Event(); cnt. my. Control. Event -= new My. Event. Handler(cnt_Event. B); cnt. Raise. Event(); } private static void cnt_Event. A (object src, Event. Args e) { Console. Write. Line("cnt_Event. A"); } private static void cnt_Event. B (object src, Event. Args e) { Console. Write. Line("cnt_Event. B"); } Вывод: My. Control. Event add cnt_Event. A cnt_Event. B My. Control. Event remove cnt_Event. A
События и интерфейсы ü События могут быть членами интерейсов. ü Класс, реализующий интерфейс, должен содержать это событие. ü Если класс реализует два интерфейса с событиями, имеющими одно и то же имя, необходимо явно реализовать свойства события. Пример из документации Microsoft: public delegate void My. Delegate_1(); public delegate int My. Delegate_2(string s); public interface I 1 { event My. Delegate_1 My. Event; } public interface I 2 { event My. Delegate_2 My. Event; } public class Explicit. Events. Sample: I 1, I 2 { public event My. Delegate_1 My. Event; // normal implementation of I 1. My. Event. private My. Delegate_2 My. Event 2 Storage; // underlying storage for I 2. My. Event. event My. Delegate_2 I 2. My. Event // explicit implementation of I 2. My. Event { add { My. Event 2 Storage += value; } remove { My. Event 2 Storage -= value; } } private void Fire. Events() { if (My. Event != null) My. Event(); if (My. Event 2 Storage != null) My. Event 2 Storage("hello"); } }
8_C#_делегаты_события.pptx