HowTo – Delegates to Lambdas

Source Code

All source code can be found on GitHub here.

My cheat sheet for Delegates can be found here.

This is part of my HowTo in .NET seriies. An overview can be seen here,

 

Intro

Delegates provide a type safe means of defining, assigning and calling methods when it is unknown which methods are to be called until runtime. It follows the Delegation design pattern where a task is delegated to another to perform.

Delegates provide the .NET way of writing the Observer design pattern which is also known as Listener, or Publish-Subscribe design patterns.

Examples of usage include events of  UI elements such as buttons, callbacks upon thread completions such as tasks or calling web services.

It is commonly used as a way to free up a resource or execute a command which was not known until runtime; aka asynchronous method invocation.

A delegate can point to nothing, a method or a list of methods all requiring notification of an event; multicasting or via multicastdelegates.

Delegates have changed their syntax through the versions of .NET, the hardest part of delegates comes from the numerous ways of writing them from initial delegates, method grouping, anonymous methods, lambdas and now events; even lambdas have various syntax notations to add to the complexity! However if you understand the rules, the versions of the syntax then you realise that each iteration of .NET has brought an easier way to write delegates!

Delegates can be split into three parts

  1. Delegate Definition
  2. Registration
    1. Original .net 1 egistration
    2. Method Groups invocation
    3. Anonymous Methods
    4. Lambdas
  3. Execution

Delegate Definition

The definition of the delegate is made up of three parts and is virtually the same as a method signature:

  1. Memory allocation.
  2. The parameter list (optional)
  3. The return type (can be null)

Take the following example:

public delegate bool IsGreaterThan(int aValue, int compareTo);

Notice the only difference to a method signature as found within an Interface is the keyword delegate and also a method accessor. Delegates can be static or instance variables and have any access modifier. The delegate definition defines the required method signature which is required for any listener to register to our broadcaster. If the listener tries to register with an incorrect method signature we will get a compile error.

Registration

Memory Allocation

We are initially going to look at how to implement delegates in .Net 1. The syntax has changed drastically through the .NET version iterations however we still need to understand how to write them in all possible ways; you might have to support or read code written in any one of them! Creating the memory allocation is as simple as creating a variable with the delegate definition as the type. For our example above our type is IsGreaterThan, we would therefore would write:

IsGreaterThan isGreaterThanHandler

Assignment Operator

Defined delegates all inherit from System.MulticastDelegate which in turn inherits from System.Delegate and provide us the ability to assign multiple events and to call them with one line of code. Like any reference type memory allocations, unless they are assigned they are initially null. We can assign methods to them with = or +=. Using = will change the contents of the delegate instance to be only the new method. Using += will add the method to the delegate instance allowing multiple methods to be registered; you can only perform += once an initial assignment has been made.

Assignment

We have already created a delegate definition which takes two ints as parameters and returns a bool. We need to assign a method which matches the same same signature; there are multiple ways we can do this, we will explore each syntax changes in the .NET version iterations.

Original ( .NET 1)

In our original orientation we need to start off writing the actual method we want to register to the delegate. This might sound a bit obvious however in later versions we don’t need to actually do this! Our method will need to match the delegate method signature above. We then create an instance of the delegate passing in the memory allocation of the method group of our require callback function, just like we were creating an instance of a reference type object! A method group is simply the method name without the brackets. If you have multiple overloads the compiler will pick the right one; remember delegates are type safe therefore the clever stuff happens at compile time!

public class DelegatesToLambda
{
	public delegate bool IsGreaterThan(int aValue, int compareTo);

	public static bool IsGreaterThanImplementation(int aValue, int compareTo)
	{
	    return aValue > compareTo;
	}

	public void Test01CreateADelegateInstance()
	{
	    IsGreaterThan isGreaterThanHandler = new IsGreaterThan(DelegatesToLambda.IsGreaterThanImplementation);
	}
}

In the example above we have a static method so we don’t need to create an instance of the class before hand. Also the accessor is public; there is nothing clever with delegates and access types; we still need to respect the law! In this example our code is all within the same class; our method could have been private, also we could access the function as if it was local.

IsGreaterThan isGreaterThanHandler = new IsGreaterThan(IsGreaterThanImplementation);

And yes we can also make use of the var keyword.

var isGreaterThanHandler = new IsGreaterThan(IsGreaterThanImplementation);

Method Group Conversion

We can actually do this:

IsGreaterThan isGreaterThanHandler = IsGreaterThanImplementation;

It is called method group conversion and as you have probably guessed the compile makes the MSIL create an instance of the delegates for us based upon our memory allocation. You can not use the var keyword here as it would not be able to infer which delegate type to use; the compiler is clever but not a mind reader!

Anonymous Methods

Anonymous Methods provide us a way to write the event method and create the delegate instance in-line of the assignment. When you use frameworks like Linq you end up writing a lot of little functions which only really have context in what you are currently doing and there is no real no benefit of having the method outside of that scope for reuse.

IsGreaterThan isGreaterThanHandler = delegate(int aValue, int compareTo) { return aValue > compareTo; };

Lambda Expressions

Lambda Expressions are a shorthand syntax of Anonymous Methods, as far as the runtime is concerned they are the same as the compiler generates the same MSIL. The above could be written with delegates as:

IsGreaterThan isGreaterThanHandler = (aValue, compareTo) => aValue > compareTo;

When compared to the Anonymous Method example we can see we have omitted the delegate keyword, the parameter types and the {} surrounding the method body. Depending upon the required method definition we could have omitted more, we could also have not omitted as much. The only difference we had to do to qualify as a Lambda was to remove the delegate keyword and enter the = >. Our example above could have been written as:

IsGreaterThan isGreaterThanHandler = (int aValue, int compareTo) => { return aValue > compareTo; }

This is probably a little more self explanatory from our current traversal of delegates from .NET 1. The syntax reads:Parameter List = > Method Body; Just like any method definition, which is what we are defining; we just have some syntax to shortcut writing the code. Lambdas themselves can take on multiple forms due to the possible short-cuts we can take with them.

  1. You can omit the parameter type in the parameter list as they can normally be worked out of ‘inferred’.
  2. If you have one line of code which returns a value  you can omit the {}.
  3. If you have one line of code and it returns a value you can omit the return keyword.
  4. If you have one parameter you can omit the () around the parameter list.
  5. If you have no parameters you use an empty ()

Below are some examples highlighting the previous points.

(int x) => { return x > 1; } // Full Lambda notation
x => x > 1; // Points 1,2,3,4
(int x) => return true; // Point 2
() => { int x = 1; return x > 1' } // Point 5
(x, y) => return x > y; // Point 1, 2

Execution

Executing or calling the registered delegate methods is the same as any other method except we use the memory allocation is the delegate instance

var isgreater = isGreaterThanHandler(1,2);

If we have multiple methods registered each one would be called in turn, however only the last registered method would be able to return its value. If you need the value you would need to loop through the delegates invocation list and call each method in turn.

foreach(aDel d in s.isGreaterThanHandler())
{
   var isgreater = d(1,2);
}

Un-Asign

We can then un-assign methods from the delegate instance to them with = null or -=. Using = null will remove all methods from the delegate instance. Using -= allows us to remove just one method though this will require us to keep a handle of the required delegate instance (which has only the method we are interested in ) and removing it from the delegate instance which contains all out methods; if you look at the original assignment we create an instance of a delegate method and then add more instances onto the original one. We can use the new event keyword which provides a little more sane syntax; this is described later on!

Delegate and Generics

We can also replace our types with generic implementations. This example shows string concatenation of two variables with type T along with all our previously mentioned ways of allocating methods.

public delegate string ConcatToString(T aValue, T compareTo);

public static string ConcatToStringImplementation(T aValue, T bValue)
{
    return string.Format("{0}{1}", aValue, bValue);
}

// Full delegate registration
var fullHandler = new ConcatToString(DelegatesTestsWithGenerics.ConcatToStringImplementation);

// Registration with method group conversion
var methodGrpHandler = DelegatesTestsWithGenerics.ConcatToStringImplementation;

// Registration with anonymous method.
var anonMethodHandler = delegate(int aValue, int bValue) { return string.Format("{0}{1}", aValue, bValue); };

// Registration with Lambda
var lambdaHandler = (aValue, compareTo) => string.Format("{0}{1}", aValue, compareTo);

Action, Func and Predicate

A delegate definition contains nothing more than the list of required parameters and the the return type. .Net provide three global delegates which are used extensively in frameworks such as Linq. Each one has numerous overloads; up to 16 different parameters with different types.

Func<T, TResult>: can take take up to 16 parameters and one return type.

Action<T>: can take up to 16 parameters and the return type of null.

Predicate<T> can take up to 16 parameters and return a type of bool.

Func<T>

// Parameters expecting  a Func will looks something like
// Func
// Func<T, TResult>
// Func<T, T, TResult>
// Func<T, T, T, TResult>
// Func<T, T, T..... TResult>

Func<int, int, string> aHandler = delegate(int aInt, int bInt) { return string.Empty; };
Func<int, int, string> bHandler = (anInt, anotherInt) => { return string.Empty; };
Func<int, int, string> cHandler = (anInt, anotherInt) => string.Empty;

Func dHandler = delegate() { return string.Empty; };
Func eHandler = () => string.Empty;

Predicate

// Parameters expecting  a Predicate will looks something like
// Predicate
// Predicate<T, T>
// Predicate<T, T, T>
// Predicate<T, T, T..... T>

Predicate aHandler = delegate(int anInt) { return anInt > 1; };
Predicate bHandler = delegate(int anInt) { return anInt > 1; };
Predicate cHandler = anInt => { return anInt > 1; };
Predicate dHandler = anInt => anInt > 1;

Action

// Parameters expecting  a Predicate will looks something like
// Action
// Action<T, T>
// Action<T, T, T>
// Action<T, T, T..... T>

Action aHandler = delegate(int x) { throw new Exception(); };
Action bHandler = delegate(int x) { throw new Exception(); };
Action cHandler = x => { throw new Exception(); };
// Action dHandler = x => throw new Exception();  // We don't return anything so we can not omit the {} around the method body

Contravariance

Contravariance allows us to call a delegate with a parameter which is of a child or inherited class. In many cases you don’t need to know about the parameters, only to trigger an event. This can save writing multiple delegates!


  public class ChildEventArgs : EventArgs
    {

    }

    public class ContravarianceExample
    {
        // Define the delegate.
        public delegate void RaiseEventWithEventArgs(object sender, EventArgs e);
        public delegate void RaiseEventWithChildEventArgs(object sender, ChildEventArgs e);

        public static void DoWithEventArgs(object sender, EventArgs e)
        {
        }

        public static void DoWithChildEventArgs(object sender, DataReceivedEventArgs e)
        {
        }

        public void TestCovariance()
        {
            RaiseEventWithChildEventArgs raiseEventWithChildEventArgs = DoWithEventArgs;
            raiseEventWithChildEventArgs(this, new ChildEventArgs());
        }
    }

Covariance

Covariance allows us to return a type which is a child of the defined return type of the delegate.

 // Define the delegate.
        public delegate Parent ReutrnParent();

        public static Parent ReturnParent()
        {
            return new Parent {ParentField = "A Parent!"};
        }

        public static Child ReturnChild()
        {
            return new Child { ChildField = "A Child!", ParentField = "A Parent!" };
        }

        public static void TestCovariance()
        {
            ReutrnParent returnParent = ReturnParent;

            var aParent = returnParent();

            // Covariance allows this delegate.
            ReutrnParent returnChild = ReturnChild;

            var aChild = returnChild() as Child;
        }

Multicast delegates and Events

We have mentioned previously that delegates are multicast. We can assign multiple event methods into the instance. During normal instantiation of delegates you create one. When you want to assign another you += another instance onto a previous one.

IsGreaterThan isGreaterThanHandler = IsGreaterThanImplementation;
isGreaterThanHandler += IsGreaterOrEqual;
isGreaterThanHandler(1,2) // execution

To remove a method you use the -= against the delegate instance which contains all methods and either the method group or the instance of the delegate you created.

IsGreaterThan isGreaterThanHandler = IsGreaterThanImplementation;
isGreaterThanHandler += IsGreaterOrEqual;
isGreaterThanHandler(1,2) // execution
isGreaterThanHandler -= IsGreaterOrEqual;

Events allow a common place holder for storing the event methods with functionality to call, add and remove methods without having to ‘keep a handle of the first instance’.

public delegate bool IsGreaterThan(int aValue, int compareTo);
public event IsGreaterThan IsGreaterThanEvent;
IsGreaterThanEvent += IsGreaterThanImplementation;
IsGreaterThanEvent += IsGreaterOrEqual;

IsGreaterThan isGreaterThanHandler = IsGreaterThanImplementation;
isGreaterThanHandler += IsGreaterOrEqual;
isGreaterThanHandler(1,2) // execution
isGreaterThanHandler -= IsGreaterOrEqual;
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s