Title: Use the predefined Action, Func, and EventHandler delegate types in C#
This and the next couple of posts discuss delegates and the somewhat related topics of anonymous methods and lambda expressions.
Briefly, delegate types are programmer-defined data types that represent methods. For example, the following code declares a delegate type.
// Define a delegate type named FofXY.
private delegate float FofXY(float x, float y);
The FofXY type represents methods that take two float values as parameters and that return a float value.
Later the program can use delegate types to declare variables, parameters, or return values. The following code uses the type to define the parameter to the DrawGraph method.
// Draw the indicated function.
private void DrawGraph(FofXY func)
{
...
}
The DrawGraph method can invoke whatever method is passed into it for the func parameter just as it would use func if it were a normal method. For example, it could use the following code to evaluate the function for the X and Y values 1.2 and 6.5.
float result = func(1.2f, 6.5f);
This can be a bit confusing because a delegate represents a kind of method instead of some more data-oriented piece of information such as an int, struct, or object.
Defining delegate types isn't hard, but it does add one extra step when you want to declare a variable or parameter to have a method type. To make that kind of declaration easier, the .NET Framework defines two generic method types: Func and Action.
One of the simpler Func types is defined as:
public delegate TResult Func<in T1, out TResult>(T1 arg1);
In other words, it represents a method that takes a parameter of type T1 and returns a result of type TResult. For example, suppose the GraphFunction method takes as a parameter a function y = F(x) and graphs it. The following code shows how you could declare this method.
private void GraphFunction(Func<float, float> func)
{
...
}
This is equivalent to the following version that declares its own delegate type.
private delegate float MyFunction(float x);
private void GraphFunction(MyFunction func)
{
...
}
Other versions of the Func type take between 0 and 16 parameters of different types, plus a result type. You can use the version that takes two parameters (plus the return type) to rewrite the declaration of the DrawGraph method shown earlier like this:
// Draw the indicated function.
private void DrawGraph(Func<float, float, float> func)
{
...
}
This version of Func takes two float parameters and returns a float.
In addition to the Func delegate, the .NET Framework also defines a generic Action delegate to represent a method that takes between 0 and 16 parameters and that doesn't return any value. (Or returns void if you prefer.)
For example, suppose the LogMessage method takes two parameters, a string and a method that takes a string as a parameter. The LogMessage method calls its delegate parameter to make it do something to the string. The following code shows how you could define the LogMessage method.
private void LogMessage(string message, Action<string> action)
{
action(message);
}
The .NET Framework defines one other useful delegate type: EventHandler. This type takes two parameters, a non-specific object and an EventArgs object. It is equivalent to Action<object, EventArgs>.
You can use the EventHandler type to make it easier to define your own events. For example, consider the following BankAccount class.
public class BankAccount
{
public delegate void OverdrawnEventHandler(
object sender, OverdrawnArgs e);
public event OverdrawnEventHandler Overdrawn;
public void Debit()
{
OverdrawnArgs args = new OverdrawnArgs();
if (Overdrawn != null) Overdrawn(this, args);
}
}
This code declares the OverdrawnEventHandler delegate to be a method that returns void and that takes as parameters a non-specific object and an OverdrawnArgs object. It then uses the delegate to declare the event. Finally, the Debit method raises the event.
Compare this to the following version that uses the predefined generic EventHandler delegate.
public class BankAccount
{
public event EventHandler<OverdrawnArgs> Overdrawn;
public void Debit()
{
OverdrawnArgs args = new OverdrawnArgs();
if (Overdrawn != null) Overdrawn(this, args);
}
}
Here the class uses the predefined EventHandler delegate instead of defining its own. The code is simpler and easier to read.
In my next few posts, I'll describe anonymous methods and lambda statements that you can use to initialize delegate variables or to pass into methods as if they were delegates.
Download the example to experiment with it and to see additional details.
|