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.

Other examples such as Use delegates to pass a method's address to another method in C# show how to use delegates. Briefly a delegate is a programmer-defined data type that represents a method. For example, the previous example uses the following code to declare 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 returns a float value.

Later the program can use this delegate type to represent variables, parameters, or return values. The previous example uses the following code to pass a method with the FofXY signature to the DrawGraph method.

// Draw the indicated function.
private void DrawGraph(FofXY func)
{
    ...
}

The DrawGraph method can invoke whatever method is passed in 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 type of method not some more data-oriented piece of information such as an integer, struct, or even a class.)

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. (The example available for download is similar to the earlier example that graphs equations but it uses this form instead of defining its own delegate type.)

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.

   

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this post.
Comments
  • No comments exist for this post.
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.