Sunday, November 06, 2011

C#/.NET Little Wonders: The Generic Action Delegates

Once again, in this series of posts I look at the parts of the .NET Framework that may seem trivial, but can help improve your code by making it easier to write and maintain. The index of all my past little wonders posts can be found here.

Back in one of my three original “Little Wonders” Trilogy of posts, I had listed generic delegates as one of the Little Wonders of .NET.  Later, someone posted a comment saying said that they would love more detail on the generic delegates and their uses, since my original entry just scratched the surface on them.

So over the next few weeks, I’ll be looking at some of the handy generic delegates built into .NET.  For this week, I’ll give a quick overview of delegates and their usefulness.  Then I’ll launch into a look at the Action generic delegates and how they can be used to support more generic, reusable algorithms and classes.
Delegates in a nutshell

Delegates are similar to function pointers in C++ in that they allow you to store a reference to a method.  They can store references to either static or instance methods, and can actually be used to chain several methods together in one delegate.

Delegates are very type-safe and can be satisfied with any standard method, anonymous method, or a lambda expression.  They can also be null as well, so care should be taken to make sure that the delegate is not null before you call it.

So let’s go back to the early days of .NET, before generic delegates, when you typically had to define delegates explicitly.  For example, if we wanted to define a delegate for a logging method, we could define a delegate that takes a string as an argument and returns void:

   1: // This delegate matches any method that takes string, returns nothing
   2: public delegate void Log(string message);

This delegate defines a type named Log that can be used to store references to any method(s) that satisfies its signature (whether instance, static, lambda expression, etc.).

Let’s look at some example code using this delegate type:

   1: // creates a delegate instance named currentLogger defaulted to Console.WriteLine (static method)
   2: Log currentLogger = Console.WriteLine;
   3: currentLogger("Hi Console!");
   5: using(var file = File.CreateText("c:\\sourcecode\\out.txt"))
   6: {
   7:     // changes delegate to now refer to the file instance's WriteLine method
   8:     currentLogger = file.WriteLine;
   9:     currentLogger("Hello file!");
  11:     // now append Console.WriteLine to delegate (instead of replace, adds)
  12:     currentLogger += Console.WriteLine;
  13:     currentLogger("Hi to both!");
  14: }

Let’s examine what’s going on in the code above:

    At line 2, we create an instance of a delegate of type Log and have it refer to a static method that takes a string and returns void by assigning it the method name, Console.WriteLine() has an overload that fits this signature.
    At line 3, we invoke the delegate just like we would any other method, by using the delegate name and passing the arguments in the parenthesis.  If our delegate returned a value, we could assign it like a normal method as well.
    At line 8, we change the delegate to refer to an instance method, in this case the WriteLine() method of the file variable.
    At line 9, we invoke the delegate again, this time it will write to file.
    At line 12, instead of replacing the delegate, we are appending a method to the delegate.  This means that both Console.WriteLine (from the previous assignment) and file.WriteLine() appended with the += will be called.

So, both = and += can be used to assign method references to a delegate, albeit in different ways.  The = releases any previous method(s) referenced, and assigns a new method reference (or you can assign to null to remove all and leave unassigned).  And the += is used to chain a new method reference to any existing method referenced (if any).  Likewise, you can use –= to remove a method from a delegate chain.

Always remember that on the call to invoke the delegate, you should make sure the delegate reference isn’t currently null.  Depending on the structure of your code, you may or may not have to check at the point you call it, but you should at least be logically sure it has a value (by comparing to null) before you invoke it.

Also remember that delegates can be used to refer to anonymous methods or lambda expressions as well:

   1: // assign to an anonymous method
   2: currentLogger = delegate(string message) { Console.WriteLine(message.ToUpper()); };
   3: currentLogger("This will be upper case.");
   5: // assign to a lambda expression
   6: currentLogger = msg => Console.WriteLine(msg.Length);
   7: currentLogger("This will output 19");

Read more: James Michael Hare

Posted via email from Jasper-Net