2015-03-07 5 views
4

Надеюсь, этот вопрос не будет тесно связан с другими, но другие, похоже, не заполняют пробел в знаниях.События и делегаты Vs Методы вызова

Это, кажется, горячая тема, чтобы попытаться понять события и делегаты, и после прочтения многих вопросов SO и статей MSDN я боюсь сказать, что я до сих пор не понимаю. Спустя несколько лет создавая отличные веб-приложения, я обнаружил, что очень расстраиваюсь, не понимая их. Пожалуйста, кто-нибудь может прояснить это в общем коде. Поэтому возникает вопрос: зачем использовать события и делегаты для вызова метода?

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

Public Class Email 
{ 
    public string To {get;set;} 
    //Omitted code 

    public void Send() 
    { 
    //Omitted code that sends. 
    } 
} 

Public Class SomeClass 
{ 
    //Some props 

    Public void DoWork() 
    { 
    //Code that does some magic 

    //Now Send Email 
    Email newEmail = new Email(); 
    newEmail.To = "[email protected]"; 
    newEmail.Send(); 
    } 
} 

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

С уважением,

+0

Возможный дубликат [Делегаты, почему?] (Http://stackoverflow.com/questions/3567478/delegates-why) –

+0

Спасибо, но в идеале, чтобы по-настоящему оценить это, я надеялся, что я получу ответ на код, который я написал или даже это возможно. –

ответ

4

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

Когда один класс вызывает методы в другом классе, эти классы «тесно связаны». Чем больше классов вы тесно связаны, тем труднее становится превращение в одного из них, не изменяя и другие. В этот момент вы можете написать один большой класс.

Использование событий вместо этого делает вещи более «слабо связанными» и значительно облегчает изменение одного класса, не беспокоя других.

Принимая ваш пример выше, предположим, что у нас был третий класс, Logger, который должен регистрироваться при отправке электронного письма. Он использует метод, LogEvent(string desc, DateTime time), чтобы сделать запись в журнал:

public class Logger 
{ 
    ... 
    public void LogEvent(string desc, DateTime time) 
    { 
    ...//some sort of logging happens here 
    } 
} 

Если мы используем методы, нам нужно обновить Email класс Send метода для создания экземпляра Logger и вызвать его LogEvent метода:

public void Send() 
    { 
    //Omitted code that sends. 
    var logger = new Logger(); 
    logger.LogEvent("Sent message", DateTime.Now); 
    } 

В настоящее время Email тесно связан с Logger. Если мы изменим подпись этого метода LogEvent в Logger, мы также должны внести изменения в Email. Вы видите, как это может быстро стать кошмаром, когда вы имеете дело даже с проектом среднего размера? Более того, никто не хочет даже пытаться использовать метод LogEvent, потому что они знают, что если им нужно что-то изменить, им придется начинать менять другие классы, и то, что должно было быть днем ​​работы, быстро превращается через неделю. Поэтому вместо этого они пишут новый метод или новый класс, который затем становится тесно связанным с тем, что они делают, все становится раздутым, и каждый программист начинает проникать в свое собственное «гетто» своего собственного кода. Это очень, очень плохо, когда вы должны прийти позже и выяснить, что, черт возьми, программа делает или выслеживать ошибку.

Если поставить некоторые события на вашем Email классе вместо этого, вы можете свободно пара этих классов:

Public Class Email 
{ 
    public event EventHandler<EventArgs> Sent; 
    private void OnSent(EventArgs e) 
    { 
     if (Sent!= null) 
      Sent(this, e); 
    } 

    public string To {get;set;} 
    //Omitted code 

    public void Send() 
    { 
    //Omitted code that sends. 
    OnSent(new EventArgs());//raise the event 
    } 
} 

Теперь вы можете добавить обработчик событий Logger и subcribe его в Email.Sent случае от примерно где-нибудь в ваше приложение и это делать то, что нужно сделать:

public class Logger 
{ 
    ... 
    public void Email_OnSent(object sender, EventArgs e) 
    { 
    LogEvent("Message Sent", DateTime.Now); 
    } 

    public void LogEvent(string desc, DateTime time) 
    { 
    ...//some sort of logging happens here 
    } 
} 

и в других местах:

var logger = new Logger(); 
var email = new Email(); 

email.Sent += logger.Email_OnSent;//subscribe to the event 

Теперь ваши занятия очень слабо связаны и через шесть месяцев после того, как вы решите, что хотите, чтобы ваш Logger собирал больше или другую информацию или даже делал что-то совершенно другое, когда отправлено электронное письмо, вы можете изменить LogEvent или обработчиком событий без необходимости касаться класса Email.Кроме того, другие классы могут также подписаться на событие, не изменяя класс Email, и вы можете иметь целый ряд вещей, когда отправляется электронное письмо.

Теперь ваш код намного проще, и другие люди гораздо чаще используют ваш код, потому что знают, что им не нужно будет копаться в 20 разных классах, чтобы внести изменения в то, как что-то обрабатывается.

БОЛЬШОЙ РЕДАКТОР: Больше о делегатах. Если вы прочтете здесь: Curiosity is Bliss: C# Events vs Delegates (Я свяжусь с минимумом, я обещаю), вы видите, как автор попадает в то, что события - это в основном специальные типы делегатов. Они ожидают подписи определенного типа (т. Е. (object sender, EventArgs e)) и могут иметь более одного метода, добавленного к ним (+=), который должен быть выполнен, когда метод поднят. Существуют и другие различия, но это основные из них, которые вы заметите. Итак, что хорошего - делегат?

Представьте, что вы хотели дать клиенту своего Email класс некоторые варианты отправки почты. Можно определить ряд методов для этого:

Public Class Email 
{ 
    public string To {get;set;} 
    //Omitted code 

    public void Send(MailMethod method) 
    { 
    switch(method) 
    { 
     case MailMethod.Imap: 
     ViaImap(); 
     break; 
     case MailMethod.Pop: 
     ViaPop(); 
     break; 
     } 
    } 

    private void ViaImap() {...} 

    private void ViaPop() {...} 
} 

Это хорошо работает, но если вы хотите добавить больше вариантов позже, вы должны изменить свой класс (а также MailMethod перечисление, которое предполагается здесь). Если объявить делегат вместо этого, вы можете отложить этот вид решения для клиента и сделать ваш класс гораздо более гибким:

Public Class Email 
{ 
    public Email() 
    { 
    Method = ViaPop;//declare the default method on instantiation 
    } 

    //define the delegate 
    public delegate void SendMailMethod(string title, string message); 

    //declare a variable of type SendMailMethod 
    public SendMailMethod Method; 

    public string To {get;set;} 
    //Omitted code 

    public void Send() 
    { 
    //assume title and message strings have been determined already 
    Method(title, message); 
    } 

    public void SetToPop() 
    { 
    this.Method = ViaPop; 
    } 

    public void SetToImap() 
    { 
    this.Method = ViaImap; 
    } 

    //You can write some default methods that you forsee being needed 
    private void ViaImap(string title, string message) {...} 

    private void ViaPop(string title, string message) {...} 
} 

Теперь клиент может использовать свой класс со своими собственными методами или предоставить свой собственный метод для отправки почте примерно однако они выбирают:

var regularEmail = new Email(); 
regularEmail.SetToImap(); 
regularEmail.Send(); 

var reallySlowEmail = new Email(); 
reallySlowEmail.Method = ViaSnailMail; 

public void ViaSnailMail(string title, string message) {...} 

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

+0

Мне действительно понравился этот ответ! Как/Почему (если необходимо) вы бы определили делегатов? –

+0

@ Ссылка O.R.Mapper выше - хороший ресурс, почему существуют делегаты. Это способ уменьшить количество кода, который вы пишете, и сделать код, который вы делаете, более удобным для обслуживания. Представьте, что вам приходилось писать конкретный метод каждый раз, когда вы хотели использовать '.Where()'! Ваш код будет быстро загромождать и нечитабельно. Используя делегат, или, еще лучше, лямбда (который чаще встречается сейчас), мы можем абстрагировать многое гораздо больше и сделать код, который мы делаем, писать гораздо более многократно. –

+0

Я согласен, что ссылка очень находчива, но поскольку ваш ответ очень информативен и богат, объяснение делегатов в приведенном выше примере кода, безусловно, поможет мне и многим другим. –

1

Ok, я понимаю, что этот ответ обыкновение строго говоря, правильным, но я расскажу вам, как я пришел, чтобы понять их.

Все функции имеют адрес памяти, а некоторые функции - простое получение/набор данных. Это помогает думать обо всех переменных как о функциях только с двумя способами - get и set. Вам довольно удобно передавать переменные по ссылке, что означает (упрощенно), что вы передаете указатель на свою память, что позволяет другому коду вызывать методы get/set, неявно, используя «=» и «==».

Теперь переведите эту концепцию на функции и код. Некоторые коды и функции имеют имена (например, имена переменных), которые вы им даете. вы используете для выполнения этих функций, вызывая их имя; но имя является просто синонимом расположения памяти (упрощенно). Вызывая функцию, вы удаляете ссылку на свой адрес памяти, используя ее имя, а затем вызываете метод, который живет по этому адресу памяти.

Как я уже сказал, все это очень упрощенно и по-разному неверно. Но это помогает мне.

Итак - возможно ли передать адрес памяти функции, но не называть ее? Точно так же вы передаете ссылку на переменную без ее оценки? То есть что эквивалентно вызову

DoSomeFunction(ref variablePointer) 

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

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

Простым способом просмотра обработчиков событий будет;

class myClass 
{ 
    public List<delegate> eventHandlers = new List<delegate>(); 
    public void someMethod() 
    { 
      //... do some work 
      //... then call the events 
      foreach(delegate d in eventHandlers) 
      { 
       // we have no idea what the method name is that the delegate 
       // points to, but we dont need to know - the pointer to the 
       // function is stored as a delegate, so we just execute the 
       // delegate, which is a synonym for the function. 
       d(); 
      } 
     } 
} 

public class Program() 
{ 
     public static void Main() 
     { 
      myClass class1 = new myClass(); 
      // 'longhand' version of setting up a delegate callback 
      class1.eventHandlers.Add(new delegate(eventHandlerFunction)); 
      // This call will cause the eventHandlerFunction below to be 
      // called 
      class1.someMethod(); 
      // 'shorthand' way of setting up a delegate callback 
      class1.eventHandlers.Add(() => eventHandlerFunction()); 
     } 
     public static eventHandlerFunction() 
     { 
      Console.WriteLine("I have been called"); 
     } 

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

Надеюсь, что это поможет.

0

Почему вы используете события и делегаты по вызову метода?

В контексте приведенного вами примера, если вы хотите асинхронно отправлять электронные письма, вам необходимо реализовать механизм уведомления.

См реализации SmtpClient для примера: https://msdn.microsoft.com/en-us/library/system.net.mail.smtpclient.sendcompleted%28v=vs.110%29.aspx

0

Если больше объяснений не требуется, а не примеры кода, я попытаюсь объяснить, почему вы должны использовать делегат или событие в данном примере вы дали выше ,

Представьте, что вы хотите знать, было ли отправлено электронное письмо или нет после того, как вы позвонили по электронной почте. Отправить(). В классе электронной почты у вас будет два события: один для неудачной отправки и один для успешной отправки. Когда класс электронной почты отправляется без ошибок, он посмотрел бы, есть ли какие-либо подписчики для события SuccessfulSend(), и, если есть, он вызывает это событие. Затем это будет уведомлять подписчиков, которые хотели бы получить информацию, если передача была успешной, чтобы они могли выполнять другую задачу.

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

Что касается делегатов, если для отправки почты использовались разные функции (или серверы), которые использовали разные функции (или серверы), клиент, вызывающий метод Email.Send(), может предоставить соответствующий класс электронной почты для использования при отправке Эл. адрес. Класс электронной почты будет использовать интерфейс IEmail, и три класса электронной почты будут реализовывать IEmail (To, From, Subject, Body, Attachments, HTMLBody и т. Д.), Но могут выполнять взаимодействия/правила по-разному.

Для этого может потребоваться Тема, другому требуется приложение, можно использовать CDONTS, другой - другой протокол. Клиент может определить, нужно ли использовать CDONTS в зависимости от того, где он установлен, или он может находиться в области приложения, где требуется вложение, или форматировать тело в HTML. Это делается для устранения бремени логики от клиента и всех мест, где эти проверки и логика должны быть проверены, и переместить их в отдельные версии соответствующего класса. Затем клиент просто вызывает Email.Send() после предоставления правильного объекта для использования в его конструкторе (или с помощью настраиваемого свойства). Если требуется исправление или изменение конкретного кода объекта электронной почты, оно выполняется в одном месте, а не находит все области на клиенте и обновляется там. Представьте, если ваш Email Класс был использован несколькими различными приложениями ...

0

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

Возьмите, например, обработчик события Click. Он использует делегата EventHandler. Подпись: void EventHandler(object sender, EventArgs e);. Что это за делегат, так это то, что когда кто-то нажимает элемент управления, я хочу иметь возможность называть ноль или более методов, которые имеют подпись EventHandler, но Я еще не знаю, что они еще. Этот делегат позволяет мне эффективно называть неизвестные будущие методы.

Другим примером является оператор LINQ .Select(...). Он имеет подпись IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector). Этот метод содержит делегат Func<TSource, TResult> selector. То, что делает этот метод, принимает последовательность значений от source и применяет пока неизвестную проекцию для получения последовательности TResult.

И, наконец, еще один хороший пример: Lazy<T>. Этот объект имеет конструктор с этой сигнатурой: public Lazy(Func<T> valueFactory). Задача заключается в том, чтобы отложить экземпляр T до первого использования, но затем сохранить это значение для всех будущих видов использования. По-видимому, это дорогостоящий экземпляр, который был бы идеальным, чтобы избежать, если нам не нужен объект, но если нам это нужно больше, чем один, мы не хотим, чтобы его поразили затраты.Lazy<T> обрабатывает все блокировки потоков и т. Д., Чтобы убедиться, что создан только один экземпляр T. Но значение T, возвращенное Func<T> valueFactory, может быть любым - у создателей Lazy<T> есть не знаю, что делегат будет, и им тоже не должно быть.

Это, для меня, является наиболее принципиально важным для понимания делегатов.

Смежные вопросы