Tuesday, February 14, 2012

- Delegates & Events & Lambdas

Callback: is a mechanism that let the object communicate back to the entity that created it.
Delegate: is a preferred means of defining and responding to callbacks within applications. delegate is a type-safe object that points to a method (static or nonstatic) or a list of methods that can be invoked at a later time (supports multicasting and asynchronous method invocation). Following are the important pieces of information which delegate maintains:
  • Address of memory in which it makes calls
  • Parameters
  • Return type
Keep in mind you must define delegate to match the signature of the method(s) it will point to. Here is an example:

public delegate int BinaryOp(int num1, int num2);

When a C# compiler processes delegate types, it automatically generates a sealed class deriving from System.MulticastDelegate as it displayed in below picture examined by ildasm.exe: 

delegate details in ildasm.exe

Invoke method used for synchronous manner. BeginInvoke and EndInvoke methods used for asynchronous manner.
Here is compiler-generated BinaryOp:

sealed class BinaryOp : System.MulticasDelegate
{
    public int Invoke(int x, int y);
    public IAsyncResult BeginInvoke(int x, int y, AsyncCallback cb, object state);
    public int EndInvoke(IAsyncResult result);
}

Good to know you may use out, ref and params keyword for delegate parameters. However EndInvoke going to be slightly different; all out/ ref parameters defined by the delegate type will be in the EndInvoke method arguments.

In order to see the list of methods "pointed to" you can use following member of System.MulticastDelegate:

public override sealed Delegate[] GetInvocationList();

Here is properties that expose the delegate target:

//Returns a System.Reflection.MethodInfo object that represents
//details of a static method maintained by the delegate
public MethodInfo Method { get; }

//(Defined at the object level rather than a static method)
//Returns an object that represents the method maintained by the delegate.
//if return value is null, the method to be called is static member.
public object Target { get; }

Here is an example:

// This class contains methods BinaryOp will
// point to.
public class SimpleMath
{
    public int Add(int x, int y)
    { return x + y; }
    public static int  Subtract(int x, int y)
    { return x - y; }
}

class Program
{
    static void Main(string[] args)
    {
        //point to static method
        BinaryOp binaryOp1 = new BinaryOp(SimpleMath.Subtract);
        // .NET delegates can also point to instance methods.
        SimpleMath m = new SimpleMath();
        BinaryOp binaryOp2 = new BinaryOp(m.Add);
        
        DisplayDelegateInfo(binaryOp1);
        DisplayDelegateInfo(binaryOp2);

        Console.WriteLine("20 - 5 is {0}", binaryOp1(20, 5));

        // Invoke Add() method indirectly using delegate object.
        Console.WriteLine("5 + 5 is {0}", binaryOp2.Invoke(5, 5));

        Console.ReadLine();
    }    

    static void DisplayDelegateInfo(Delegate delObj)
    {
        // Print the names of each member in the
        // delegate's invocation list.
        foreach (Delegate d in delObj.GetInvocationList())
        {
            Console.WriteLine("Method Name: {0}", d.Method);
            Console.WriteLine("Type Name: {0}", d.Target);
        }
    }
}
Delegate Example Output
Lets go through more realistic example: We have a Task class which going to inform the caller about the progress of doing the task while its doing it:

class Task
{
    public Task(string name, int load)
    {
        TaskName = name;
        TaskLoad = load;
        TaskIsDone = false;
        progress = 0;
    }

    public int TaskLoad { get; set; }
    public string TaskName { get; set; }

    private bool TaskIsDone;
    private int progress;

    //Define a delegate type:
    public delegate void TaskHandler(string msgForCaller);

    //Define a member variable of this delegate
    private TaskHandler listOfHandlers;

    //Add registration function for the caller
    public void RegisterWithTask(TaskHandler methodToCall)
    {
        //short form for Delegate.Combine()
        listOfHandlers += methodToCall;
    }

    public void UnRegisterWithTask(TaskHandler methodToRemove)
    {
        //short form for Delegate.Remove()
        listOfHandlers -= methodToRemove;
    }

    public void StartTask()
    {
        if (TaskIsDone)
        {
            if (listOfHandlers != null)
                listOfHandlers("nothing left to do");
        }
        else
        {
            if (listOfHandlers != null)
            {
                string msg = string.Format("task is just started. Progress is {0} %", progress);
                listOfHandlers(msg);
            }
            //start doing the task
            //half way done
            progress = 50;

            if (listOfHandlers != null)
            {
                string msg = string.Format("half way done. Progress is {0} %", progress);
                listOfHandlers(msg);
            }
            //finsh the task
            //done
            progress = 100;
            TaskIsDone = true;
            if (listOfHandlers != null)
            {
                string msg = string.Format("task is done. Progress is {0} %", progress);
                listOfHandlers(msg);
            }
        }

    }
    public void DisplayDelegateInfo()
    {
        // Print the names of each member in the
        // delegate's invocation list.
        foreach (Delegate d in listOfHandlers.GetInvocationList())
        {
            Console.WriteLine("Method Name: {0}", d.Method);
            Console.WriteLine("Type Name: {0}", d.Target);
        }
    }   
}

And here is the main class (caller):

class Program
{
    static void Main(string[] args)
    {
        Task task1 = new Task("First Task", 200);
        task1.RegisterWithTask(new Task.TaskHandler(OnTaskEvent));
        //hold on to delegate object in case we want to unregistered later
        Task.TaskHandler handler2 = new Task.TaskHandler(OnTaskEvent2);
        task1.RegisterWithTask(handler2);
        //if you plan to unregister you can do it like this:
        // task1.UnRegisterWithTask(handler2);
        task1.StartTask();

        task1.DisplayDelegateInfo();
        Console.ReadLine();
    }

    public static void OnTaskEvent(string msg)
    {
        Console.WriteLine("______Mesage from Task Object______");
        Console.WriteLine("=> {0}", msg);
        Console.WriteLine("____________________________________");
    }
    public static void OnTaskEvent2(string msg)
    {
        Console.WriteLine(msg.ToUpper());
        Console.WriteLine();
        Console.WriteLine();
    }
}
Result for Delegate second example
Method Group Conversion:
Allows you to supply direct method name, rather than a delegate object, when calling methods that take delegates as arguments. As an example for our task class we can have:

class Program
{
    static void Main(string[] args)
    {
        Task task1 = new Task("First Task", 200);
        task1.RegisterWithTask(OnTaskEvent);
        //if you plan to unregister you can do it like this:
        // task1.UnRegisterWithTask(OnTaskEvent);
        task1.StartTask();
        Console.ReadLine();
    }

    public static void OnTaskEvent(string msg)
    {
        Console.WriteLine("=> Message From Task: {0}", msg);
    }
}
Method Group Convention example output

Delegate Covariance (aka relaxed delegates):
Covariance allows you to build a single delegate that can point to methods returning class types related by classical inheritance.
As an example to illustrate the covariance concept we add ComplexTask class drive from Task class:

class ComplexTaskTask
{
    //...Implementation
}

Main method (the Program class):

class Program
{
    //Define delegate point to Task
    public delegate Task ObtainTaskDelegate();

    public static Task GetTask()
    { return new Task("Simple Task", 100); }

    public static ComplexTask GetComplexTask()
    { return new ComplexTask(); }

    static void Main(string[] args)
    {
        ObtainTaskDelegate targetA = new ObtainTaskDelegate(GetTask);
        Task task1 = targetA();

        ObtainTaskDelegate targetB = new ObtainTaskDelegate(GetComplexTask);
        ComplexTask task2 = (ComplexTask)targetB();
    }
}

Generic Delegates:
Here is an example to illustrate the generic delegates:

// This generic delegate can call any method
// returning void and taking a single type parameter.
public delegate void MyGenericDelegate<T>(T arg);
class Program
{
    static void Main(string[] args)
    {
        // Register targets.
        MyGenericDelegate<string> strTarget =
            new MyGenericDelegate<string>(StringTarget);
        strTarget("Some string data");

        MyGenericDelegate<int> intTarget =
            new MyGenericDelegate<int>(IntTarget);
        intTarget(9);
        Console.ReadLine();    
    }
    static void StringTarget(string arg)
    {
        Console.WriteLine("arg in uppercase is: {0}", arg.ToUpper());
    }

    static void IntTarget(int arg)
    {
        Console.WriteLine("++arg is: {0}", ++arg);
    }
}

As you can see in above example you are required to specify the value of the type parameter as well as the name of the method the delegate will invoke.

C# Event:
Event is a shortcut of delegate, so you don't have to build custom methods to add or remove methods to a delegate's invocation list.
Here the steps to define an event:
  • Define a delegate type that hold the list of methods to be called when the event is fired.
  • Declare an event in terms of the related delegate type.
Consider the following example:

public class Task
{
    public Task(string name, int load)
    {
        TaskName = name;
        TaskLoad = load;
        BoolTaskIsDone = false;
        progress = 0;
    }

    public int TaskLoad { get; set; }
    public string TaskName { get; set; }

    private bool BoolTaskIsDone;
    private int progress;

    //Define a delegate type:
    public delegate void TaskHandler(string msgForCaller);

   //this task can send these events
    public event TaskHandler TaskIsDone;
    public event TaskHandler TaskHalfWayDone;
    public event TaskHandler TaskNotDoneYet;

    public void StartTask()
    {
        if (BoolTaskIsDone)
        {
            if (TaskIsDone != null)
                TaskIsDone("nothing left to do");
        }
        else
        {
            if (TaskNotDoneYet != null)
            {
                  string msg = string.Format("task is just started. Progress is {0} %", progress);
                TaskNotDoneYet(msg);
            }
            //start doing the task
            //half way done
            progress = 50;

            if (TaskHalfWayDone != null)
            {
                string msg = string.Format("half way done. Progress is {0} %", progress);
                TaskHalfWayDone(msg);
            }
            //finish the task
            //done
            progress = 100;
            BoolTaskIsDone = true;
            if (TaskIsDone != null)
            {
                string msg = string.Format("task is done. Progress is {0} %", progress);
                TaskIsDone(msg);
            }
        }

    }
}

As you see in above example there is no need for custom registration functions or declare delegate member variable.

Caller Side:
+= and -= operators can be used simply rather than having to specify custom helper methods. Consider following example:

class Program
{
    static void Main(string[] args)
    {
        Task task1 = new Task("SampleEventTask", 1000);
        //Register event handler
        //NameOfObject.NameOfEvent += new TelateDelegate(functionToCall);
        task1.TaskNotDoneYet += new Task.TaskHandler(TaskNotDoneYet);
        task1.TaskHalfWayDone += new Task.TaskHandler(TaskHalfWayDone);

      

        //task1.TaskIsDone += new Task.TaskHandler(TaskIsDone);
        //or (in order to detach later on)
        Task.TaskHandler task_handler = new Task.TaskHandler(TaskIsDone);

        task1.TaskIsDone += task_handler;

        //simplify version
        //task1.TaskNotDoneYet += TaskNotDoneYet;
        //task1.TaskHalfWayDone += TaskHalfWayDone;
        //task1.TaskIsDone += TaskIsDone;

        //detach from source of event
        //NameOfObject.NameOfEvent -= new RelatedDelegate(functionToCall);
        task1.TaskIsDone -= task_handler;
           
        //simplify version
        //task1.TaskIsDone -= TaskIsDone;

        task1.StartTask();
        Console.ReadKey();
    }

    public static void TaskNotDoneYet(string msg)
    {
        Console.WriteLine("TaskNotDoneYet : {0}", msg);
    }

    public static void TaskHalfWayDone(string msg)
    {
        Console.WriteLine("TaskHalfWayDone : {0}", msg);
    }

    public static void TaskIsDone(string msg)
    {
        Console.WriteLine("TaskIsDone : {0}", msg);
    }
}
Event example output
Visual Studio 2010 simplify your task by providing some IntelliSense and assist you to complete the code by pressing TAB key:
IntelliSense for registering an event handler
Press TAB key:
automatic handler generation
Rename the handler and press TAB key to auto generate the handler in the correct format of the delegate target. Below code will be generated for above case:

static void MyNewHandler(string msgForCaller)
{
    throw new NotImplementedException();
}

Custom Event Arguments:
There are two parameters for underlying delegate when you explore the events sent by a given type in the base class libraries:
  • First parameter: System.Object, which represents a reference to the object that sent the event (e.g TASK)
  • Second parameter: System.EventArgs, which represents information regarding the event at hand.
System.EventArgs base class represents an event that is not sending any custom information:

//System.EventArgs is the base class for classes containing event data.
public class EventArgs
{
    //Represents an event with no event data.
    public static readonly EventArgs Empty;

    //Initializes a new instance of the System.EventArgs class.
    public EventArgs();
}

In order to pass along custom data, you need to build a suitable class deriving from EventArgs.  Here is an example for out Task example:

public class TaskEventArgs : EventArgs
{
    //maintains a string representing the message sent to the receiver
    public readonly string msg;
    public TaskEventArgs(string message)
    {
        msg = message;
    }
}

Update the TaskHandler delegate type definition:

public delegate void TaskHandler(object sender, TaskEventArgs e);

Now in order to fire the events , you need to supply reference to the current Car (this keyword) and an instance of the TaskEventArgs Type. Consider following update:

string msg = string.Format("task is done. Progress is {0} %", progress);
TaskIsDone(this, new TaskEventArgs(msg));

And eventually this is the update needed in caller's side:

public static void TaskIsDone(object sender, TaskEventArgs e)
{
    Console.WriteLine("{0} says: {1}", sender, e.msg);
}

Generic EventHandler<T> Delegate:
You no longer need to define a custom delegate type by using the generic EventHandler<T> type, where T is your custom EventArgs type. Consider the Following update of above example:

public event EventHandler<TaskEventArgs> TaskIsDone;

The Main() method could then use EventHandler<TaskEventArgs> anywhere we previously specified TaskEventHadler(or use method group conversion)

Anonymous Methods
You can avoid define a separate method manually to be called by the delegate object by use of anonymous method. You can directly associate an event to a block of code statements at the time of event registration. as an example:

//TypeContainDelegate del_type = new TypeContainDelegate();
//del_type.TheEvent += delegate (optionalSpecifiedDelegateArgs)
//{.....}

//task1.TaskIsDone += delegate (object sender, TaskEventArgs e)
//or 
task1.TaskIsDone += delegate
{
    Console.WriteLine("Anonymous Method : Task is done");
};

Good to know anonymous methods are able to access the local variables of the method that defines them (aka outer variables). However you need to consider following points about anonymous methods:
  • Cannot access ref or out parameters of the defining method
  • Cannot have a local variable with the same name as local variable of the outer method.
  • Can access instance variables in the outer class scope
  • Can declare local variables with the same name as outer class member variables (hide the outer class member variables).
predicate:
//Represents the method that defines a set of criteria and determines whether
//the specified object meets those criteria. Returns true if obj meets the
//criteria defined within the method represented by this delegate; otherwise, false.
public delegate bool Predicate<in T>(T obj);

Lambda Expressions:
Simplify way of using anonymous methods and delegate type. The parameter of a lambda expression can be explicitly or implicitly typed.           ArgumentsToProcess => StatementToProcessThem
To clarify let's go through one example:

class Program
{
    static void Main(string[] args)
    {
        TraditionalDelegateSyntax();
        AnonymousMethodSyntax();
        Console.WriteLine();
        LambdaExpressionSyntax();
        Console.ReadLine();
    }

    static void TraditionalDelegateSyntax()
    {
        // Make a list of integers.
        List<int> list = new List<int>();
        list.AddRange(new int[] { 20, 1, 4, 8, 9, 44 });

        // Call FindAll() using traditional delegate syntax.
        Predicate<int> callback = new Predicate<int>(IsEvenNumber);
        List<int> evenNumbers = list.FindAll(callback);

        Console.WriteLine("Here are your even numbers:");
        foreach (int evenNumber in evenNumbers)
        {
            Console.Write("{0}\t", evenNumber);
        }
        Console.WriteLine();
    }

    // Target for the Predicate<> delegate.
    static bool IsEvenNumber(int i)
    {
        // Is it an even number?
        return (i % 2) == 0;
    }

    static void AnonymousMethodSyntax()
    {
        // Make a list of integers.
        List<int> list = new List<int>();
        list.AddRange(new int[] { 20, 1, 4, 8, 9, 44 });

        // Now, use an anonymous method.
        List<int> evenNumbers = list.FindAll(delegate(int i)
        { return (i % 2) == 0; });

        Console.WriteLine("Here are your even numbers:");
        foreach (int evenNumber in evenNumbers)
        {
            Console.Write("{0}\t", evenNumber);
        }
        Console.WriteLine();
    }

    static void LambdaExpressionSyntax()
    {
        // Make a list of integers.
        List<int> list = new List<int>();
        list.AddRange(new int[] { 20, 1, 4, 8, 9, 44 });


        //Single statement Lambda
        //List<int> evenNumbers = list.FindAll( i => ( i % 2) == 0);
 
        // Now process each argument within a group of
        // code statements.
        List<int> evenNumbers = list.FindAll((i) =>
        {
            Console.WriteLine("value of i is currently: {0}", i);
            bool isEven = ((i % 2) == 0);
            return isEven;
        });

        Console.WriteLine("Here are your even numbers:");
        foreach (int evenNumber in evenNumbers)
        {
            Console.Write("{0}\t", evenNumber);
        }
        Console.WriteLine();
    }
}
output of above example

Lambda Expression with Multiple Parameter example:


public class SimpleMath
{
    public delegate void MathMessage(string msg, int result);
    private MathMessage mmDelegate;

    public void SetMathHandler(MathMessage target)
    { mmDelegate = target; }

    public void Add(int x, int y)
    {
        if (mmDelegate != null)
            mmDelegate.Invoke("Adding has completed!", x + y);
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Register w/ delegate as a lambda expression.
        SimpleMath m = new SimpleMath();
        m.SetMathHandler((msg, result) =>
        { Console.WriteLine("Message: {0}, Result: {1}", msg, result); });

        // This will execute the lambda expression.
        m.Add(10, 10);
        Console.ReadLine();
    }
}
Output for above example
Here is Lambda syntax for Event exmaple (Task class):

//Lamda exmaple
task1.TaskIsDone += (sender, e) => { Console.WriteLine("Lambd: {0}",e.msg); } ;

Reference: Pro C# 2010 and the .NET 4 Platform by Andrew Troelsen.

No comments:

Post a Comment