При подготовке написания статьи возникла мысль скомпоновать цельный материал по делегатам и событиям, но ввиду своей объемности он мог бы запутать как начинающих программистов, так и опытных разработчиков. Итак, решил разбить материал на две статьи, в которых поделюсь базой знаний о данных понятиях:
- Использования делегатов;
- Использование событий.
Класс Delegate является базовым для типов делегатов. Однако только система и компиляторы могут
явно наследовать классы Delegate и MulticastDelegate. Мы также не можем
создать свой тип, наследуемый от классов Delegate и MulticastDelegate. В C# для того, чтобы создать делегат, нужно
использовать ключевое слово delegate, благодаря ему компилятор языка может
создавать классы, производные от MulticastDelegate. Поэтому разработчикам везде нужно
использовать это ключевое слово. Среда выполнения предоставляет такие методы
для выполнения делегата, какInvoke для синхронного выполнения запроса, а также методы BeginInvoke и EndInvoke для возможности асинхронного вызова делегата. Подробнее об этом можно прочитать здесь (поскольку эта тема очень обширная, она не включена в эту
статью). Вызывать метод Invoke не обязательно, так как исполнительная
среда вызывает этот метод автоматически. Этот метод используется для того,
чтобы с помощью рефлексии получить сигнатуру типа делегата. Хотя вы можете
вызывать этот метод явно. Ничего плохого после этого не произойдет. Делегат — это экземпляр типа делегата, содержащий указатели на:
- метод экземпляра типа и целевой объект, который можно присваивать переменной этого типа;
- метод экземпляра типа со скрытым параметром this, доступным в списке формальных параметров. Такой делегат называется открытым делегатом экземпляра;
- статический метод;
- статический метод и целевой объект, который можно присвоить первому параметру этого метода. Такой делегат называется закрытым в отношении своего первого аргумента. (Источник)
Примечание: на платформе .NET Framework версий 1.0 и 1.1 делегат может представляет
метод только в том случае, если сигнатура этого метода точно соответствует
сигнатуре, определенной типом делегата. Поэтому поддерживаются только первый и
третий элементы приведенного выше списка, а первый элемент требует точного
соответствия типов.
Для примера создадим делегат, который будем использовать
для логирования текста. Похожий пример Вы можете увидеть в книге «CLR via C#» Джефри Рихтера.
class Program
{
public delegate void LoginDelegate(string message);
static void Main(string[] args)
{
var program
= new Program();
var
logToConsoleDelegate = new LoginDelegate(LogToConsole);
var
logToFileDelegate = new LoginDelegate(program.LogToFile);
var
logToEventLogDelegate = new LoginDelegate(program.LogToEventLog);
var
logToMessageBoxDelegate = new LoginDelegate(program.LogToMessageBox);
//Проверим роботу делегатов
logToConsoleDelegate("TestConsoleDelegate");
logToFileDelegate("TestFileDelegate");
logToEventLogDelegate("TestEventLogDelegate");
logToMessageBoxDelegate("TestMessageBoxDelegate");
Console.WriteLine("---------------------------------------------------------");
//Пример использования цепочки делегатов
var
chainDelegate = (LoginDelegate)Delegate.Combine(logToConsoleDelegate, logToFileDelegate);
chainDelegate
= (LoginDelegate)Delegate.Combine(chainDelegate, logToEventLogDelegate);
chainDelegate
= (LoginDelegate)Delegate.Combine(chainDelegate, logToMessageBoxDelegate);
chainDelegate("TestChainDelegate");
Console.WriteLine("---------------------------------------------------------");
//Удаляем вывод в EventLog и MessageBox
chainDelegate
= (LoginDelegate)Delegate.Remove(chainDelegate, logToEventLogDelegate);
chainDelegate
= (LoginDelegate)Delegate.Remove(chainDelegate, logToMessageBoxDelegate);
chainDelegate("Test Remove From ChainDelegate");
Console.WriteLine("---------------------------------------------------------");
chainDelegate
+= delegate(string message) { Console.WriteLine("Call Custom Delegate {0}",
message); };
chainDelegate("Add anonimous delegate");
Console.WriteLine("---------------------------------------------------------");
Console.ReadLine();
}
public static void
LogToConsole(string message)
{
Console.WriteLine("Call
LoginToConsole {0}",message);
Console.WriteLine(message);
}
public void
LogToFile(string message)
{
Console.WriteLine("Call
LoginToFile {0}", message);
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "test.txt"), message);
}
public void
LogToMessageBox(string message)
{
Console.WriteLine("Call
LoginToMessageBox {0}", message);
MessageBox.Show(message);
}
public void
LogToEventLog(string message)
{
Console.WriteLine("Call
LoginToEventLog {0}", message);
var appLog
= new EventLog
{
Source
= "ConsoleApplicationTest.LoginDelegate"
};
appLog.WriteEntry(message,
EventLogEntryType.Information);
}
}
После запуска примера можно увидеть принцип работы
делегатов. О том, как работает цепочка делегатов, а также о понятии "анонимные делегаты" Вы узнаете немного позже. А сейчас я перепишу пример с использованием переопределенных операций += и -= вместо Delegate.Combine и Delegate.Remove. И
перепишу вызов делегата через лямбда-выражение.
static void Main(string[] args)
{
var program
= new Program();
var
logToConsoleDelegate = new LoginDelegate(LogToConsole);
var
logToFileDelegate = new LoginDelegate(program.LogToFile);
var
logToEventLogDelegate = new LoginDelegate(program.LogToEventLog);
var
logToMessageBoxDelegate = new LoginDelegate(program.LogToMessageBox);
//Проверим роботу делегатов
logToConsoleDelegate("TestConsoleDelegate");
logToFileDelegate("TestFileDelegate");
logToEventLogDelegate("TestEventLogDelegate");
logToMessageBoxDelegate("TestMessageBoxDelegate");
Console.WriteLine("---------------------------------------------------------");
//Пример использования цепочки
делегатов
var
chainDelegate = logToConsoleDelegate;
chainDelegate
+= logToFileDelegate;
chainDelegate
+= logToEventLogDelegate;
chainDelegate
+= logToMessageBoxDelegate;
chainDelegate("TestChainDelegate");
Console.WriteLine("---------------------------------------------------------");
//Удаляем вывод в EventLog и MessageBox
chainDelegate
-= logToEventLogDelegate;
chainDelegate
-= logToMessageBoxDelegate;
chainDelegate("Test Remove From
ChainDelegate");
Console.WriteLine("---------------------------------------------------------");
chainDelegate
+= message => Console.WriteLine("Call
Custom Delegate {0}", message);
chainDelegate("Add anonimous delegate");
Console.WriteLine("---------------------------------------------------------");
Console.ReadLine();
}
Как видим, текст немного упростился. Для того, чтобы понять, как работает Delegate.Combine и Delegate.Remove,
можно обратиться к книге авторитетного автора — Jon Skeet, “C# in Depth”. Источник . Вероятно, это одна из тех книг, которые каждый уважающий себя разработчик обязан прочитать. В этом источнике есть таблица, иллюстрирующая работу Combine и Remove.
Expression
|
Result
|
null + d1
|
d1
|
d1 + null
|
d1
|
d1 + d2
|
[d1, d2]
|
d1 + [d2, d3]
|
[d1, d2, d3]
|
[d1, d2] + [d2, d3]
|
[d1, d2, d2, d3]
|
[d1, d2] - d1
|
d2
|
[d1, d2] - d2
|
d1
|
[d1, d2, d1] - d1
|
[d1, d2]
|
[d1, d2, d3] - [d1, d2]
|
d3
|
[d1, d2, d3] - [d2, d1]
|
[d1, d2, d3]
|
[d1, d2, d3, d1, d2] -
[d1, d2]
|
[d1, d2, d3]
|
[d1, d2] - [d1, d2]
|
null
|
Уделим немного
внимания сложностям, которые могут возникнуть при использовании делегатов.
Также позвольте привести некоторые примеры использования делегатов, которые вводят в когнитивный диссонанс
неподготовленных разработчиков. А сейчас вернемся к тому, как переписанный
пример работает на уровне IL кода, чтобы сразу объяснить, что такое
анонимный делегат и зачем использовать лямбда-выражения. Поскольку нас
интересует, по сути, только метод Main, посмотрим, как он реализуется на уровне IL кода.
// Methods
.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2060
// Code size 324 (0x144)
.maxstack 3
.entrypoint
.locals init (
[0] class ConsoleApplicationTest.Program program,
[1] class ConsoleApplicationTest.Program/LoginDelegate logToConsoleDelegate,
[2] class ConsoleApplicationTest.Program/LoginDelegate logToFileDelegate,
[3] class ConsoleApplicationTest.Program/LoginDelegate logToEventLogDelegate,
[4] class ConsoleApplicationTest.Program/LoginDelegate logToMessageBoxDelegate,
[5] class ConsoleApplicationTest.Program/LoginDelegate chainDelegate
)
IL_0000: nop
IL_0001: newobj instance void ConsoleApplicationTest.Program::.ctor()
IL_0006: stloc.0
IL_0007: ldnull
IL_0008: ldftn void ConsoleApplicationTest.Program::LogToConsole(string)
IL_000e: newobj instance void ConsoleApplicationTest.Program/LoginDelegate::.ctor(object, native int)
IL_0013: stloc.1
IL_0014: ldloc.0
IL_0015: ldftn instance void ConsoleApplicationTest.Program::LogToFile(string)
IL_001b: newobj instance void ConsoleApplicationTest.Program/LoginDelegate::.ctor(object, native int)
IL_0020: stloc.2
IL_0021: ldloc.0
IL_0022: ldftn instance void ConsoleApplicationTest.Program::LogToEventLog(string)
IL_0028: newobj instance void ConsoleApplicationTest.Program/LoginDelegate::.ctor(object, native int)
IL_002d: stloc.3
IL_002e: ldloc.0
IL_002f: ldftn instance void ConsoleApplicationTest.Program::LogToMessageBox(string)
IL_0035: newobj instance void ConsoleApplicationTest.Program/LoginDelegate::.ctor(object, native int)
IL_003a: stloc.s logToMessageBoxDelegate
IL_003c: ldloc.1
IL_003d: ldstr "TestConsoleDelegate"
IL_0042: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
IL_0047: nop
IL_0048: ldloc.2
IL_0049: ldstr "TestFileDelegate"
IL_004e: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
IL_0053: nop
IL_0054: ldloc.3
IL_0055: ldstr "TestEventLogDelegate"
IL_005a: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
IL_005f: nop
IL_0060: ldloc.s logToMessageBoxDelegate
IL_0062: ldstr "TestMessageBoxDelegate"
IL_0067: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
IL_006c: nop
IL_006d: ldstr "---------------------------------------------------------"
IL_0072: call void [mscorlib]System.Console::WriteLine(string)
IL_0077: nop
IL_0078: ldloc.1
IL_0079: stloc.s chainDelegate
IL_007b: ldloc.s chainDelegate
IL_007d: ldloc.2
IL_007e: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_0083: castclass ConsoleApplicationTest.Program/LoginDelegate
IL_0088: stloc.s chainDelegate
IL_008a: ldloc.s chainDelegate
IL_008c: ldloc.3
IL_008d: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_0092: castclass ConsoleApplicationTest.Program/LoginDelegate
IL_0097: stloc.s chainDelegate
IL_0099: ldloc.s chainDelegate
IL_009b: ldloc.s logToMessageBoxDelegate
IL_009d: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_00a2: castclass ConsoleApplicationTest.Program/LoginDelegate
IL_00a7: stloc.s chainDelegate
IL_00a9: ldloc.s chainDelegate
IL_00ab: ldstr "TestChainDelegate"
IL_00b0: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
IL_00b5: nop
IL_00b6: ldstr "---------------------------------------------------------"
IL_00bb: call void [mscorlib]System.Console::WriteLine(string)
IL_00c0: nop
IL_00c1: ldloc.s chainDelegate
IL_00c3: ldloc.3
IL_00c4: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_00c9: castclass ConsoleApplicationTest.Program/LoginDelegate
IL_00ce: stloc.s chainDelegate
IL_00d0: ldloc.s chainDelegate
IL_00d2: ldloc.s logToMessageBoxDelegate
IL_00d4: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_00d9: castclass ConsoleApplicationTest.Program/LoginDelegate
IL_00de: stloc.s chainDelegate
IL_00e0: ldloc.s chainDelegate
IL_00e2: ldstr "Test Remove From ChainDelegate"
IL_00e7: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
IL_00ec: nop
IL_00ed: ldstr "---------------------------------------------------------"
IL_00f2: call void [mscorlib]System.Console::WriteLine(string)
IL_00f7: nop
IL_00f8: ldloc.s chainDelegate
IL_00fa: ldsfld class ConsoleApplicationTest.Program/LoginDelegate ConsoleApplicationTest.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_00ff: brtrue.s IL_0114
IL_0101: ldnull
IL_0102: ldftn void ConsoleApplicationTest.Program::'<Main>b__0'(string)
IL_0108: newobj instance void ConsoleApplicationTest.Program/LoginDelegate::.ctor(object, native int)
IL_010d: stsfld class ConsoleApplicationTest.Program/LoginDelegate ConsoleApplicationTest.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0112: br.s IL_0114
IL_0114: ldsfld class ConsoleApplicationTest.Program/LoginDelegate ConsoleApplicationTest.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0119: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_011e: castclass ConsoleApplicationTest.Program/LoginDelegate
IL_0123: stloc.s chainDelegate
IL_0125: ldloc.s chainDelegate
IL_0127: ldstr "Add anonimous delegate"
IL_012c: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
IL_0131: nop
IL_0132: ldstr "---------------------------------------------------------"
IL_0137: call void [mscorlib]System.Console::WriteLine(string)
IL_013c: nop
IL_013d: call string [mscorlib]System.Console::ReadLine()
IL_0142: pop
IL_0143: ret
} // end of method Program::Main
.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2060
// Code size 324 (0x144)
.maxstack 3
.entrypoint
.locals init (
[0] class ConsoleApplicationTest.Program program,
[1] class ConsoleApplicationTest.Program/LoginDelegate logToConsoleDelegate,
[2] class ConsoleApplicationTest.Program/LoginDelegate logToFileDelegate,
[3] class ConsoleApplicationTest.Program/LoginDelegate logToEventLogDelegate,
[4] class ConsoleApplicationTest.Program/LoginDelegate logToMessageBoxDelegate,
[5] class ConsoleApplicationTest.Program/LoginDelegate chainDelegate
)
IL_0000: nop
IL_0001: newobj instance void ConsoleApplicationTest.Program::.ctor()
IL_0006: stloc.0
IL_0007: ldnull
IL_0008: ldftn void ConsoleApplicationTest.Program::LogToConsole(string)
IL_000e: newobj instance void ConsoleApplicationTest.Program/LoginDelegate::.ctor(object, native int)
IL_0013: stloc.1
IL_0014: ldloc.0
IL_0015: ldftn instance void ConsoleApplicationTest.Program::LogToFile(string)
IL_001b: newobj instance void ConsoleApplicationTest.Program/LoginDelegate::.ctor(object, native int)
IL_0020: stloc.2
IL_0021: ldloc.0
IL_0022: ldftn instance void ConsoleApplicationTest.Program::LogToEventLog(string)
IL_0028: newobj instance void ConsoleApplicationTest.Program/LoginDelegate::.ctor(object, native int)
IL_002d: stloc.3
IL_002e: ldloc.0
IL_002f: ldftn instance void ConsoleApplicationTest.Program::LogToMessageBox(string)
IL_0035: newobj instance void ConsoleApplicationTest.Program/LoginDelegate::.ctor(object, native int)
IL_003a: stloc.s logToMessageBoxDelegate
IL_003c: ldloc.1
IL_003d: ldstr "TestConsoleDelegate"
IL_0042: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
IL_0047: nop
IL_0048: ldloc.2
IL_0049: ldstr "TestFileDelegate"
IL_004e: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
IL_0053: nop
IL_0054: ldloc.3
IL_0055: ldstr "TestEventLogDelegate"
IL_005a: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
IL_005f: nop
IL_0060: ldloc.s logToMessageBoxDelegate
IL_0062: ldstr "TestMessageBoxDelegate"
IL_0067: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
IL_006c: nop
IL_006d: ldstr "---------------------------------------------------------"
IL_0072: call void [mscorlib]System.Console::WriteLine(string)
IL_0077: nop
IL_0078: ldloc.1
IL_0079: stloc.s chainDelegate
IL_007b: ldloc.s chainDelegate
IL_007d: ldloc.2
IL_007e: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_0083: castclass ConsoleApplicationTest.Program/LoginDelegate
IL_0088: stloc.s chainDelegate
IL_008a: ldloc.s chainDelegate
IL_008c: ldloc.3
IL_008d: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_0092: castclass ConsoleApplicationTest.Program/LoginDelegate
IL_0097: stloc.s chainDelegate
IL_0099: ldloc.s chainDelegate
IL_009b: ldloc.s logToMessageBoxDelegate
IL_009d: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_00a2: castclass ConsoleApplicationTest.Program/LoginDelegate
IL_00a7: stloc.s chainDelegate
IL_00a9: ldloc.s chainDelegate
IL_00ab: ldstr "TestChainDelegate"
IL_00b0: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
IL_00b5: nop
IL_00b6: ldstr "---------------------------------------------------------"
IL_00bb: call void [mscorlib]System.Console::WriteLine(string)
IL_00c0: nop
IL_00c1: ldloc.s chainDelegate
IL_00c3: ldloc.3
IL_00c4: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_00c9: castclass ConsoleApplicationTest.Program/LoginDelegate
IL_00ce: stloc.s chainDelegate
IL_00d0: ldloc.s chainDelegate
IL_00d2: ldloc.s logToMessageBoxDelegate
IL_00d4: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_00d9: castclass ConsoleApplicationTest.Program/LoginDelegate
IL_00de: stloc.s chainDelegate
IL_00e0: ldloc.s chainDelegate
IL_00e2: ldstr "Test Remove From ChainDelegate"
IL_00e7: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
IL_00ec: nop
IL_00ed: ldstr "---------------------------------------------------------"
IL_00f2: call void [mscorlib]System.Console::WriteLine(string)
IL_00f7: nop
IL_00f8: ldloc.s chainDelegate
IL_00fa: ldsfld class ConsoleApplicationTest.Program/LoginDelegate ConsoleApplicationTest.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_00ff: brtrue.s IL_0114
IL_0101: ldnull
IL_0102: ldftn void ConsoleApplicationTest.Program::'<Main>b__0'(string)
IL_0108: newobj instance void ConsoleApplicationTest.Program/LoginDelegate::.ctor(object, native int)
IL_010d: stsfld class ConsoleApplicationTest.Program/LoginDelegate ConsoleApplicationTest.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0112: br.s IL_0114
IL_0114: ldsfld class ConsoleApplicationTest.Program/LoginDelegate ConsoleApplicationTest.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0119: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_011e: castclass ConsoleApplicationTest.Program/LoginDelegate
IL_0123: stloc.s chainDelegate
IL_0125: ldloc.s chainDelegate
IL_0127: ldstr "Add anonimous delegate"
IL_012c: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
IL_0131: nop
IL_0132: ldstr "---------------------------------------------------------"
IL_0137: call void [mscorlib]System.Console::WriteLine(string)
IL_013c: nop
IL_013d: call string [mscorlib]System.Console::ReadLine()
IL_0142: pop
IL_0143: ret
} // end of method Program::Main
Код получился довольно громоздкий. Для просмотра IL кода использовалась утилита ILSpy, которая является
бесплатной альтернативой .Net Reflector. Как мы видим с кода операции, += и -= приводит к такому вызову кода. Для Delegate.Combine код для IL выглядит, как показано ниже. Delegate.Remove код — см. ниже.
IL_0097: stloc.s chainDelegate
IL_0099: ldloc.s chainDelegate
IL_009b: ldloc.s logToMessageBoxDelegate
IL_009d: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_00a2: castclass ConsoleApplicationTest.Program/LoginDelegate
IL_0099: ldloc.s chainDelegate
IL_009b: ldloc.s logToMessageBoxDelegate
IL_009d: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_00a2: castclass ConsoleApplicationTest.Program/LoginDelegate
Этот код интерпретируется со строчки
chainDelegate += logToMessageBoxDelegate;
Для
удаления мы можем увидеть аналогичный код с методом Delegate.Remove.
Здесь, пожалуй, более интересный код с созданием анонимного метода.
IL_00f8: ldloc.s chainDelegate
IL_00fa: ldsfld class ConsoleApplicationTest.Program/LoginDelegate ConsoleApplicationTest.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_00ff: brtrue.s IL_0114
IL_0101: ldnull
IL_0102: ldftn void ConsoleApplicationTest.Program::'<Main>b__0'(string)
IL_0108: newobj instance void ConsoleApplicationTest.Program/LoginDelegate::.ctor(object, native int)
IL_010d: stsfld class ConsoleApplicationTest.Program/LoginDelegate ConsoleApplicationTest.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0112: br.s IL_0114
IL_0114: ldsfld class ConsoleApplicationTest.Program/LoginDelegate ConsoleApplicationTest.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0119: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_011e: castclass ConsoleApplicationTest.Program/LoginDelegate
IL_0123: stloc.s chainDelegate
IL_0125: ldloc.s chainDelegate
IL_0127: ldstr "Add anonimous delegate"
IL_012c: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
IL_00fa: ldsfld class ConsoleApplicationTest.Program/LoginDelegate ConsoleApplicationTest.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_00ff: brtrue.s IL_0114
IL_0101: ldnull
IL_0102: ldftn void ConsoleApplicationTest.Program::'<Main>b__0'(string)
IL_0108: newobj instance void ConsoleApplicationTest.Program/LoginDelegate::.ctor(object, native int)
IL_010d: stsfld class ConsoleApplicationTest.Program/LoginDelegate ConsoleApplicationTest.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0112: br.s IL_0114
IL_0114: ldsfld class ConsoleApplicationTest.Program/LoginDelegate ConsoleApplicationTest.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0119: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
IL_011e: castclass ConsoleApplicationTest.Program/LoginDelegate
IL_0123: stloc.s chainDelegate
IL_0125: ldloc.s chainDelegate
IL_0127: ldstr "Add anonimous delegate"
IL_012c: callvirt instance void ConsoleApplicationTest.Program/LoginDelegate::Invoke(string)
Как
можно увидеть из примера, анонимный делегат имеет сигнатуру 9__CachedAnonymousMethodDelegate1. В C# вызов создание анонимного метода выглядит
так:
delegate(string message) { Console.WriteLine("Call Custom Delegate {0}", message); };
Или через лямбда-выражение:
message => Console.WriteLine("Call Custom Delegate {0}",
message);
Лямбда-выражением
в данном случае выступает выражение =>. Анонимные методы были
представлены в C# 2.0, а в версиях C# 3.0 и более поздних лямбда-выражения
заменяют эти методы и являются предпочтительным способом написания встроенного
кода. Однако сведения об анонимных методах, представленные в данном
разделе, применяются и к лямбда-выражениям. Существует
только один случай, в котором функциональность анонимного метода отсутствует в
лямбда-выражениях. Анонимные методы позволяют отказаться от использования списка
параметров. Это означает, что анонимный метод может быть преобразован в
делегаты с различными сигнатурами. Это невозможно в
ситуации с лямбда-выражениями. Дополнительные сведения именно о
лямбда-выражениях см. в разделе Лямбда-выражения (Руководство по
программированию в C#). Лямбда-выражение — это анонимная функция,
которую можно использовать для создания типов делегатов или деревьев выражений. Более подробно о работе с лямбда-выражениями
можно узнать, работая с LINQ.
Делегаты
в .Net Framework
Некоторые типы делегатов уже существуют в .Net
Framework, так что если вы используете .Net Framework, то можете их
использовать. Это делегаты Action, Predicate и Func. Вы можете использовать эти
делегаты, как в LINQ, чтобы не
"изобретать велосипед". Приведем ориентировочные примеры использования этих типов
делегатов. Рассмотрим делегат Action<T>. Это тип делегата, который возвращает тип void и принимает
параметр типа T. Action<> имеет
перегруженные версии, которые могут принимать от 1 до 9 параметров.
class Program
{
static void Main(string[] args)
{
Action<string> action = LogToConsole;
action("Test Action Methods");
Console.ReadLine();
}
public static void
LogToConsole(string message)
{
Console.WriteLine(message);
}
}
Как видим из примера, все очень просто.
Пример использования делегата Func<T>:
class Program
{
static void Main(string[] args)
{
Func<string, string> action = UpperCaseString;
var result
= action("Test Action
Methods");
Console.WriteLine(result);
Console.ReadLine();
}
public static string
UpperCaseString(string value)
{
return value.ToUpper();
}
}
Делегат Func<T>,
в отличие от делегата Action<T>, — тип возвращаемого результата
передается последним параметром. Также Func<T> имеет перегруженную
версию, которая принимает 1-16 входных параметров.
Остался последний
делегат — Predicate<T>. Этот тип делегата, в отличии от делегата
Action<T>, возвращает результат как bool. Представляет метод, в котором
задан набор критериев и который позволяет определить, соответствует ли этим
критериям заданный объект. Пример использования:
class Program
{
static void Main(string[] args)
{
Predicate<string> action = IsNullOrEmptyString;
var result
= action("Test Action
Methods");
Console.WriteLine(result);
Console.ReadLine();
}
public static bool
IsNullOrEmptyString(string value)
{
return string.IsNullOrEmpty(value);
}
}
Метод используется в List<T> функции Find.
Ковариации и контравариации в делегатах
Ковариантные и контравариантные параметры
универсального типа предоставляют большую гибкость в назначении и использовании
универсальных типов. Например, параметры ковариантного типа позволяют создавать
назначения, которые выглядят очень похоже на обычный полиморфизм. Предположим,
что существуют базовый класс и производный класс с именами Base и Derived
соответственно. Полиморфизм позволяет назначать экземпляр Derived переменной
типа Base. Аналогичным образом, так как параметр типа интерфейса IEnumerable<T>
является ковариантным, существует возможность назначить экземпляр
IEnumerable<Derived> (IEnumerable(Of Derived) в Visual Basic) переменной
типа IEnumerable<Base>, как показано в следующем коде. Пример:
IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = d;
Начиная с .NET
Framework 4, универсальные типы делегатов могут иметь вариативные параметры
типов. Параметры контравариантного типа можно использовать в качестве типов
параметров делегата, а параметр ковариантного типа — в качестве возвращаемого
типа. Эта функция позволяет типам универсальных делегатов, которые создаются из
одного определения универсального типа, быть совместимыми с назначениями, если
типы их аргументов являются ссылочными в отношение наследования. Источник
Примечание MSDN: универсальные делегаты, которые совместимы с назначениями из-за вариативности, не обязательно являются сочетаемыми. Чтобы быть комбинируемыми, эти типы должны в точности совпадать. Например, предположим, что класс с именем Derived является производным от класса с именем Base. Делегат типа Action<Base> (Action(Of Base) в Visual Basic) может быть назначен переменной типа Action<Derived>, но два делегата комбинировать нельзя, так как типы не совпадают в точности.
Примечание MSDN: универсальные делегаты, которые совместимы с назначениями из-за вариативности, не обязательно являются сочетаемыми. Чтобы быть комбинируемыми, эти типы должны в точности совпадать. Например, предположим, что класс с именем Derived является производным от класса с именем Base. Делегат типа Action<Base> (Action(Of Base) в Visual Basic) может быть назначен переменной типа Action<Derived>, но два делегата комбинировать нельзя, так как типы не совпадают в точности.
Сложные
моменты при использования делегатов
Есть несколько
особенностей при использовании делегатов.
Если вызванный метод выбрасывает исключение, выполнение этого метода
прекращается, исключение передается обратно коду, вызвавшему делегат, и
оставшиеся в списке вызовов методы не вызываются. Перехват исключения в
вызывающем коде не меняет этого поведения.
Когда сигнатура методов, вызываемых делегатом, включает возвращаемое
значение, делегат возвращает значение, возвращенное последним элементом в списке
вызовов. Когда сигнатура включает
параметр, передаваемый по ссылке, конечным значением параметра является
результат выполнения всех методов из списка вызовов, вызываемых последовательно
и обновляющих значение этого параметра.
Одним из сложных моментов в языке C#
есть замыкание. Насчет последнего, вы можете прочитать статью Сергея
Теплякова
о замыканиях. Приведу простенький пример с замыканием и расскажу, как это
работает.
var funcs =
new List<Func<int>>();
for (int i = 0;
i < 3; i++)
{
funcs.Add(()
=> i);
}
foreach (var f in funcs)
Console.WriteLine(f());
Замыкание
переменной — это захват значения некоего объекта посредством лямбда-выражений.
Без дополнительных усилий вроде «out»
и «ref» модификаторов к
параметрам. Для того чтобы этот пример заработал, надо сохранить нужное
значение цикла в локальную переменную. Код будет выглядеть так:
var funcs = new List<Func<int>>();
for (int i = 0;
i < 3; i++)
{
var temp =
i;
funcs.Add(()
=> temp);
}
foreach (var f in funcs)
Console.WriteLine(f());
После
исправления на каждой итерации цикла будет осуществляться захват нового экземпляра переменной temp, в результате чего
каждый делегат будет ссылаться на свою копию переменной temp. Интересный момент связан с так называемым исправлением замыканий в С# 5.0 для цикла foreach. В C# 5.0 решили изменить цикл foreach таким образом, чтобы на каждой итерации
цикла переменная цикла создавалась вновь. По сути, в предыдущих версиях языка C#
в цикле foreach была лишь одна переменная цикла, а
начиная с C#
5.0, используется новая переменная для каждой итерации. Источник
Итоги:
Делегаты и события — это, пожалуй, самая
сложная тема для изучения новичками C#. Многие профессионалы
также могут не знать о некоторых моментах, описанных в этой статье. Надеюсь,
что материал получился не очень сухим.
Если проецировать делегаты на другие языки, то ближайшим эквивалентом
делегата в языке C или С++ является
указатель на функцию. В
отличие от указателей на функции, делегаты являются объектно-ориентированными и
строго типизированными.
Источники:
No comments:
Post a Comment