Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Делегаты и события в .NET

Anna | 17.06.2014 | нет комментариев
От переводчика. Судя по своему навыку, а также по навыку знакомых коллег-программистов, могу сказать, что для начинающего разработчика среди всех базовых функций языка Cи платформы .NET делегаты и события являются одними из особенно трудных. Допустимо, это из-за того, что надобность делегатов и событий на 1-й взор кажется неочевидной, либо же из-за некоторой путаницы в терминах. Следственно я решил перевести статью Джона Скита, рассказывающую о делегатах и событиях на самом базовом ярусе, «на пальцах». Она совершенна для тех, кто знаком с C#/.NET, впрочем испытывает затруднение в понимании делегатов и событий.

Представленный тут перевод является вольным. Впрочем если под «вольным», как правило, понимают сокращённый перевод, с упущениями, облегчениями и пересказами, то тут всё напротив. Данный перевод является немножко расширенной, уточнённой и обновлённой версией оригинала. Я выражаю громадную признательность Сергею Теплякову aka SergeyT, тот, что внёс неоценимый взнос в перевод и оформление данной статьи.

Люди Зачастую испытывают затруднения в понимании отличий между событиями и делегатами. И C# ещё огромнее запутывает обстановку, так как разрешает объявлять field-like события, которые механически преобразуются в переменную делегата с таким же самым именем. Эта статья призвана прояснить данный вопрос. Ещё одним моментом является путаница с термином «делегат», тот, что имеет несколько значений. Изредка его применяют для обозначения типа делегата (delegate type), а изредка — для обозначенияэкземпляра делегата (delegate instance). Дабы избежать путаницы, я буду очевидно применять эти термины — тип делегата и экземпляр делегата, а когда буду применять слово «делегат» — значит, я говорю о них в самом широком смысле.

Типы делегатов

В каком-то смысле вы можете думать о типе делегата как о некоем интерфейсе, в котором определён лишь один способ с чётко заданной сигнатурой (в этой статье под сигнатурой способа я буду понимать все его входные и выходные (ref и out) параметры, а также возвращаемое значение). Тогда экземпляр делегата — это объект, реализующий данный интерфейс. В этом понимании, имея экземпляр делегата, вы можете вызвать всякий присутствующий способ, сигнатура которого будет совпадать с сигнатурой способа, определённого в «интерфейсе». Делегаты владеют и иной функциональностью, но вероятность делать вызовы способов с предварительно определёнными сигнатурами — это и есть самая суть делегатов. Экземпляр делегата хранит ссылку (указатель, метку) на целевой способ и, если данный способ является экземплярным, то и ссылку на экземпляр объекта (класса либо конструкции), в котором «находится» целевой способ.

Тип делегата объявляется при помощи ключевого слова delegate. Типы делегатов могут существовать как независимые сущности, так и быть объявленными внутри классов либо конструкций. Скажем:

namespace DelegateArticle
 {
     public delegate string FirstDelegate (int x);

     public class Sample
     {
         public delegate void SecondDelegate (char a, char b);
     }
 }

В этом примере объявлены два типа делегата. 1-й — DelegateArticle.FirstDelegate, тот, что объявлен на ярусе пространства имён. Он «совместим» с любым способом, тот, что имеет один параметр типа int и возвращает значение типа string. 2-й — DelegateArticle.Sample.SecondDelegate, тот, что объявлен теснее внутри класса и является его членом. Он «совместим» с любым способом, тот, что имеет два параметра типаchar и не возвращает ничего, так как возвращаемый тип помечен как void.

Обратите внимание, что оба типа делегата имеют модификатор доступа public. Вообще, по отношению модификаторов доступа типы делегатов ведут себя так же, как классы и конструкции. Если для типа делегата очевидно не указан модификатор доступа и данный тип объявлен внутри пространства имён, то он будет доступен для всех объектов, также находящихся внутри этого пространства имён. Если же тип делегата без модификатора объявлен внутри класса либо конструкции, то он будет закрытым, подобно действию модификатора private.

При объявлении типа делегата невозможно применять модификатор static.

Но помните, что ключевое слово delegate не неизменно обозначает объявление типа делегата. Это же ключевое слово применяется при создании экземпляров делегатов при применении неизвестных способов.

Оба типа делегата, объявленные в этом примере, наследуются от System.MulticastDelegate, тот, что, в свою очередь, наследуется от System.Delegate. На практике рассматривайте наследование только отMulticastDelegate — отличие между Delegate и MulticastDelegate лежит раньше каждого в историческом аспекте. Эти отличия были существенны в бета-версиях .NET 1.0, но это было неудобно, и Microsoft решила объединить два типа в один. К сожалению, решение было сделано слишком поздно, и когда оно было сделано, делать такое серьёзное метаморфоза, затрагивающее основу .NET, не решились. Следственно считайте, чтоDelegate и MulticastDelegate — это одно и то же.

Всякий тип делегата, сделанный вами, наследует члены от MulticastDelegate, а именно: один конструктор с параметрами Object и IntPtr, а также три способа: InvokeBeginInvoke и EndInvoke. К конструктору мы вернёмся чуточку позднее. Вообще-то эти три способа не наследуются в прямом смысле, так как их сигнатура для всякого типа делегата своя — она «подстраивается» под сигнатуру способа в объявленном типе делегата. Глядя на пример кода выше, выведем «наследуемые» способы для первого типа делегатаFirstDelegate:

public string Invoke (int x);
public System.IAsyncResult BeginInvoke(int x, System.AsyncCallback callback, object state);
public string EndInvoke(IAsyncResult result);

Как вы видите, возвращаемый тип способов Invoke и EndInvoke совпадает с таковым, указанным в сигнатуре делегата, так само, как и параметр способа Invoke и 1-й параметр BeginInvoke. Мы разглядим цель способаInvoke дальше в статье, а BeginInvoke и EndInvoke разглядим в разделе, описывающем продвинутое применение делегатов. Теперь же об этом говорить слишком несвоевременно, так как мы ещё даже не знаем, как создавать экземпляры делегатов. Об этом и побеседуем в дальнейшем разделе.

Экземпляры делегатов: основы

Сейчас мы знаем, как объявляется тип делегата и что он содержит, так что давайте взглянем, как дозволено сделать экземпляр делегата, и что с ним дозволено сделать.

Создание экземпляров делегатов

Раньше каждого подмечу, что эта статья не рассказывает о новых функциональных вероятностях C# 2.0 и 3.0, связанных с созданием экземпляров делегатов, так же, как и не покрывает обобщённые делегаты, появившиеся в C# 4.0. Моя отдельная статья о замыканиях «The Beauty of Closures» повествует о новых вероятностях делегатов, которые возникли в C# 2.0 и 3.0; помимо того, много информации по этой теме содержится в главах 5, 9 и 13 моей книги «C# in Depth». Я буду придерживаться очевидного жанра создания экземпляров делегатов, тот, что возник в C# 1.0/1.1, так как предполагаю, что такой жанр проще для понимания того, что происходит «под капотом». Вот когда вы постигнете основы, то дозволено будет приступать к освоению новых вероятностей из C# 2.0, 3.0 и 4.0; и напротив, без твёрдого понимания основ, высказанных в этой статье, «новейший» функционал делегатов может быть для вас неподъёмным.

Как говорилось ранее, всякий экземпляр делегата непременно содержит ссылку на целевой способ, тот, что может быть вызван через данный экземпляр делегата, и ссылку на экземпляр объекта (класса либо конструкции), в котором объявлен целевой способ. Если целевой способ является статическим, то, безусловно, ссылка на экземпляр отсутствует. CLR поддерживает и другие, немножко разные формы делегатов, где 1-й довод, передаваемый в статический способ, хранится в экземпляре делегата, либо же ссылка на целевой экземплярный способ передаётся как довод при вызове способа. Больше детально об этом дозволено прочитать в документации к System.Delegate на MSDN, впрочем теперь, на данном этапе, эти добавочные данные не существенны.

Выходит, мы знаем, что для создания экземпляра нам необходимы две «единицы» данных (ну и сам тип делегата, безусловно), впрочем как дать знать об этом компилятору? Мы используем то, что в спецификации к C# именуется «выражение создания делегата» (delegate-creation-expression), что является одной из формnew delegate-type (expression). Выражение (expression) должно быть либо иным делегатом с таким же самым типом (либо с совместимым типом делегата в C# 2.0), либо же «группой способов» (method group), которая состоит из наименования способа и опциональной ссылки на экземпляр объекта. Группа способов указывается так само, как и обыкновенный вызов способа, но без каких-либо доводов и круглых скобок. Надобность в создании копий делегата появляется достаточно редко, следственно мы сосредоточимся на больше всеобщих формах. Примеры ниже.

/* Два выражения создания экземпляров делегатов d1 и d2 равнозначны. Тут InstanceMethod является экземплярным способом, тот, что объявлен в классе, в котором также объявлены нижеприведённые выражения (базовый класс). Соответственно, ссылка на экземпляр объекта — this, и именно следственно эти выражения равнозначны. */
FirstDelegate d1 = new FirstDelegate(InstanceMethod);
 FirstDelegate d2 = new FirstDelegate(this.InstanceMethod);

/* Тут (d3) мы создаём экземпляр делегата, ссылающийся на тот же способ, что и в предыдущих 2-х выражениях, но на данный раз с иным экземпляром класса. */
FirstDelegate d3 = new FirstDelegate(anotherInstance.InstanceMethod);

/* В этом (d4) экземпляре делегата применяется теснее иной способ, тоже экземплярный, тот, что объявлен в ином классе; мы указываем экземпляр этого класса и сам способ. */
FirstDelegate d4 = new FirstDelegate(instanceOfOtherClass.OtherInstanceMethod);

/* А вот данный (d5) экземпляр делегата использует статический способ, тот, что размещен в том же классе, где и это выражение (базовом классе). */
FirstDelegate d5 = new FirstDelegate(StaticMethod);

/* Тут (d6) экземпляр делегата использует иной статический способ, объявленный на данный раз в стороннем классе. */
FirstDelegate d6 = new FirstDelegate(OtherClass.OtherStaticMethod);

Конструктор делегата, о котором мы говорили ранее, имеет два параметра — ссылку на вызываемый способ типа System.IntPtr (в документации MSDN данный параметр именуется method) и ссылку на экземпляр объекта типа System.Object (в документации MSDN данный параметр именуется target), которая принимает значение null, если способ, указанный в параметре method, является статическим.

Нужно сделать главное примечание: экземпляры делегатов могут ссылаться на способы и экземпляры объектов, которые будут заметными (вне области видимости) по отношению к тому месту в коде, где будет произведён вызов экземпляра делегата. Скажем, при создании экземпляра делегата может быть использован приватный (private) способ, а потом данный экземпляр делегата может быть возвращён из иного, публичного (public) способа либо свойства. С иной стороны, экземпляр объекта, указанный при создании экземпляра делегата, может быть объектом, тот, что при вызове будет незнакомым по отношению к тому объекту, в котором был совершен вызов. Значимо то, что и способ, и экземпляр объекта обязаны быть доступны (находиться в области видимости) на момент создания экземпляра делегата. Другими словами, если (и только если) в коде вы можете сделать экземпляр определённого объекта и вызвать определённый способ из этого экземпляра, то вы можете применять данный способ и экземпляр объекта для создания экземпляра делегата. А вот во время вызова ранее сделанного экземпляра делегата права доступа и область видимости игнорируются. Кстати, о вызовах…

Вызов экземпляров делегатов

Экземпляры делегатов вызываются так само, как если бы это были те способы, на которые экземпляры делегатов ссылаются. К примеру, вызов экземпляра делегата d1, тип которого определён в самом верху какdelegate string FirstDelegate (int x), будет дальнейшим:

string result = d1(10);

Способ, ссылку на тот, что хранит экземпляр делегата, вызывается «в рамках» (либо «в контексте», если другими словами) экземпляра объекта, если такой есть, позже чего возвращается итог. Написание полновесной программы, демонстрирующей работу делегатов, и при этом суперкомпактной, не содержащей «лишнего» кода, является непростой задачей. Тем не менее, ниже приведена сходственная программа, содержащая один статический и один экземплярный способ. Вызов DelegateTest.StaticMethod равнозначен вызову StaticMethod — я включил наименование класса, Дабы сделать пример больше понимаемым.

using System;

public delegate string FirstDelegate (int x);

class DelegateTest
 {    
     string name;

     static void Main()
     {
         FirstDelegate d1 = new FirstDelegate(DelegateTest.StaticMethod);

         DelegateTest instance = new DelegateTest();
         instance.name = "My instance";
         FirstDelegate d2 = new FirstDelegate(instance.InstanceMethod);

         Console.WriteLine (d1(10)); // Выводит на консоль "Static method: 10"
         Console.WriteLine (d2(5));  // Выводит на консоль "My instance: 5"
     }

     static string StaticMethod (int i)
     {
         return string.Format ("Static method: {0}", i);
     }

     string InstanceMethod (int i)
     {
         return string.Format ("{0}: {1}", name, i);
     }
 }

Синтаксис C# по вызову экземпляров делегатов является синтаксическим сахаром, маскирующим вызов способа Invoke, тот, что есть у всякого типа делегата. Делегаты могут выполняться асинхронно, если предоставляют способы BeginInvoke/EndInvoke, но об этом позднее.

Комбинирование делегатов

Делегаты могут комбинироваться (объединяться и вычитаться) таким образом, что когда вы вызываете один экземпляр делегата, то вызывается целый комплект способов, причём эти способы могут быть из разных экземпляров разных классов. Когда я прежде говорил, что экземпляр делегата хранит ссылки на способ и на экземпляр объекта, я немножко упрощал. Это объективно для тех экземпляров делегатов, которые представляют один способ. Для ясности в последующем я буду называть такие экземпляры делегатов«примитивными делегатами» (simple delegate). В противовес им, существуют экземпляры делегатов, которые реально являются списками примитивных делегатов, все из которых базируются на одном типе делегата (т.е. имеют идентичную сигнатуру способов, на которые ссылаются). Такие экземпляры делегатов я буду называть «составными делегатами» (combined delegate). Несколько составных делегатов могут быть скомбинированы между собой, реально становясь одним огромным списком примитивных делегатов. Список примитивных делегатов в комбинированном делегате именуется «списком вызовов» либо «списком действий» (invocation list). Т.о., список вызовов — это список пар ссылок на способы и экземпляры объектов, которые (пары) расположены в порядке вызова.

Значимо знать, что экземпляры делегатов неизменно неизменяемы (immutable). Всякий раз при объединении экземпляров делегатов (а также при вычитании – это мы разглядим чуть ниже) создаётся новейший составной делегат. В точности, как и со строками: если вы применяете String.PadLeft к экземпляру строки, то способ не изменяет данный экземпляр, а возвращает новейший экземпляр с проделанными изменениями.

Объединение (также встречается термин «сложение») 2-х экземпляров делегатов обыкновенно производится при помощи оператора сложения, как если бы экземпляры делегатов были числами либо строками. Подобно, вычитание (также встречается термин «удаление») одного экземпляра делегата из иного производится при помощи оператора вычитания. Имейте ввиду, что при вычитании одного комбинированного делегата из иного вычитание производится в рамках списка вызовов. Если в подлинном (сокращаемом) списке вызовов нет не одного из тех примитивных делегатов, которые находятся в вычитаемом списке вызовов, то итогом операции (разностью) будет подлинный список. В отвратном случае, если в подлинном списке присутствуют примитивные делегаты, присутствующие и в вычитаемом, то в результирующем списке будут отсутствовать лишь последние вступления примитивных делегатов. Однако, это легче показать на примерах, нежели описать на словах. Но взамен очередного начального кода я продемонстрирую работу объединения и вычитания на примере нижеприведенной таблицы. В ней литералами d1, d2, d3 обозначены примитивные делегаты. Дальше, обозначение [d1, d2, d3] подразумевает составной делегат, тот, что состоит из трёх примитивных именно в таком порядке, т.е. при вызове вначале будет вызван d1, потом d2, а после этого d3. Пустой список вызовов представлен значением null.

Выражение Итог
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

Помимо оператора сложения, экземпляры делегатов могут объединяться при помощи статического способаDelegate.Combine; подобно ему, операция вычитания имеет альтернативу в виде статического способаDelegate.Remove. Вообще говоря, операторы сложения и вычитания — это неповторимый синтаксический сахар, и компилятор C#, встречая их в коде, заменяет на вызовы способов Combine и Remove. И именно потому, что данные способы являются статическими, они легко справляются с null-экземплярами делегатов.

Операторы сложения и вычитания неизменно работают как часть операции присваивания d1 = d2, которая всецело эквивалента выражению d1 = d1 d2; то же самое для вычитания. Вновь-таки, напоминаю, что экземпляры делегатов, участвующие в сложении и вычитании, не изменяются в процессе операции; в данном примере переменная d1 легко сменит ссылку на новосозданный составной делегат, состоящий из «ветхого» d1 и d2.

Обратите внимание, что добавление и удаление делегатов происходит с конца списка, следственно последовательность вызовов x = y; x -= y; равнозначна пустой операции (переменная x будет содержать постоянный список подписчиков, прим. перев.).

Если сигнатура типа делегата объявлена такой, что возвращает значение (т.е. возвращаемое значение не является void) и «на основе» этого типа сделан составной экземпляр делегата, то при его вызове в переменную будет записано возвращаемое значение, «предоставленное» последним простым делегатом в списке вызовов комбинированного делегата.

Если есть составной делегат (содержащий список вызовов, состоящий из множества примитивных делегатов), и при его вызове в каком-то простом делегате произойдёт исключение, то в этом месте вызов комбинированного делегата прекратится, исключение будет проброшено, и все остальные примитивные делегаты из списка вызовов так никогда и не будут вызваны.

События

Перво-наперво: события (event) не являются экземплярами делегатов. А сейчас вновь:
События — это НЕ экземпляры делегатов.

В некотором смысле жалко, что язык C# разрешает применять события и экземпляры делегатов в определённых обстановках идентичным образом, впрочем дюже значимо понимать разницу.

Я пришел к итогу, что самый наилучший метод осознать события, это думать о них как о «как бы» свойствах (properties). Свойства, правда и выглядят «типа как» поля (fields), на самом деле ими определённо не являются — вы можете сделать свойства, которые вообще никак не применяют поля. Сходственным образом ведут себя и события — правда и выглядят как экземпляры делегатов в плане операций добавления и вычитания, но на самом деле не являются ими.

События являются парами способов, соответствующе «оформленные» в IL (CIL, MSIL) и связанные между собой так, Дабы языковая среда чётко знала, что она «имеет дело» не с «примитивными» способами, а с способами, которые представляют события. Способы соответствуют операциям добавления (add) и удаления(remove), всякая из которых принимает один параметр с экземпляром делегата, тот, что имеет тип, идентичный с типом события. То, что вы будете делать с этими операциями, в существенной степени зависит от вас, но обыкновенно операции добавления и удаления применяются для добавления и удаления экземпляров делегатов в/из списка обработчиков события. Когда событие срабатывает (и не значимо, что было поводом срабатывания — щелчок по кнопке, таймер либо необработанное исключение), происходит поочерёдный (один за иным) вызов обработчиков. Знайте, что в C# вызов обработчиков события не являетсячастью самого события.

Способы добавления и удаления вызываются в C# так eventName = delegateInstance; и так eventName -= delegateInstance;, где eventName может быть указано по ссылке на экземпляр объекта (скажем,myForm.Click) либо по называнию типа (скажем, MyClass.SomeEvent). Однако, статические события встречаются достаточно редко.

События сами по себе могут быть объявлены двумя методами. 1-й метод — с очевидной (explicit) реализацией способов add и remove; данный метод дюже схож на свойства с очевидно объявленными геттерами (get) и сеттерами (set), но с ключевым словом event. Ниже представлен пример свойства для типа делегатаSystem.EventHandler. Обратите внимание, что в способах add и remove не происходит никаких операций с экземплярами делегатов, которые туда передаются — способы легко выводят в консоль сообщения о том, что они были вызваны. Если вы исполните данный код, то увидите, что способ remove будет вызван, несмотря на то, что мы ему передали для удаления значение null.

using System;

class Test
 {
     public event EventHandler MyEvent //публичное событие MyEvent с типом EventHandler
     {
         add
         {
             Console.WriteLine ("add operation");
         }

         remove
         {
             Console.WriteLine ("remove operation");
         }
     }       

     static void Main()
     {
         Test t = new Test();

         t.MyEvent  = new EventHandler (t.DoNothing);
         t.MyEvent -= null;
     }

    //Метод-заглушка, сигнатура которого совпадает с сигнатурой типа делегата EventHandler
     void DoNothing (object sender, EventArgs e)
     {
     }
 }

Моменты, когда доводится игнорировать полученное значение value, появляются достаточно редко. И правда случаи, когда мы можем игнорировать передаваемое таким образом значение, весьма редки, бывают случаи, когда нам не подойдет применение легкой переменной делегата для оглавления подписчиков. Скажем, если класс содержит уйма событий, но подписчики будут применять лишь определенный из них, мы можем сделать ассоциативный массив, в качестве ключа которой будет применять изложение событие, а в качестве значения — делегат с его подписчиками. Именно эта техника применяется в Windows Forms — т.е. класс может содержать большое число событий без тщетного применения памяти под переменные, которые в большинстве случаев будут равными null.

Field-like события

C# обеспечивает примитивный метод объявления переменной делегата и события в один и тот же момент. Данный метод именуется «field-like событием» (field-like event) и объявляется дюже легко — так же, как и «длинная» форма объявления события (приведённая выше), но без «тела» с способами add и remove.

public event EventHandler MyEvent;

Эта форма создает переменную делегата и событие с идентичным типом. Доступ к событию определяется в объявлении события с поддержкой модификатора доступа (т.о., в примере выше создаётся публичное событие), но переменная делегата неизменно приватна. Неявное (implicit) тело события разворачивается компилятором во абсолютно явственные операции добавления и удаления экземпляров делегата к/из переменной делегата, причём эти действия выполняются под блокировкой (lock). Для C# 1.1 событие MyEventиз примера выше равнозначно дальнейшему коду:

private EventHandler _myEvent;

public event EventHandler MyEvent
 {
     add
     {
         lock (this)
         {
             _myEvent  = value;
         }
     }
     remove
     {
         lock (this)
         {
             _myEvent -= value;
         }
     }        
 }

Это что касается экземплярных членов. Что касается статических событий, то переменная тоже является статической и блокировка захватывается на типе вида typeof(XXX), где XXX — это имя класса, в котором объявлено статическое событие. Язык C# 2.0 не дает никаких гарантий по поводу того, что применяется для захвата блокировок. Он говорит лишь о том, что для блокировки экземплярных событий применяется исключительный объект, связанный с нынешним экземпляром, а для блокировки статических событий — исключительный объект, связанный с нынешним классом. (Обратите внимание, что это объективно лишь для событий, объявленных в классах, но не в конструкциях. Существуют задачи с блокировками событий в конструкциях; и на практике я не помню ни одного примера конструкции, в которой было объявлено событие.) Но ничего из этого не является таким уж пригодным, как вы могли бы подуматventArgs e) { SomeEventHandler handler; lock (someEventLock) { handler = someEvent; } if (handler != null) { handler (this, e); } }
Вы можете применять цельную блокировку для всех ваших событий, и даже применять эту блокировку для чего-либо ещё — это теснее зависит от определенной обстановки. Обратите внимание, что вам необходимо «записать» нынешнее значение в локальную переменную внутри блокировки (для того, Дабы получить самое актуальное значение), а после этого проверить это значение на null и исполнить вне блокировки: удерживание блокировки во время вызова события является дюже дрянной идеей, легко приводящей к взаимоблокировке. Дабы объяснить это, представьте, что некоторый обработчик событий должен дождаться, пока иной поток исполнит какую-то свою работу, и если во время неё данный иной поток вызовет операцию add/remove для вашего события, то вы получите взаимоблокировку.

Вышеприведённый код работает правильно потому, что как только локальной переменной handler будет присвоено значение someEvent, то значение handler теснее не изменится даже в том случае, если изменится сам someEvent. Если все обработчики событий отпишутся от события, то список вызовов будет пуст,someEvent станет null, но handler будет беречь своё значение, которое будет таким, каковым оно было на момент присвоения. На самом деле, экземпляры делегатов являются неизменяемыми (immutable), следственно всякие подписчики, подписавшиеся между присваиванием (handler = someEvent) и вызовом события (handler (this, e);), будут проигнорированы.

Помимо этого, необходимо определить, необходима ли вам вообще потокобезопасность. Собираетесь ли вы добавлять и удалять обработчики событий из других потоков? Собираетесь ли вы вызывать события из различных потоков? Если вы всецело контролируете своё приложение, то дюже верным и легким в реализации результатом будет «нет». Если же вы пишете библиотеку классов, то, скорее каждого, обеспечение потокопезопасности сгодится. Если же вам определённо не необходима потокобезопасность, то отличной идеей будет самосильно реализовать тело операций add/remove, Дабы они очевидно не применяли блокировки; чай, как мы помним, C# при автогенерации этих операций использует «свой» «неверный» механизм блокировки. В этом случае ваша задача дюже примитивна. Ниже — пример вышеприведённого кода, но без потокобезопасности.

/// <summary>
/// Переменная типа делегата SomeEventHandler, являющаяся «фундаментом» события.
/// </summary>
 SomeEventHandler someEvent;

/// <summary>
/// Само событие
/// </summary>
public event SomeEventHandler SomeEvent
 {
     add
     {
         someEvent  = value;
     }
     remove
     {
         someEvent -= value;
     }
 }

/// <summary>
/// Вызов события SomeEvent
/// </summary>
protected virtual void OnSomeEvent(EventArgs e)
 {
     if (someEvent != null)
     {
         someEvent (this, e);
     }
 }

Если на момент вызова способа OnSomeEvent переменная делегата someEvent не содержит списка экземпляров делегатов (в итоге того, что они не были добавлены через способ add либо же были удалены через способ remove), то значение этой переменной будет null, и Дабы избежать её вызова с таким значением, и была добавлена проверка на null. Сходственную обстановку дозволено решить и иным путём. Дозволено сделать экземпляр делегата-заглушку (no-op), тот, что будет привязан к переменной «по умолчанию» и не будет удаляться. В этом случае в способе OnSomeEvent необходимо легко получить и вызвать значение переменной делегата. Если «настоящие» экземпляры делегатов так и не были добавлены, то будет легко-напросто вызвана заглушка.

Экземпляры делегатов: другие способы


Ранее в статье я показал, что вызов someDelegate(10) — это легко сокращение для вызоваsomeDelegate.Invoke(10). Помимо Invoke, типы делегатов имеют и асинхронное поведение посредством пары способов BeginInvoke/EndInvoke. В CLI они являются опциональными, но в C# они неизменно есть. Они придерживаются той же модели асинхронного выполнения, что и остальная часть .NET, разрешая указывать обработчик обратного вызова (callback handler) совместно с объектом, хранящим информацию о состоянии. В итоге асинхронного вызова код выполняется в потоках, сделанных системой и находящихся в пуле потоков (thread-pool) .NET.

В первом примере, представленном ниже2!br/>

Завершение


Делегаты предоставляют легкой метод вызова способов с учётом экземпляров объектов и с вероятностью передачи неких данных. Они являются основой для событий, которые сами по себе являются результативным механизмом для добавления и удаления обработчиков, которые будут вызваны в соответствующее время.

Примечания

1. Прим. перев. Сознаюсь, трактование Джона Скита достаточно невнятное и скомканное. Дабы подробно разобраться, отчего блокировка на нынешнем экземпляре и на типе — это нехорошо, и отчего следует вводить отдельное приватное поле только для чтения, я весьма советую воспользоваться книгой «CLR via C#» за авторством Джеффри Рихтера, которая теснее пережила 4 издания. Если говорить о втором издании 2006 года выпуска, переведённое в 2007 на русский язык, то информация о данной задаче расположена в «Часть 5. Средства CLR» – «Глава 24. Синхронизация потоков» — раздел «Отчего же «хорошая» идея оказалась такой неудачной».

2. Прим. перев. Данный и дальнейший примеры кода, как и их итог на консоль, были немножко изменены по сопоставлению с подлинными примерами от Дж. Скита. Помимо перевода, я добавил итог идентификаторов потоков, Дабы было чётко видно, какой код в каком потоке исполняется.

От переводчика


Невзирая на немаленький размер статьи, невозможно не согласиться, что тема делегатов и событий гораздо больше обширна, трудна и многогранна. Впрочем гипотетическая статья, всецело описывающая делегаты и события, имела бы размер, сходный с размером средней книги. Следственно я привожу ссылки на особенно пригодные статьи по теме, причём такие, которые как дозволено больше слаженно дополняют именно эту статью.

Алексей Дубовцев. Делегаты и события (RSDN).
Правда статья не новая (датируется 2006 годом) и рассматривает лишь основы делегатов и событий, ярус «рассмотрения основ» гораздо глубже: тут и больше пристальное рассмотрение типа MulticastDelegate, исключительно в плане составных делегатов, и изложение тезиса работы на ярусе MSIL, и изложение класса EventHandlerList, и многое другое. В всеобщем, если вы хотите разглядеть основы делегатов и событий на больше глубоком ярусе, то данная статья определённо для вас.

coffeecupwinnerСобытия .NET в деталях.
Верю, вы обратили внимание на заметку об устаревшем материале сначала раздела «Потокобезопасные события»? В C# 4 внутренняя реализация field-like событий, которую осуждают Скит и Рихтер, была всецело переработана: сейчас потокобезопасность реализуется через Interlocked.CompareExchange, безо каждых блокировок. Об этом, в числе прочего, и рассказывает эта статья. Вообще, статья скрупулёзно рассматривает только события, впрочем на гораздо больше глубоком ярусе, чем у Джона Скита.

Daniel Grunwald. andreychaСлабые события в C#.
Когда говорят о превосходствах между C#/.NET с одной стороны и C с иной, то в превосходство первым помимо прочего записывают механическую сборку мусора, которая ликвидирует утраты памяти как класс ошибок. Впрочем не всё так светло: события могут приводить к утратам памяти, и именно решению этих задач посвящена данная дюже детальная статья.

rroyterДля чего необходимы делегаты в C#?
Как я упоминал во введении, в начинающих разработчиков недопонимания с делегатами связаны с отсутствием видимых причин, требующих их применения. Данная статья дюже доходчиво показывает некоторые обстановки, где делегаты будут весьма уместны. Помимо того, тут продемонстрированы новые вероятности делегатов, введённые в C# 2-й и 3-й версий.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB