Wednesday, December 26, 2012

Understanding Generic delegates, The Generic Action<> and Func<> Delegates

Understanding Generic Delegates

For example, assume you want to define a delegate type that can call any method returning void and receiving a single parameter. If the argument in question may differ, you could model this using a type parameter.

Public delegate void MyGenericDelegate<T>(T arg);

static void Main(string[] args)

{

MyGenericDelegate<string> strTarget =  new MyGenericDelegate<string>(StringTarget);       strTarget("Some string data");

MyGenericDelegate<int> intTarget = new MyGenericDelegate<int>(IntTarget);

}

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);     }

Notice that MyGenericDelegate<T> defines a single type parameter that represents the argument to pass to the delegate target. When creating an instance of this type, you are required to specify the value of the type parameter, as well as the name of the method the delegate will invoke. Thus, if you specified a string type, you send a string value to the target method:

// Create an instance of MyGenericDelegate<T> // with string as the type parameter.

MyGenericDelegate<string> strTarget =   new MyGenericDelegate<string>(StringTarget); strTarget("Some string data");

Given the format of the strTarget object, the StringTarget() method must now take a single string as a parameter:

static void StringTarget(string arg) {   Console.WriteLine("arg in uppercase is: {0}", arg.ToUpper()); }

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Over the course of this article, you have seen that when you want to use delegates to enable callbacks in your applications, you typically follow the steps shown here:

• Define a custom delegate that matches the format of the method being pointed to. 

• Create an instance of your custom delegate, passing in a method name as a constructor argument. 

• Invoke the method indirectly, via a call to Invoke() on the delegate object.

When you take this approach, you typically end up with a number of custom delegates that might never be used beyond the current task at hand (e.g., MyGenericDelegate<T>, CarEngineHandler, and so forth). While it may certainly be the case that you do indeed need to have a custom, uniquely named delegate for your project, other times the exact name of the delegate is irrelevant. In many cases, you simply want “some delegate” that takes a set of arguments and possibly has a return value other than void. In these cases, you can make use of the framework’s built-in Action<> and Func<> delegates. To illustrate their usefulness, create a new Console Application project type named ActionAndFuncDelegates. The generic Action<> delegate is defined in the System namespaces of mscorlib.dll and System.Core.dll assemblies. You can use this generic delegate to “point to” a method that takes up to 16 arguments (that ought to be enough!) and returns void. Now recall, because Action<> is a generic delegate, you will need to specify the underlying types of each parameter as well. 

static void DisplayMessage(string msg, ConsoleColor txtColor, int printCount)

{   // Set color of console text.   

ConsoleColor previous = Console.ForegroundColor;  

Console.ForegroundColor = txtColor; 
for (int i = 0; i < printCount; i++)   {     Console.WriteLine(msg);   } 
  // Restore color.    Console.ForegroundColor = previous;

}

static void Main(string[] args)

{

Action<string, ConsoleColor, int> actionTarget = new Action<string, ConsoleColor, int>(DisplayMessage);  

actionTarget("Action Message!", ConsoleColor.Yellow, 5);

}

As you can see, using the Action<> delegate saves you the bother of defining a custom delegate. However, recall that the Action<> delegate can point only to methods that take a void return value. If you want to point to a method that does have a return value (and don’t want to bother writing the custom delegate yourself), you can use Func<>.  The generic Func<> delegate can point to methods that (like Action<>) take up to 16 parameters and a custom return value. To illustrate, add the following new method to the Program class:

// Target for the Func<> delegate. 

static int Add(int x, int y)

{ return x + y; }

Earlier in the chapter, I had you build a custom BinaryOp delegate to “point to” addition and subtraction methods. However, we can simplify our efforts using a version of Func<> that takes a total of three type parameters. Be aware that the final type parameter of Func<> is always the return value of the method. Just to solidify that point, assume the Program class also defines the following method:

static string SumToString(int x, int y) {   return (x + y).ToString(); }

Now, our Main() method can call each of these methods, as so:

Func<int, int, int> funcTarget = new Func<int, int, int>(Add);

int result = funcTarget.Invoke(40, 40);

Console.WriteLine("40 + 40 = {0}", result); 
Func<int, int, string> funcTarget2 = new Func<int, int, string>(SumToString);

string sum = funcTarget2(90, 300);

Console.WriteLine(sum);

 

So, given that Action<> and Func<> can save you the step of manually defining a custom delegate, you might be wondering if you should use them all the time. The answer, like so many aspects of programming is “it depends.” In many cases, Action<> and Func<> will be the preferred course of action (no pun intended). However, if you need a delegate that has a custom name that you feel helps better capture your problem domain, building a custom delegate is as simple as a single code statement. You’ll see both approaches as you work over the remainder of this text. 

No comments:

Post a Comment