Delegates

 

Delegates are another type of object supported in .Net that encapsulate function calls in an object-oriented manner i.e., providing type safety at run time. Languages like C and C++ have used function pointers to call a function indirectly and to provide a generic approach to handling different data types. For example, if you are creating a general purpose sorting class that can sort any kind of data such as integers, floats, characters, and even objects of other classes, then in this case, you will need to specify how to compare two objects of a class whose objects you need to sort . Your generic sorting class can simply delegate the comparison to the compare function written in the class whose objects are being sorted.

 

            Delegates in C# are used to provide writing general purpose classes that can operate on different data types, starting execution of threads, event handling, call back mechanisms, providing MDI interface implementations etc..

 

            Declaring a delegate in C# is extremely simple. Delegates are derived from the System.Delegate class, but your declaration of a delegate does not look like a class declaration. You simply declare the prototype for a function and prefix it with the delegate keyword, e.g.,

            delegate void SomeFunction(int a) ;    // defines a delegate type

 

            private delegate string ReportResult( ) ;  // another delegate type

 

To create the delegate object, you invoke its constructor and pass one parameter to it which is the name of the function to which the delegate will point to (if you are confused, think delegates are somewhat equivalent to function pointers). The parameter being passed to the delegate function must have the same prototype as declared by the delegate declaration, e.g., Suppose a class X has a member function called X having code as:

string f1( ) { …}

 

Then inside some class Y, you will create the delegate object as:

            X x1 = new X( );

ReportResult MyDelegate = new ReportResult(x1.f1);

 

 

Some where in another function of class Y, if you made the call:

 

String res = MyDelegate( ) ;  // calls x1.f1( )

 

 

Example: Create a console type application named “DelegateEx” with the following code in it.

 

using System;

 

namespace DelegateEx

{

 

  delegate string ReportClassData();

 

  class TestDelegate

  {

    int a, b;

 

    TestDelegate() // constructor

    {

      a = 5;

      b = 7;

    }

   

    /// <summary>

    /// The main entry point for the application.

    /// </summary>

    [STAThread]

    static void Main(string[] args)

    {

      TestDelegate tg1 = new TestDelegate();

      ReportClassData rpcd = new ReportClassData(tg1.ToString);

      // now rpcd is a delegate object pointing to ToString

      Console.WriteLine(rpcd());  // call rpcd which points to ToString

    }

 

    override public string ToString()

    {

      return ("a: " + a + " b: " + b);

    }

  }

}

 

If you run the above example, you will see the following output:

 

A delegate can point to a member function of a particular object or a static member function. In case, the delegate is pointing to a static member function of a class, the object creation of the delegate will look like:

            ReportClassData stDel = new ReportClassData(MyClass.f2);

where f2 is declared a static function inside MyClass and has a return type of string.

You can also create am array of delegate objects, each pointing to different functions.

 

Example:

            Add another class to your project. Name the class, SimpleMath. Type the following code in it.

  public class SimpleMath

  {

    public SimpleMath()

    {

    }

    public static double SquareRoot(double value)

    {

      return Math.Sqrt(value);

    }

 

    public static double Square(double value)

    {

      return value*value;

    }

  }

 

 

Modify the main code so that it looks like as shown below. The modifications are shown in bold.

using System;

 

namespace DelegateEx

{

 

  delegate string ReportClassData();

  delegate double SimpleMathOp(double x);

 

  class TestDelegate

  {

    int a, b;

 

    TestDelegate() // constructor

    {

      a = 5;

      b = 7;

    }

   

    /// <summary>

    /// The main entry point for the application.

    /// </summary>

    [STAThread]

    static void Main(string[] args)

    {

      TestDelegate tg1 = new TestDelegate();

      ReportClassData rpcd = new ReportClassData(tg1.ToString);

      // now rpcd is a delegate object pointing to ToString

      Console.WriteLine(rpcd());  // call rpcd which points to ToString

   

      //---- creating an array of delegates

      SimpleMathOp [] delArr =

      {

        new SimpleMathOp(SimpleMath.SquareRoot),

        new SimpleMathOp(SimpleMath.Square)

      };

 

      for (int i=0 ; i<delArr.Length ; i++)

      {

        Console.WriteLine("Using Math op [{0}]:", i);

        Console.WriteLine(delArr[i](64.0));

        Console.WriteLine();

      }

    }

 

    override public string ToString()

    {

      return ("a: " + a + " b: " + b);

    }

  }

}

 

 

You can also multicast delegates i.e., one delegate can point to several different functions as long as they have the proper prototype, and also the return type of each delegate is void.

 

Let us modify our previous example to use a single delegate to point to both the Square and SquareRoot functions in the SimpleMath class. The major change that we will need to make is that the return type of both the Square and SquareRoot functions will need to be changed to void. To get the data back from the SimpleMath class, we can add data members to it called sqResult and sqrtResult.

The modified SimpleMath class is shown below.

 

using System;

 

namespace DelegateEx

{

  /// <summary>

  /// Summary description for SimpleMath.

  /// </summary>

  public class SimpleMath

  {

    double sqResult;

    public double SqResult

    {

      get { return sqResult; }

    }

 

    double sqrtResult;

    public double SqrtResult

    {

      get { return sqrtResult; }

    }

   

    public SimpleMath()

    {

    }

    public void SquareRoot(double value)

    {

      sqrtResult = Math.Sqrt(value);

    }

 

    public void Square(double value)

    {

      sqResult = value*value;

    }

  }

}

 

The modified main code is shown below:

 

using System;

 

namespace DelegateEx

{

 

  delegate string ReportClassData();

  delegate double SimpleMathOp(double x);

  delegate void MultiCastOp(double x);

 

  class TestDelegate

  {

    int a, b;

 

    TestDelegate() // constructor

    {

      a = 5;

      b = 7;

    }

   

    /// <summary>

    /// The main entry point for the application.

    /// </summary>

    [STAThread]

    static void Main(string[] args)

    {

      TestDelegate tg1 = new TestDelegate();

      ReportClassData rpcd = new ReportClassData(tg1.ToString);

      // now rpcd is a delegate object pointing to ToString

      Console.WriteLine(rpcd());  // call rpcd which points to ToString

   

      //---- creating an array of delegates

  /*    SimpleMathOp [] delArr =

      {

        new SimpleMathOp(SimpleMath.SquareRoot),

        new SimpleMathOp(SimpleMath.Square)

      };

 

      for (int i=0 ; i<delArr.Length ; i++)

      {

        Console.WriteLine("Using Math op [{0}]:", i);

        Console.WriteLine(delArr[i](64.0));

        Console.WriteLine();

      } */

 

      SimpleMath sm1 = new SimpleMath();

      MultiCastOp mcdel = new MultiCastOp(sm1.Square);

      mcdel += new MultiCastOp(sm1.SquareRoot);

      mcdel(25);

      Console.WriteLine(sm1.SqrtResult);

      Console.WriteLine(sm1.SqResult);

    }

 

    override public string ToString()

    {

      return ("a: " + a + " b: " + b);

    }

  }

}

 

If you run the program, the output will look as:

 

 

Example of Using Multicast delegates in a Windows Application:

Create a windows application called calculator. Design the user interface to look as:

 

The important code for the different handlers is shown below:

  public class Form1 : System.Windows.Forms.Form

  {

    private double num1, num2;

    string prevOP; // previous operation

    private bool clrDisplay;

 

    public Form1()

    {

      //

      // Required for Windows Form Designer support

      //

      InitializeComponent();

      num1 = 0.0; num2 = 0.0;

      clrDisplay = true;

      prevOP = "";

      lblDisplay.Text = "0";

      Form1_Resize(null,null);

    }

 

    private void NumericClick(object sender, System.EventArgs e)

    {

      Button bt = (Button) sender;

      if (clrDisplay)

      {

        lblDisplay.Text="";

        clrDisplay = false;

      }

 

      switch(bt.Text) // CSharp can switch on strings

      {

        case "0" : lblDisplay.Text = lblDisplay.Text + bt.Text;

          break;

        case "1" : lblDisplay.Text = lblDisplay.Text + bt.Text;

          break;

        case "2" : lblDisplay.Text = lblDisplay.Text + bt.Text;

          break;

        case "3" : lblDisplay.Text = lblDisplay.Text + bt.Text;

          break;

        case "4" : lblDisplay.Text = lblDisplay.Text + bt.Text;

          break;

        case "5" : lblDisplay.Text = lblDisplay.Text + bt.Text;

          break;

        case "6" : lblDisplay.Text = lblDisplay.Text + bt.Text;

          break;

        case "7" : lblDisplay.Text = lblDisplay.Text + bt.Text;

          break;

        case "8" : lblDisplay.Text = lblDisplay.Text + bt.Text;

          break;

        case "9" : lblDisplay.Text = lblDisplay.Text + bt.Text;

          break;

        case "." :

          if (!(lblDisplay.Text.IndexOf(".",0) >= 0))

            lblDisplay.Text = lblDisplay.Text + bt.Text;

          break;

 

      }

     

    }

 

    private void OperationClick(object sender, System.EventArgs e)

    {

      Button btnOp = (Button) sender;

      string op = btnOp.Text;

     

      double result=0.0;

     

      clrDisplay = true;  // clear display on next numeric click

      if (prevOP == "")

        num1 = double.Parse(lblDisplay.Text);

      else

        num2 = double.Parse(lblDisplay.Text);

     

      switch(op)

      {

        case "Sine" : result = Math.Sin(num1);

          prevOP="";

          lblDisplay.Text = result.ToString();

          num1 = result; return;

          break;

        case "Cos" : result = Math.Cos(num1);

          prevOP="";

          lblDisplay.Text = result.ToString();

          num1 = result; return;

          break;

        case "Tan" : result = Math.Tan(num1);

          prevOP="";

          lblDisplay.Text = result.ToString();

          num1 = result; return;

          break;

      }

 

     

      switch (prevOP)

      {

        case "+" :

          result = num1 + num2;

          break;

        case "-" :

          result = num1 - num2;

          break;

        case "*" :

          result = num1 * num2;

          break;

        case "/" :

          result = num1 / num2;

          break;

      }

 

      if (prevOP != "")

      {

        lblDisplay.Text = result.ToString();

        num1 = result;

      }

      if (op == "=")

        prevOP = "";

      else

        prevOP = op;

    }

 

    private void btnC_Click(object sender, System.EventArgs e)

    {

      prevOP="";

      clrDisplay = true;

      lblDisplay.Text="0";

    }

 

    private void Form1_Resize(object sender, System.EventArgs e)

    {

      int rtopButton = ClientRectangle.Height/5;

 

      foreach (Control ct in this.Controls)

      {

        if (ct is Label)

        {

          Rectangle rdisp = new Rectangle();

          rdisp.Width = ClientRectangle.Width;

          rdisp.Height = ClientRectangle.Height/5;

          rdisp.Inflate(-10,-10);

          SetControlSize(ct,rdisp);

        }

        if (ct is Button)

        {

          int ypos = int.Parse(ct.Name[ct.Name.Length-1].ToString());

          int xpos = int.Parse(ct.Name[ct.Name.Length-2].ToString());

          Rectangle rbt = new Rectangle();

          rbt.Width = ClientRectangle.Width/5;

          rbt.Height = ClientRectangle.Height/5;

          Point ptopleft = new Point(ypos*ClientRectangle.Width/5,

            xpos*ClientRectangle.Height/5);

          rbt.Location = ptopleft;

          rbt.Inflate(-10,-10);

          SetControlSize(ct,rbt);

        }

 

      }

 

    }

 

    private void SetControlSize(Control ct, Rectangle rc)

    {

      ct.Width = rc.Width;

      ct.Height = rc.Height;

      ct.Location = rc.Location;

    }

   

  }

}