Using delegates in C# (Part 2) - Raj Kaimal

Using delegates in C# (Part 2)

Part 1 of this post can be read here.

We are now about to see the different syntaxes for invoking a delegate and some c# syntactic sugar which allows you to code faster. We have the following console application.

   1: public delegate double Operation(double x, double y);
   2:  
   3: public class Program
   4: {
   5:     [STAThread]
   6:     static void Main(string[] args)
   7:     {
   8:         Operation op1 = new Operation(Division);
   9:         double result = op1.Invoke(10, 5);
  10:         
  11:         Console.WriteLine(result);
  12:         Console.ReadLine();
  13:     }
  14:     
  15:     static double Division(double x, double y) {
  16:         return x / y;
  17:     }
  18: }

Line 1 defines a delegate type called Operation with input parameters (double x, double y) and a return type of double.
On Line 8, we create an instance of this delegate and set the target to be a static method called Division (Line 15)
On Line 9, we invoke the delegate (one entry in the invocation list).
The program outputs 2 when run.

The language provides shortcuts for creating a delegate and invoking it (see line 9 and 11). Line 9 is a syntactical shortcut for creating an instance of the Delegate. The C# compiler will infer on its own what the delegate type is and produces intermediate language that creates a new instance of that delegate. Line 11 uses a a syntactical shortcut for invoking the delegate by removing the Invoke method. The compiler sees the line and generates intermediate language which invokes the delegate. When this code is compiled, the generated IL will look exactly like the IL of the compiled code above.

   1: public delegate double Operation(double x, double y);
   2:  
   3: public class Program
   4: {
   5:     [STAThread]
   6:     static void Main(string[] args)
   7:     {
   8:         //shortcut constructor syntax
   9:         Operation op1 = Division;
  10:         //shortcut invoke syntax
  11:         double result = op1(10, 2);
  12:         
  13:         Console.WriteLine(result);
  14:         Console.ReadLine();
  15:     }
  16:     
  17:     static double Division(double x, double y) {
  18:         return x / y;
  19:     }
  20: }

C# 2.0 introduced Anonymous Methods. Anonymous methods avoid the need to create a separate method that contains the same signature as the delegate type. Instead you write the method body in-line.

There is an interesting fact about Anonymous methods and closures which won’t be covered here. Use your favorite search engine ;-)

We rewrite our code to use anonymous methods (see line 9):

   1: public delegate double Operation(double x, double y);
   2:  
   3: public class Program
   4: {
   5:     [STAThread]
   6:     static void Main(string[] args)
   7:     {
   8:         //Anonymous method
   9:         Operation op1 = delegate(double x, double y) {
  10:             return x / y;
  11:         };
  12:         double result = op1(10, 2);
  13:         
  14:         Console.WriteLine(result);
  15:         Console.ReadLine();
  16:     }
  17:     
  18:     static double Division(double x, double y) {
  19:         return x / y;
  20:     }
  21: }

We could rewrite our delegate to be of a generic type like so (see line 2 and line 9). You will see why soon.

   1: //Generic delegate
   2: public delegate T Operation<T>(T x, T y);
   3:  
   4: public class Program
   5: {
   6:     [STAThread]
   7:     static void Main(string[] args)
   8:     {
   9:         Operation<double> op1 = delegate(double x, double y) {
  10:             return x / y;
  11:         };
  12:         double result = op1(10, 2);
  13:         
  14:         Console.WriteLine(result);
  15:         Console.ReadLine();
  16:     }
  17:     
  18:     static double Division(double x, double y) {
  19:         return x / y;
  20:     }
  21: }

The .NET 3.5 framework introduced a whole set of predefined delegates for us including

public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);


Our code can be modified to use this delegate instead of the one we declared. Our delegate declaration has been removed and line 7 has been changed to use the Func delegate type.

   1: public class Program
   2: {
   3:     [STAThread]
   4:     static void Main(string[] args)
   5:     {
   6:         //Func is a delegate defined in the .NET 3.5 framework
   7:         Func<double, double, double> op1 = delegate (double x, double y) {
   8:             return x / y;
   9:         };
  10:         double result = op1(10, 2);
  11:         
  12:         Console.WriteLine(result);
  13:         Console.ReadLine();
  14:     }
  15:     
  16:     static double Division(double x, double y) {
  17:         return x / y;
  18:     }
  19: }

.NET 3.5 also introduced lambda expressions. A lambda expression is an anonymous function that can contain expressions and statements, and can be used to create delegates or expression tree types. We change our code to use lambda expressions.

   1: public class Program
   2: {
   3:     [STAThread]
   4:     static void Main(string[] args)
   5:     {
   6:         //lambda expression
   7:         Func<double, double, double> op1 = (x, y) => x / y;
   8:         double result = op1(10, 2);
   9:         
  10:         Console.WriteLine(result);
  11:         Console.ReadLine();
  12:     }
  13: }

As a side note, C# 3.0 introduced the keyword var (implicitly typed local variable) where the type of the variable is inferred based on the type of the associated initializer expression. We can rewrite our code to use var as shown below (line 7).  The implicitly typed local variable op1 is inferred to be a delegate of type Func<double, double, double> at compile time.

   1: public class Program
   2: {
   3:     [STAThread]
   4:     static void Main(string[] args)
   5:     {
   6:         //implicitly typed local variable
   7:         var op1 = new Func<double, double, double>((x, y) => x / y);
   8:         double result = op1(10, 2);
   9:         
  10:         Console.WriteLine(result);
  11:         Console.ReadLine();
  12:     }
  13: }

You have seen how we can write code in fewer lines by using a combination of the Func delegate type, implicitly typed local variables and lambda expressions.

Edit Mar 29, 2010 : Fixed typos

Published Sunday, March 28, 2010 9:01 AM by rajbk
Filed under: , , ,

Comments

# re: Using delegates in C# (Part 2)

I may have this wrong but -- I thought you couldn't assign a lambda to an implicitly typed variable?

Monday, March 29, 2010 5:49 AM by Tim Barrass

# re: Using delegates in C# (Part 2)

Your last example won't work, as what you expect is also what I'd expect, but the compiler makers didn't implement:

Error 12 Cannot assign lambda expression to an implicitly-typed local variable

The error is logical, because delegates are type safe. You could define another delegate with the same signature and how is the compiler then supposed to find the correct delegate type to infer? The Func<..> delegates are a framework and not a language feature, which I also find a bit clumsy. I'd prefer untyped but "signature-safe" delegates to avoid these types of problems. Perhaps a new version of C# will address this issue.

Monday, March 29, 2010 7:14 AM by Prakash Punnoor

# re: Using delegates in C# (Part 2)

Great article! it really helped me understand better what is going on with the lambdas and delegates I use everyday :-)

One comment, you say the output of the programs is "5" but it should be "2".

Thanks for writing this ;-)

Monday, March 29, 2010 12:28 PM by Arturo Molina

# re: Using delegates in C# (Part 2)

Thanks Arturo. Fixed.

Monday, March 29, 2010 1:09 PM by rajbk

# re: Using delegates in C# (Part 2)

Good one . Simple way to explain.

Tuesday, March 30, 2010 2:57 AM by Anil

# re: Using delegates in C# (Part 2)

Great of you to fix your last example but not show my comment in which I explained the problem to you... :-/

Thursday, April 1, 2010 5:10 AM by Prakash Punnoor

# re: Using delegates in C# (Part 2)

Thanks Tim and Prakash. I forgot to include the ctor. That's what happens when you write sample code at 2am.

Thursday, April 1, 2010 7:21 AM by rajbk