C# 11 Delegates

This chapter will continue exploring C# methods by introducing the concepts of C# delegates, including what they are and how to use them.

What is a Delegate?

Before we start exploring C# delegates it first helps to understand the concept of method signatures. When a method is declared, the parameters it accepts and the result type it returns make up the method’s signature. The signature of the following method indicates that it accepts a string value as a parameter and returns an integer:

public int GetLength(string value) {
.
.
}

The following method performs an entirely different task, but has the identical signature as the above GetLength method:

public int CustomerCount(string customer) {
.
.
}

A C# delegate is a type of variable which is used to store a reference to a method. If you are familiar with C or C++ programming, delegates are similar to function pointers.

Once declared, the delegate can be changed to point to any method which matches the delegate’s method signature.

Declaring a delegate

A delegate is declared using the following syntax:

delegate <returntype> <delegatename> (<parameters>);

To declare a delegate that matches our two example methods, we would write the following:

public delegate string MyDelegate (string s);

Using delegates

With a delegate declared, we can use it to store references to methods. Delegate instances a recreated using the new keyword, passing through the method to be referenced, for example:

public delegate string MyDelegate (string s);

MyDelegate delegate1 = new MyDelegate(GetLength);
MyDelegate delegate2 = new MyDelegate(CustomerCount);

In the above code, we have created two delegate instances, each pointing to one of our example methods. Once declared, these can be called just like any other method:

var strlen = delegate1("This is some text");
var count = delegate2("Robert Addison"); 

The following code provides a demonstration of the use of delegates:

class HelloWorld
{

    delegate string StringConverter(string value);

    static string LowerCase(string value) {
      System.Console.Write("Converting to lower case > ");
      return value.ToLower();
    }

    static string UpperCase(string value) {
      System.Console.Write("Converting to upper case > ");
      return value.ToUpper();
    }

    static void Main()
    {
        StringConverter converter1 = new StringConverter(LowerCase);
        StringConverter converter2 = new StringConverter(UpperCase);

        var lower = converter1("This is some TEXT");
        System.Console.WriteLine(lower);

        var upper = converter2("tHis IS sOmE TExt");
        System.Console.WriteLine(upper);
    }
}

The above example declares a delegate named StringConverter that can be used to reference any method with accepts and returns a string value. Next, we declared two methods matching this signature named LowerCase and UpperCase. Finally, two instances of the delegate are created, each referencing one of the two methods. These methods are then called via the delegates and the resulting strings displayed in the console. Since the delegate is a variable, a different method may be assigned at any time.

In the above examples, both delegate1 and delegate2 are single cast in that they reference only one method each. it is also possible to create multicast delegates.

Multicast delegates

A multicast delegate contains more than one delegate allowing it to reference multiple methods. Multicast delegates are created by combining existing delegates using the + operator.

In the following example, a new delegate named multicast is created comprising both delegate1 and delegate2:

class HelloWorld
{

    delegate string StringConverter(string value);

    static string LowerCase(string value) {
      System.Console.Write("Converting to lower case > ");
      return value.ToLower();
    }

    static string UpperCase(string value) {
      System.Console.Write("Converting to upper case > ");
      return value.ToUpper();
    }

    static void Main()
    {
        StringConverter converter1 = new StringConverter(LowerCase);
        StringConverter converter2 = new StringConverter(UpperCase);

        StringConverter multicast = converter1 + converter2;

        var result = multicast("This is some TEXT");
        System.Console.WriteLine(result);
    }
}

When the code is executed, you should see from the console output that both the LowerCase and UpperCase methods were called via the single multicast delegate call. Delegates can be removed from a multicast delegate using the - operator. The following example adapts the above example to remove delegate1 from multicast after the first call:

class HelloWorld
{

    delegate string StringConverter(string value);

    static string LowerCase(string value) {
      System.Console.Write("Converting to lower case > ");
      return value.ToLower();
    }

    static string UpperCase(string value) {
      System.Console.Write("Converting to upper case > ");
      return value.ToUpper();
    }

    static void Main()
    {
        StringConverter converter1 = new StringConverter(LowerCase);
        StringConverter converter2 = new StringConverter(UpperCase);

        StringConverter multicast = converter1 + converter2;

        var result = multicast("This is some TEXT");
        System.Console.WriteLine(result);

        multicast -= converter1; // Remove a delegate

        result = multicast("AfTeR reMovAL oF converter1");
        System.Console.WriteLine(result);
    }
}

When the code executes, the first call to the delegate results in calls to both the LowerCase and UpperCase methods. After the removal of converter2 from the multicast delegate, only UpperCase is called.

Passing a delegate to a method

C# delegates have some useful capabilities, one of which is the ability to be passed to as method parameters.

If you need a method to accept a delegate as a parameter, simply declare the parameter type accordingly in the method signature. Suppose that we need to write a method that takes as parameters an instance of our StringConverter delegate and a string to be converted. Such a method could to be declared as follows:

public static void DisplayText(StringConverter converter) 
{
    // Code to display text  here      
}

The following code example fully implements the DisplayText method and demonstrates how it might be used:

class HelloWorld
{

    delegate string StringConverter(string value);

    static string UpperCase(string value) {
      System.Console.Write("Converting to upper case > ");
      return value.ToUpper();
    }

    static void DisplayText(StringConverter converter, string text) {
        string result = converter(text);
        System.Console.WriteLine($"Converted text = {result}");
    }

    static void Main()
    {
        StringConverter converter = new StringConverter(UpperCase);
        
        DisplayText(converter, "This IS a teST.");
    }
}

Categories