240 likes | 522 Views
Тип delegate. Объявление делегата определяет ссылочный тип, который является оболочкой для метода с заданной сигнатурой. public delegate void AlarmHandler(int code);. Возвращаемое значение и параметры хранимого в делегате метода. Имя нового типа.
E N D
Тип delegate • Объявление делегата определяет ссылочный тип, который является оболочкой для метода с заданной сигнатурой. public delegate void AlarmHandler(int code); Возвращаемое значение и параметры хранимого в делегате метода Имя нового типа • Объект-делегат может инкапсулировать статический или экземплярный метод. • Для экземплярного метода делегат содержит пару <объект, метод>. • Делегаты в С# - это объектно-ориентированная реализация указателей на функцию. • Делегаты поддерживают тип event.
Делегаты. Пример delegate string MyDelegate ( int j); class Program { static void Main(string[] args) { MyDelegate myd1 = new MyDelegate (Abc.FS); FInMain (myd1, 1); Abcabc1 = new Abc(55); MyDelegate myd2 = abc1.FI; // 2.0 и выше FInMain (myd2, 2); Abcabc2 = new Abc(77); MyDelegate myd3 = abc2.FI; MyDelegate[] arr = new MyDelegate[2] {myd2, myd3}; foreach ( MyDelegate f in arr) FInMain (f, 89); } static void FInMain (MyDelegate dlgt, int j) { Console.WriteLine ( dlgt(j));} } class Abc{ int dt; public Abc( int n) { dt = n; } public string FI (int j) { return "FI" + j + "" + dt; } public static string FS (int j) { return "FS" + j; } } Вывод: FS 1 FI 2 55 FI 89 55 FI 89 77
Класс делегата • При компиляции кода для делегата создается класс с именем типа-делегата и методами для синхронного и асинхронного вызова делегата. • Для делегата MyDelegate из примера создается класс рrivate sealed class MyDelegate : MulticastDelegate { public string Invoke (int j) { … } public System.IAsyncResultBeginInvoke (int j, System.AsyncCallback, object) { … } public string EndInvoke( System.IAsyncResult obj) { … } } • Метод-делегат может быть вызван как синхронно ( возврат не произойдет до того, как метод-делегат завершит свою работу) , так и асинхронно ( метод сразу возвращает управление). • Program Files\\ Microsoft Visual Studio 8 \\ SDK \\ v2.0\\ Bin \\ ildasm.exe • Дж. Рихтер - Программирование на платформе Microsoft .NET Framework, гл. 17
КлассыSystem.Delegateи System.MulticastDelegate • Класс, который создается при компиляции кода для делегата, является производным от System.MulticastDelegate. System.ObjectSystem.DelegateSystem.MulticastDelegate • Некоторые методы класса System.Delegate: public abstract class Delegate : ICloneable, ISerializable {... public MethodInfo Method {get;} public object Target {get;} // объект, для которого делегат (объект- // делегат) вызывает экземплярный метод, null для статического метода }
КлассSystem.Delegate. Пример delegate string MyDelegate ( int j); class Program { static void Main(string[] args) { Abcabc1 = new Abc(55); MyDelegate myd2 = abc1.FI; FInMain (myd2, 2); } static void FInMain (MyDelegate dlgt, int j) { Console.WriteLine ( dlgt.Target); Console.WriteLine ( dlgt(j)); } } class Abc{ int dt; public Abc( int n) { dt = n; } public override string ToString() { return "Abc object n=" + dt; } public string FI (int j) { return "FI" + j+ "" + dt; } public static string FS (int j) { return "FS" + j; }} Вывод: Abc object n=55 FI 2 55
Цепочки делегатов • В классе System.MulticastDelegate поддерживается список вызовов (invocation list), который дает возможность создавать цепочки делегатов. • Цепочки делегатов обычно используются при обработке событий. • В классе System.Delegate есть методы для добавления и удаления делегатов из списка: publicstaticDelegateCombine( Delegatea, Delegateb ); publicstaticDelegateRemove( Delegatesource, Delegatevalue ); • В C# определены операции для += и -= для добавления и удаления делегатов из цепочки (вызывают Delegate.Combine и Delegate.Remove).
Цепочки делегатов. Пример class Abc{ int dt; public Abc( int n) { dt = n; } public string FI (int j) { Console.WriteLine(“FI”); return "FI" + j + "" + dt; } public static string FS (int j) { Console.WriteLine(“FS”); return "FS" + j; } } delegate string MyDelegate ( int j); class Class1{ static void Main(string[] args) { MyDelegate myd1 = new MyDelegate (Abc.FS); Abc abc1 = new Abc(11); MyDelegate myd2 = new MyDelegate (abc1.FI); Abc abc2 = new Abc(22); MyDelegate myd3 = new MyDelegate (abc2.FI); myd1 += myd2; myd1 += myd3; FInMain (myd1, 555); } static void FInMain (MyDelegate dlgt, int j) { Console.WriteLine ( dlgt(j));} } Вывод: FS FI FI FI 555 22
CovarianceandContravariance в делегатах • В .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 F1() { return new Bs(); } public static Dr F2() { return new Dr(); } public static void F3(Bs bs) { Console.WriteLine(“F3"); } } class Program { static void Main() { // Covariance Delegate_1 dlg1 = Test.F2; // Contravariance Delegate_2 dlg2 = Test.F3; ..... } }
Анонимные методы (AnonymousMethods ) • .NET Framewok 2.0 и выше поддерживают анонимные методы. • Анонимный метод позволяет передать блок кода объемлющего метода как параметр делегата. delegate string MyDelegate ( int j); class Program { static void Main(string[] args) {int jmain = 111; Console.WriteLine("1"); MyDelegate anm1 = delegate(int j) { Console.WriteLine("MyDelegate anm1"); jmain += 222; return "anm1 j= " + j + " jmain=" + jmain; }; Console.WriteLine("2"); Console.WriteLine(anm1(123)); Console.WriteLine(anm1(456)); MyDelegate anm2 = delegate(int j) { Console.WriteLine("MyDelegate anm2"); return "anm2 j= " + j + " jmain=" + jmain; }; Console.WriteLine(anm2(789)); Console.WriteLine("jmain={0}", jmain); } Вывод: 1 2 MyDelegate anm1 anm1 j= 123 jmain=333 MyDelegate anm1 anm1 j= 456 jmain=555 MyDelegate anm2 anm2 j= 789 jmain=555 jmain=555
Анонимные методы. Ограничения • Анонимные методы упрощают определение (instantiating ) делегатов для событий. • Ограничения: • Нельзя передавать управление из объемлющего метода в блок анонимного метода (или наоборот) с помощью операторов goto, continue или break. • В блоке анонимного метода можно использовать переменные объемлющего метода. Нельзя использовать значения параметров объемлющего метода с модификаторами ref и out. • В анонимном методе нельзя использовать небезопасный (unsafe) код.
Обобщенные делегаты • Обобщенные делегаты можно определить вне класса. • Делегаты, определенные вобобщенном классе, могут использовать обобщенные параметры класса и/или иметь свои собственные обобщенные параметры. public delegate void GenericDelegate<T>(T t); public class MyClass1<T> { public delegate void GenericDelegate_1(T t); public delegate void GenericDelegate_2 <X> (T t, X x) where X : IComparable<X>; } public class MyClass2 { public static void FS<T>(T t) { Console.WriteLine("FS<T>(T t)"); } public static void FS1(string t) { Console.WriteLine("FS1(string t)"); } public static void FS2(string t, int j) { Console.WriteLine("FS2(string t, int j)"); } } class Program { static void Main(string[] args) { GenericDelegate<string> dlg1 = MyClass2.FS; GenericDelegate<string> dlg2 = MyClass2.FS1; MyClass1<string>.GenericDelegate_1 dlg3 = MyClass2.FS1; MyClass1<string>.GenericDelegate_2<int> dlg4 = MyClass2.FS2; } }
public delegate TOutput Converter<TInput,TOutput> ( TInput input ) Обобщенные делегаты. Пример • В пространстве имен System определены обобщенные делегаты public delegate boolPredicate<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 TrueForAll<T> (T[] array, Predicate<T> match ); • Обобщенный метод ConvertAll в классе System.Array преобразует массива одного типа в массив другого типа. public static TOutput[] ConvertAll<TInput,TOutput> ( TInput[] array, Converter<TInput,TOutput> converter ) ;
Обобщенные делегаты. Пример • В приведенном ниже примере проверяется наличие символа ‘a’ в элементах массива и выполняется преобразование массива типа string[] в массив типа StringBuilder[]. class Program { static void Main(string[] args) { string[] str = new string[] { "abc", "efg", "asd" }; Predicate<string> pred = Program.ContainsA; Console.WriteLine(System.Array.Exists<string>(str, pred));// true Console.WriteLine(System.Array.TrueForAll<string>(str, pred)); // false // Convert array type StringBuilder[] sb = Array.ConvertAll<string, StringBuilder> (str, Program.MySbConverter); } public static StringBuilder MySbConverter(string str) { return new StringBuilder(str); } public static bool ContainsA(string str) { return str.Contains("a"); } }
События • В классе могут быть объявлены события - поля данных типа delegate public class Teacher : Person { public event EventHandler GetListEvent; public static event AddHandler AddStudentEvent; ... List<Student> stdlist = new List<Student>(); } • Делегат EventHandler объявлен в BCL в пространстве имен System и используется для событий, которые не передают дополнительной информации в обработчик publicdelegatevoidEventHandler( objectsender, EventArgse ); publicclassEventArgs { publicEventArgs(); publicstaticreadonlyEventArgsEmpty; ... }
События • Делегат AddHandler – пользовательский тип public delegate void AddHandler( object obj, AddHandlerArgs h); public class AddHandlerArgs : EventArgs { Student st; Teacher tch; public AddHandlerArgs( Student st, Teacher tch) { this.st = st; this.tch = tch; } public Teacher Teacher { get { return tch; } set { tch = value; } } public Student Student { get { return st; } set { st = value; } } }
Вызов события • Вызвать(raise) событие можно только из класса, в котором находится событие. • Событие AddStudentEvent происходит при добавлении объекта типа Student в список public class Teacher : Person { … public void Add( Student st) { stdlist.Add(st); if (AddStudentEvent != null) AddStudentEvent(this, new AddHandlerArgs(st, this)); } … } • Событие GetListEvent происходит при вызове get в свойстве StudentList public class Teacher : Person { … public List<Student> StudentList { get { if (GetListEvent != null) GetListEvent(this, EventArgs.Empty); return stdlist; } } … }
Подписка на события • Любые объекты могут подписаться на событие. • На статическое событие AddStudentEvent (пользовательский тип AddHandler ) из класса Teacher подписывается объект типа StudentsList StudentsList list1 = new StudentsList(); Teacher.AddStudentEvent += list1.AddStudentHandler; • В классе StudentsList есть метод AddStudentHandler с сигнатурой делегата AddHandler public class StudentsList { List<Student> stlist = new List<Student>(); public void AddStudentHandler( object obj, AddHandlerArgs args) { if( !stlist.Contains(args.Student)) stlist.Add(args.Student); } … }
Подписка на события • На событие GetListEvent ( тип EventHandler) из класса Teacher подписывается класс Program tc1.GetListEvent += Program.GetListHandler; • В классе Program есть статический метод GetListHandler с сигнатурой делегата EventHandler public static void GetListHandler(object obj, EventArgs args) { Teacher tch = obj as Teacher; Console.WriteLine("\n GetListHandler " + tch.ToShortString()); }
Inside event • При компиляции класса, содержащего поле event, в классе создаются : • закрытое поле с соответствующим типом делегата - ссылка на начало списка делегатов, которые будут вызваны как реакция на событие; • метод add_... , вызывающий метод Combine() из класса Delegate, для добавления нового делегата в список; • метод remove_..., вызывающий метод Remove() из класса Delegate, для удаления делегата из списка. public class Teacher : Person { public event EventHandler GetListEvent; public static event AddHandler AddStudentEvent; ... } • В класс Teacher при компиляции будут добавлены поля private EventHandler GetListEvent = null; private static AddHandler AddStudentEvent = null; и методы add_GetListEvent( EventHandler handler) {…} remove_GetListEvent(EventHandler handler) {…} add_AddStudentEvent( AddHandler handler) {…} remove_AddStudentEvent(AddHandler handler) {…}
Событияв стиле .NET • В базовой библиотеке классов .NET Framework все обработчики событий принимают два параметра: • через первый параметр передается объект – источник события; • второй параметр является производным от класса EventArgs и содержит информацию, связанную с событием. • Например, publicdelegatevoidMouseEventHandler( objectsender,MouseEventArgse ); publicclassControl : Component, ISynchronizeInvoke, IWin32Window { publiceventMouseEventHandlerMouseDown; publiceventMouseEventHandlerMouseUp; publiceventMouseEventHandlerMouseMove; ... } publicclassMouseEventArgs : EventArgs{ … }
Делегаты EventHandler иEventHandler<TEventArgs> • Для событий, которые не передают дополнительной информации, в BCL определен делегат EventHandler. publicdelegatevoidEventHandler( objectsender, EventArgse ); publicclassEventArgs { publicEventArgs(); publicstaticreadonlyEventArgsEmpty; ... } • В .NET Framework 2.0 и выше для обработчиков событий определен обобщенный делегат publicdelegate voidEventHandler<TEventArgs> ( Objectsender, TEventArgse ) whereTEventArgs : EventArgs
Свойства события (event properties) • C# позволяет реализовать событие “вручную”. Для этого необходимо • явно определить поле-делегат для поддержки события; • реализовать методы-аксессоры add и remove. • Метод add выполняетсякаждый раз, когда добавляется делегат в цепочку обработчиков для события (подписка на событие), remove – при удалении делегата из цепочки (отказ от подписки). Должны быть определены оба метода add и remove. public delegate void MyEventHandler(object src, EventArgs e); class MyControl {private MyEventHandler meh; public event MyEventHandler myControlEvent { add { // Console.WriteLine("myControlEvent add"); meh += value; } remove { // Console.WriteLine("myControlEvent remove"); meh -= value; } } public void RaiseEvent() { if (meh != null) meh (this, EventArgs.Empty); } }
Свойства события. Пример static void Main(string[] args) {MyControl cnt = new MyControl(); cnt.myControlEvent += new MyEventHandler(cnt_EventA); cnt.myControlEvent += new MyEventHandler(cnt_EventB); cnt.RaiseEvent(); cnt.myControlEvent -= new MyEventHandler(cnt_EventB); cnt.RaiseEvent(); } private static void cnt_EventA (object src, EventArgs e) { Console.WriteLine("cnt_EventA");} private static void cnt_EventB (object src, EventArgs e) { Console.WriteLine("cnt_EventB");} Вывод: MyControlEvent add MyControlEvent add cnt_EventA cnt_EventB MyControlEvent remove cnt_EventA
События могут быть членами интерейсов. • Класс, реализующий интерфейс, должен содержать это событие. • Если класс реализует два интерфейса с событиями, имеющими одно и то же имя, необходимо явно реализовать свойства события. Пример из документации Microsoft: События и интерфейсы public delegate void MyDelegate_1(); public delegate int MyDelegate_2(string s); public interface I1 { event MyDelegate_1 MyEvent;} public interface I2 {event MyDelegate_2 MyEvent;} public class ExplicitEventsSample: I1, I2 {public event MyDelegate_1 MyEvent; // normal implementation of I1.MyEvent. private MyDelegate_2 MyEvent2Storage; // underlying storage for I2.MyEvent. event MyDelegate_2 I2.MyEvent // explicit implementation of I2.MyEvent {add { MyEvent2Storage += value;} remove { MyEvent2Storage -= value;} } private void FireEvents() {if (MyEvent != null) MyEvent(); if (MyEvent2Storage != null) MyEvent2Storage("hello"); } }