Тип delegateТип delegate Объявление делегата определяет ссылочный тип, который является оболочкой для метода с заданной сигнатурой.
public delegate void AlarmHandler(int code);
Возвращаемое значение и параметры хранимого в делегате метода
Имя нового типа
Объект-делегат может инкапсулировать статический или экземплярный метод.
Для экземплярного метода делегат содержит пару <объект, метод>.
Делегаты в С# - это объектно-ориентированная реализация указателей на функцию.
Делегаты поддерживают тип event.
Делегаты. ПримерДелегаты. Пример
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;}
}
delegate string MyDelegate ( int j);
class Program {
static void Main(string[] args){ MyDelegate myd1 = new MyDelegate (Abc.FS); FInMain (myd1, 1);
Abc abc1 = new Abc(55); MyDelegate myd2 = abc1.FI; // 2.0 и выше FInMain (myd2, 2);
Abc abc2 = 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)); }
}
Вывод: FS 1 FI 2 55 FI 89 55 FI 89 77
Класс делегатаКласс делегата При компиляции кода для делегата создается класс с именем типа-делегата и методами для синхронного и асинхронного вызова делегата.
Для делегата MyDelegate из примера создается класс
рrivate sealed class MyDelegate : MulticastDelegate {
public string Invoke ( int j){ … }public System.IAsyncResult BeginInvoke (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.Delegate и System.MulticastDelegate
Класс, который создается при компиляции кода для делегата, является производным от System.MulticastDelegate.
System.Object System.Delegate System.MulticastDelegate
Некоторые методы класса System.Delegate:
public abstract class Delegate : ICloneable, ISerializable
{...
public MethodInfo Method {get;}
public object Target {get;} // объект, для которого делегат (объект-
// делегат) вызывает экземплярный метод, null для статического метода
}
Класс System.Delegate. ПримерКласс System.Delegate. Пример
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;}}
delegate string MyDelegate ( int j);
class Program {
static void Main(string[] args){ Abc abc1 = 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)); }
}
Вывод: Abc object n=55 FI 2 55
Цепочки делегатовЦепочки делегатов
В классе System.MulticastDelegate поддерживается список вызовов (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).
Цепочки делегатов. ПримерЦепочки делегатов. Пример
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
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;}
}
Covariance and Contravariance в делегатах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 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; ..... }}
Анонимные методы (Anonymous Methods )Анонимные методы (Anonymous Methods ) .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; } }
Обобщенные делегаты можно определить вне класса. Делегаты, определенные в обобщенном классе, могут использовать обобщенные параметры класса и/или иметь свои собственные обобщенные параметры.
Обобщенные делегаты. ПримерОбобщенные делегаты. Пример
В пространстве имен System определены обобщенные делегаты
public static bool Exists<T> ( T[] array, Predicate<T> match );
public static bool TrueForAll<T> ( T[] array, Predicate<T> match );
public delegate TOutput Converter<TInput,TOutput> ( TInput input )
public delegate bool Predicate<T> (T obj);
public delegate TOutput Converter<TInput,TOutput> ( TInput input );
В классе System.Array определены методы, проверяющие выполнение условий, определенных в заданном предикате, для элементов массива.
Обобщенный метод ConvertAll в классе System.Array преобразует массива одного типа в массив другого типа.
public static TOutput[] ConvertAll <TInput,TOutput> ( TInput[] array, Converter<TInput,TOutput> converter ) ;
Обобщенные делегаты. ПримерОбобщенные делегаты. Пример
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"); }
}
В приведенном ниже примере проверяется наличие символа ‘a’ в элементах массива и выполняется преобразование массива типа string[] в массив типа StringBuilder[].
СобытияСобытия
public class Teacher : Person
{ public event EventHandler GetListEvent;
public static event AddHandler AddStudentEvent;
...
List<Student> stdlist = new List<Student>();
}
В классе могут быть объявлены события - поля данных типа delegate
Делегат EventHandler объявлен в BCL в пространстве имен System и используется для событий, которые не передают дополнительной информации в обработчик
public delegate void EventHandler( object sender, EventArgs e );
public class EventArgs { public EventArgs(); public static readonly EventArgs Empty; ...}
СобытияСобытия
Делегат 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 eventInside event При компиляции класса, содержащего поле event, в классе создаются :
• закрытое поле с соответствующим типом делегата - ссылка на начало списка делегатов, которые будут вызваны как реакция на событие;
• метод add_... , вызывающий метод Combine() из класса Delegate, для добавления нового делегата в список;
• метод remove_..., вызывающий метод Remove() из класса Delegate, для удаления делегата из списка.
В класс 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) {…}
public class Teacher : Person{ public event EventHandler GetListEvent; public static event AddHandler AddStudentEvent; ...}
События в стиле .NETСобытия в стиле .NET
public delegate void MouseEventHandler ( object sender, MouseEventArgs e );
public class Control : Component, ISynchronizeInvoke, IWin32Window { public event MouseEventHandler MouseDown; public event MouseEventHandler MouseUp; public event MouseEventHandler MouseMove; ...}
public class MouseEventArgs : EventArgs { … }
В базовой библиотеке классов .NET Framework все обработчики событий принимают два параметра:
• через первый параметр передается объект – источник события;• второй параметр является производным от класса EventArgs и содержит информацию, связанную с событием.
Например,
Делегаты EventHandler и EventHandler<TEventArgs> Делегаты EventHandler и EventHandler<TEventArgs>
public delegate void EventHandler( object sender, EventArgs e );
public class EventArgs
{ public EventArgs();
public static readonly EventArgs Empty;
...
}
Для событий, которые не передают дополнительной информации, в BCL определен делегат EventHandler.
public delegatevoid EventHandler<TEventArgs> ( Object sender, TEventArgs e )
where TEventArgs : EventArgs
В .NET Framework 2.0 и выше для обработчиков событий определен обобщенный делегат
Свойства события (event properties)Свойства события (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 addMyControlEvent addcnt_EventAcnt_EventBMyControlEvent removecnt_EventA
События и интерфейсыСобытия и интерфейсы
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");}
}
События могут быть членами интерейсов. Класс, реализующий интерфейс, должен содержать это событие. Если класс реализует два интерфейса с событиями, имеющими одно и то же имя, необходимо явно реализовать свойства события.
Пример из документации Microsoft:
Top Related