Friday, 17 July 2015

Delegates and Events

Delegates

Delegates are used to decouple the class that uses a method from the class that declares it.  A delegate wraps a method, so that you can call the method through the delegate.

public delegate int DelegateName(object inputParam)

When specifying a delegate you define the input type(s) and return type of a method that will be wrapped by the delegate.  You don't name the method, and hence the delegate can wrap any method that matches the input type(s) and return type specified in the delegate.  The delegate has no method body - that is provided/defined by the encapsulated method.

You can create a class that contains members and define a delegate.  Now, any client using your class can perform some action with the object instance of your class.  The action will depend upon the method encapsulated by the delegate - it may not be defined in your class containing the delegate.  You can pass delegates as parameters into methods - typically used to check the return type of the delegate.  You pretty much need a method in your class that takes as its parameter your delegate, so that you can use the delegate in a calling class.

The implementing method won't contain anything that indicates it is related to the delegate or your class that contains the delegate.

The calling class instantiates an object of your class.  If the implementing method is actually in a third class then your calling class needs to instantiate an object of that class too.  To instantiate the delegate, create a variable and assign to it yourclass.delegate and specify the class to be encapsulated by the delegate.


Class containing method = Document
Implementing method is "PostToBlog" in a class "BlogPoster".
Calling class:-

Document doc = new Document();  //Instantiate an object of your class
var blogPoster = new BlogPoster();  //Instantiate an object that contains the implementing method

var blogDelegate = new Document.SendDoc(blogPoster.PostToBlog);  //Instantiate the delegate, telling it to encapsulate the method PostToBlog

You're now ready to invoke the delegate.  As it is in your class you execute the method of your class that takes the delegate as its input parameter.

doc.ReportSendingDoc(blogDelegate);

This has dynamically instantiated the delegate and passed it to a method of your class, instructing it to use this delegate instance.  Your delegate will in turn call the implementing method.

You could instantiate a second delegate that encapsulates a different implementing method.  This would be invoked by calling the same method in your class and passing the second delegate.

Events

Events fire (or are raised) when something happens to trigger the event, e.g. a button click.  In C# events are implemented with delegates.  The subscribing class is encapsulated by the delegate so that the subscribing class is invoked when the event fires (delegate is called).

The convention in .Net for events means events always:-

  • have a return type of void
  • take two parameters - An object (the sending object) and EventArgs (or something derived from EventArgs) that could contain helper data.


Events can be used in the publish-subscribe pattern.  The publishing class (your class, above) contains a delegate so that it can raise events.  Remember the delegate won't return anything and the methods encapsulated will take two input parameters.  As well as defining the signature of methods to be encapsulated by the delegate you need to also create a member instance of the delegate that the subscriber methods will subscribe to.  This will be null unless or until someone subscribes to the delegate.  This is a useful feature as you can detect if anybody is subscribed/listening for your events and only raise them if someone is subscribed ok.

The subscribing class needs to subscribe itself to the delegate of the publishing class.  The simplest way to do this is to create a method Subscribe in your subscribing class.  This method takes as its input parameter an object of type publishing class.  In that method you add/append to the delegate your subscription, specifying the name of the class to be encapsulated by the delegate, i.e. the method to be invoked within the subscriber when the event is raised.

public void Subscribe(PublishingClass thePublisher)
{
   thePublisher.DelegateName += new PublishingClass.DelegateDefinition(SubscribingMethod);
}


The += operator adds you to the delegate's subscription list.  -= unsubscribes you.  You can have multiple subscriber to an event.  These need to be added to the list of classes that are subscribed to the delegate, rather than overwrite/replace the list.  The event keyword enforces this at compile time as it prevents you from incorrectly using just the = keyword.  The = operator would set the delegate's subscriber list (handler) to just this method, overwriting any previous subscriptions.

The solution is to use the event keyword into the publishing class at the instantiation of the delegate.

public delegate void DelegateHandler (object publisher, EventArgs otherInfo);
public event DelegateHandler delegate;

Accidentally using the wrong operator = instead of += will be detected and noted as an error at compile time.



Anonymous Methods

A delegate wraps a method so that you can call that method through the delegate.  You register a delegate and then instantiate the delegate, passing it the name of the method to wrap.  Rather than all of that - which forces you to have a separate method - you can just pass in the method itself.  You don't name the method, hence it is anonymous, but you do still use the keyword delegate.

In the events example, above, any class subscribing to an event registered itself with the event and provides the name of the method to be wrapped.  Instead of this

public void Subscribe(PublishingClass thePublisher)
{
   thePublisher.DelegateName += new PublishingClass.DelegateDefinition(SubscribingMethod);
}

you could replace the line after the += with the keyword delegate and the implementation (including signature) of SubscribingMethod thus:-


public void Subscribe(PublishingClass thePublisher)
{
   thePublisher.DelegateName += delegate(object sender, EventArgs e)
   {
              // code of SubscribingMethod.
              // resolve the naming conflict thePublisher by renaming the input param to sender
              // Put a semicolon at the end, below
   };
}

This can be a valid way to do this, provided nothing else was calling your (now anonymous) SubscibingMethod.

A more popular way of doing this is now lambda expressions.

Lambda Expressions

Lambda expressions are a more flexible and powerful syntax for anonymous methods.  The syntax is thus:
(input parameters) => {expression or block evaluating to a value}

=> is "goes to"
Input types are not provided, but type safety is assured.  There's no return keyword because it evaluates to a value.

(x,y) => { x*y };

Read as "x y goes to x times y".

To convert the above implementation of an anonymous method into a lambda expression you:-

  • take out the delegate keyword and input param types
  • Reformat as per coding standards for indentation
  • insert the => goes to operator

public void Subscribe(PublishingClass thePublisher)
{
   thePublisher.DelegateName += 
   (sender, e) =>
   {
              // code of SubscribingMethod.
              // resolve the naming conflict thePublisher by renaming the input param to sender
              // Put a semicolon at the end, below
   };
}


You now have a lambda expression in the registration for the DelegateName event.
You read this as "Register with DelegateName the anonymous delegate represented by this lambda expression that takes two parameters.

Summary


  • Defining and using delegates
    • Delegates wrap/encapsulate any method that matches the signature and return type
    • The delegate has no method body
    • Delegates decouple the declaring class from the consuming class
  • Events and how they 'fix' delegates
  • Anonymous methods
  • Introduction to Lambda Expressions





No comments:

Post a Comment