简体   繁体   中英

Events and Delegates Vs Calling methods

I hope this question is not to closely related to others but others don't seem to fill the gap in knowledge.

This seems to be hot topic to try and understand Events and Delegates and after reading many SO questions and MSDN articles I'm afraid to say I still don't understand. After some years creating great web applications, I found myself getting extremely frustrated in not understanding them. Please can anyone clarify this in common code. So the question is, why would you use events and delegates over calling a method?

Below is some basic code I written at work. Would I be able to leverage events and delegates?

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 = "me@me.com";
    newEmail.Send();
  }
}

This is probably not the best example but is there anyway that the DoWork() method can subscribe to the Email? Would this work? Any help for me to truly understand events and delegates would be greatly appreciated.

Regards,

The biggest reason I have found in real-world programming for the use of events and delegates is easing the task of code maintenance and encouraging code reuse .

When one class calls methods in another class, those classes are "tightly coupled". The more classes you have tightly coupled, the more difficult it becomes to make a change to one of them without having to also change several others. You may as well have written one big class at that point.

Using events instead makes things more "loosely coupled" and makes it much easier to change one class without having to disturb others.

Taking your example above, suppose we had a third class, Logger , that should log when an email is sent. It uses a method, LogEvent(string desc, DateTime time) , to write an entry to the log:

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

If we use methods, we need to update your Email class' Send method to instantiate a Logger and call its LogEvent method:

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

Now Email is tightly coupled to Logger . If we change the signature of that LogEvent method in Logger , we will also have to make changes to Email . Do you see how this can quickly become a nightmare when you are dealing with even a medium-sized project? Furthermore, no one wants to even try to use the LogEvent method because they know that if they need to make any sort of change to it, they will have to start changing other classes, and what should have been an afternoon's worth of work quickly turns into a week. So instead, they write a new method, or a new class, that then becomes tightly coupled to whatever else they are doing, things get bloated, and each programmer starts to get into their own little "ghetto" of their own code. This is very, very bad when you have to come in later and figure out what the hell the program is doing or hunt down a bug.

If you put some events on your Email class instead, you can loosely couple these classes:

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
   }
}

Now you can add an event handler to Logger and subcribe it to the Email.Sent event from just about anywhere in your application and have it do what it needs to do:

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
  }
}

and elsewhere:

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

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

Now your classes are very loosely coupled, and six months down the road, when you decide that you want your Logger to capture more or different information, or even do something totally different when an email is sent, you can change the LogEvent method or the event handler without having to touch the Email class. Furthermore, other classes can also subscribe to the event without having to alter the Email class, and you can have a whole host of things happen when an email is sent.

Now maintaining your code is much easier, and other people are much more likely to reuse your code, because they know they won't have to go digging through the guts of 20 different classes just to make a change to how something is handled.

BIG EDIT: More about delegates. If you read through here: Curiosity is Bliss: C# Events vs Delegates (I'll keep links to a minimum, I promise), you see how the author gets into the fact that events are basically special types of delegates. They expect a certain method signature (ie (object sender, EventArgs e) ), and can have more than one method added to them ( += ) to be executed when the method is raised. There are other differences as well, but these are the main ones you will notice. So what good is a delegate?

Imagine you wanted to give the client of your Email class some options for how to send the mail. You could define a series of methods for this:

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() {...}
}

This works well, but if you want to add more options later, you have to edit your class (as well as the MailMethod enum that is assumed here). If you declare a delegate instead, you can defer this sort of decision to the client and make your class much more flexible:

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) {...}
}

Now a client can use your class with its own methods or provide their own method to send mail just about however they choose:

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

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

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

Now your classes are somewhat less tightly coupled and much easier to maintain (and write tests for!). There are certainly other ways to use delegates, and lambdas sort of take things up a notch, but this should suffice for a bare-bones introduction.

Ok, I understand this answer wont strictly speaking be correct, but I'll tell you how I came to understand them.

All functions have a memory address, and some functions are simple get/set for data. It helps to think of all variables as being functions with only two methods - get and set. You're quite comfortable passing variables by reference, which means (simplistically) you are passing a pointer to their memory, which enables some other code to call their get/set methods, implicitly by using "=" and "==".

Now translate that concept to functions and code. Some code and functions have names (like variable names) which you give them. you are used to executing those functions by calling their name; but the name is just a synonym for their memory location (simplistically). By calling the function you are de-referencing its memory address using its name, and then calling the method which lives at that memory address.

As I said, this is all very simplistic and in various ways, incorrect. But it helps me.

So - is it possible to pass the memory address of a function but not call it ? In the same way you pass a reference to a variable without evaluating it ? Ie what is the equivalent of calling

DoSomeFunction(ref variablePointer) 

Well, the reference to a function is called a delegate. But because a function can also take parameters (which a variable cannot) you need to use a calling syntax more elaborate than just ref. You set up the call you want to make into a delegate structure and pass that delegate stucture to the recipient, who can either immediately evaluate (call) that delegate, or store it for later use.

Its the "store for later use" that is the key to understanding event handlers. The special (and somewhat confusing) syntax around event handlers is just another way of setting up a function pointer (delegate) and add it to a list of function pointers that the recipient class can evaluate at some convenient time.

A simple way of looking at event handlers would be;

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");
      }

It gets slightly more complicated when you want the caller of the delegate to pass in some values to the function, but otherwise all delegate concepts are the same as the concepts of "ref" variables - they are references to code which will be executed at a later date, and typically you pass them as callbacks to other classes, who will decide when and whether to execute them. In earler languages delegates were pretty much the same as "function pointers" or (in beloved long departed Nantucket Clipper) "Code blocks". Its all much more complex than simply passing around a memory address of a block of code, but if you hang on to that concept, you wont go far wrong.

Hope that helps.

The simplest way of thinking about the use of delegates is to think in terms of when you want to call a method, but you don't not yet know which one (or ones) .

Take a control's Click event handler, for example. It uses the EventHandler delegate. The signature is void EventHandler(object sender, EventArgs e); . What this delegate is there for is that when someone clicks the control I want to be able to call zero or more methods that have the EventHandler signature, but I don't know what they are yet . This delegate lets me effectively call unknown future methods.

Another example is LINQ's .Select(...) operator. It has the signature IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) . This method contains a delegate Func<TSource, TResult> selector . What this method does is takes a sequence of values from source and applies an as yet unknown projection to produce a sequence of TResult .

Finally, another good example is Lazy<T> . This object has a constructor with this signature: public Lazy(Func<T> valueFactory) . The job of Lazy<T> is to delay the instantiate of T until the first time it is used, but then to retain that value for all future uses. It presumably is a costly instantiation that would be ideal to avoid if we don't need the object, but if we need it more than one we don't want to be hit with the cost. Lazy<T> handles all the thread locking, etc, to make sure that only one instance of T is created. But the value of T returned by Func<T> valueFactory can be anything - the creators of Lazy<T> has no idea what the delegate will be , and nor should they.

This, to me, is the most fudamentally important thing to understand about delegates.

why would you use events and delegates over calling a method?

In the context of the example you posted, if you wanted to send emails asynchronously, you would have to implement a notification mechanism.

See the SmtpClient implementation for an example: https://msdn.microsoft.com/en-us/library/system.net.mail.smtpclient.sendcompleted%28v=vs.110%29.aspx

If more of an explanation is required, rather than code examples, I'll try to explain why you would use a Delegate or Event in the example you gave above.

Imagine that you wish to know if the email was sent or not after you called Email.Send(). In the Email Class you would have two events - one for a failed send, and one for a successful send. When the Email Class sends without an error, it would look to see if there are any subscribers to the 'SuccessfulSend()' event, and if there are, it raises that event. This would then notify the subscribers that wanted to be informed if the send was successful so that they can perform some other task.

So you could have an event handler that is notified of the successful send, and in this handler, you could call another method (DoMoreWork()). If the Email.Send() failed, you could be notified of this, and call another method that logs the failure for later reference.

With regards to Delegates, if there were three different Email Classes that used different functionality (or servers) to send mail, the client calling the Email.Send() method, could supply the relevant Email Class to use when sending the email. The Email Class would use the IEmail interface, and the three Email Classes would implement IEmail (To, From, Subject, Body, Attachments, HTMLBody etc.), but could perform the interactions/rules in different ways.

One could require a Subject, another require an Attachment, one could use CDONTS, another use a different protocol. The client could determine if it needs to use CDONTS depending on where it is installed, or it could be in an area of the app where an attachment is required, or would format the body in HTML. What this does is to remove the burden of logic from the client and all of the places where these checks and logic should be checked, and move it into the single versions of the relevant Class. Then the client simply calls the Email.Send() after providing the correct object to use in its constructor (or by using a settable property). If a fix or change to a particular email object's code is required - it is carried out in one place rather than finding all areas in the client and updating there. Imagine if your Email Class was used by several different applications...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM