User Tools

Site Tools


notes:csharp:delegates

Delegates in C#

Delegate Types and Instances

Define a delegate type 'Translator':

public delegate string Translator(string text);

Create a delegate instance and invoke it. This, in turn, calls a method the delegate is associated with:

// Define a delegate type.
public delegate string Translator(string text);
...
// Create a method with a signature compatible with the delegate type.
public string GetGermanWord(string word)
{
    string translatedText = "";
    // execute a translation algorithm
    return translatedText;
}
...
// Create a delegate instance and associate it with the GetGermanWord method.
Translator handler = new Translator(GetGermanWord);
 
// Invoke the delegate instance. This, in turn, calls the GetGermanWord method.
 
// Method #1
string helloInGerman = handler("Hello");
 
// Method #2
string helloInGerman = handler.Invoke("Hello");

Create a delegate instance and associate it with an anonymous method:

public delegate string Translator(string text);
...
// Create a delegate instance.
TextTranslator handler = delegate (string word) 
{
    string translatedText = "";
    // execute a translation algorithm
    return translatedText;
};
 
// Invoke the delegate instance.
string translatedHello = handler("Hello");

Create a delegate instance of a built-in delegate type EventHandler:

// Declare a delegate of a built-in type EventHandler.
EventHandler handler;
 
// Method #1 - Create a delegate instance.
handler = new EventHandler(MyMethod);
 
// Method #2 - Create a delegate instance using method group conversion.
handler = MyMethod;
 
// Method #3 - Create a delegate instance with an anonymous method.
handler = delegate (object sender, EventArgs e) { /* some code */ };
 
// Method #4 - Create a delegate instance with an anonymous method that does not require parameters.
handler = delegate { /* some code */ };
 
// Invoke the delegate instance.
handler(this, new EventArgs());
...
// A method with a signature compatible with the EventHandler delegate type.
void MyMethod(object sender, EventArgs e)
{
    /* some code */
}  

Create a delegate instance by providing a method group. Note that the correct overload is picked based on the delegate signature:

public delegate string Translator(string text);
...
public string GetGermanWord(string word)
{
    string translatedText = "";
    // execute a translation algorithm
    return translatedText;
}
 
// An overloaded method which signature is not compatible with the delegate.
public void GetGermanWord(string word, int id)
{
    // execute some code
}
...
// Create a delegate instance by providing a method group. 
Translator handler = GetGermanWord;
 
// Invoke the delegate instance. The method 'string GetGermanWord(string word)' is called.
string helloInGerman = handler("Hello");

Multicast Delegates

Multicast capability of delegate instances means that a delegate instance can reference a list of methods.

  • A delegate instance contains a list of methods called the invocation list.
  • The static Delegate.Combine method splices together the invocation lists of two delegate instances.
  • The static Delegate.Remove method removes the invocation list of one delegate instance from another.
  • Delegates are immutable. Calling += or −= creates a new delegate instance and assigns it to the existing variable.
  • When combining null with a delegate instance, the null is treated as if it were a delegate instance with an empty invocation list.
  • Delegate.Remove(source, value) creates a new delegate whose invocation list is the one from source, with the list from value having been removed
  • All delegate types implicitly derive from System.MulticastDelegate, which in turn inherits from System.Delegate.
  • Delegate types are reference types.

If any of the actions in the invocation list throws an exception, it prevents the subsequent actions from being executed.

Combine and remove delegate instances:

public delegate void MethodDelegate();
 
public void Method1() { Console.WriteLine(1); }
public void Method2() { Console.WriteLine(2); }
public void Method3() { Console.WriteLine(3); }
...
// Combine delegate instances. Delegate.Combine is called under the hood.
MethodDelegate d = Method3;
d += Method1;
d += Method2;
d(); // writes: 3,1,2
 
// Determine how many methods a delegate is going to call.
int cnt = d.GetInvocationList().GetLength(0); // cnt == 3
 
// Remove a delegate instance. Delegate.Remove is called under the hood.
d -= Method1;
d(); // writes: 3,2
 
// Assign a single delegate instance.
d = Method2;
d(); // writes: 2
 
// The same as d = null because d has a single method.
d -= Method2; 

Delegate Type Variance

  • Covariance specifies return type compatibility. It permits a method that has a return type that is more derived than that defined in the delegate.
  • Contravariance specifies parameter compatibility. It permits a method that has parameter types that are less derived than those in the delegate type.

An example of covariance:

using System.IO;
 
namespace ConsoleTest
{
    // TextWriter is a base class for both StreamWriter and StringWriter.
    public delegate TextWriter MyDelegate();
 
    class Program
    {
        // StreamWriter derives from TextWriter.
        public static StreamWriter Method1() { return null; }
 
        // StringWriter derives from TextWriter.
        public static StringWriter Method2() { return null; }
 
        static void Main()
        {
            MyDelegate d;
 
            // Covariance makes the following assignments possible.
            d = Method1; // StreamWriter --> TextWriter
            d = Method2; // StringWriter --> TextWriter
        }
    }
}

An example of contravariance:

A single method MyMethod that accepts EventArgs is invoked by two different delegates, one passing a MouseEventArgs and the other passing a KeyEventArgs.

namespace ConsoleTest
{
    public delegate void MouseDelegate(MouseEventArgs args);
    public delegate void KeyDelegate(KeyEventArgs args);
 
    class Program
    {
        // EventArgs is less derived than MouseEventArgs and KeyEventArgs.
        public static void MyMethod(EventArgs args) { }
 
        static void Main()
        {
            MouseDelegate d1;
            KeyDelegate d2;
 
            // Both delegates can represent MyMethod.
            d1 = MyMethod; // EventArgs --> MouseEventArgs
            d2 = MyMethod; // EventArgs --> KeyEventArgs
        }
    }
}

Func and Action

Generic delegate types Func take parameters of specified types and return a value of another specified type. For example, the Func<int,int> is a shorthand notation for a delegate that takes an int and returns an int. You can also use Action for specifying a delegate that doesn't have a return value.

delegate TResult Func <out TResult> ();
delegate TResult Func <in T, out TResult> (T arg);
delegate TResult Func <in T1, in T2, out TResult> (T1 arg1, T2 arg2);
... etc.
 
delegate void Action ();
delegate void Action <in T> (T arg);
delegate void Action <in T1, in T2> (T1 arg1, T2 arg2);
... etc.

A few examples of Func and Action:

Action print = () => Console.WriteLine("Welcome");
print(); // displays 'Welcome'
 
Action<string> print = s => Console.WriteLine(s);
print("Hello"); // displays 'Hello'
 
Action<string, int> print = (s, n) => { for (int i = 1; i <= n; ++i) Console.WriteLine(s); };
print("Hi", 3); // displays 'Hi' three times
 
Func<string, int> fun = str => str.Length;
int len = fun("test"); // len == 4
 
Func<int, int, string> fun = (x, y) => (x * y).ToString();
string result = fun(2, 3); // result == "6"
 
Func<int, int> square = x => x * x; // the same as: x => { return x * x; };
int result = square(3); // result == 9

Generic Delegate Types

Example: A custom delegate type OperationDelegate with a generic type parameter:

using System;
using System.Collections.Generic;
 
namespace ConsoleTest
{
    public delegate T OperationDelegate<T>(IEnumerable<T> sequence);
 
    static class SequenceHelper
    {
        public static T First<T>(IEnumerable<T> sequence)
        {
            foreach (T element in sequence)
                return element;
 
            throw new InvalidOperationException("Empty collection");
        }
 
        public static T Last<T>(IEnumerable<T> sequence)
        {
            IEnumerator<T> e = sequence.GetEnumerator();
            T v = default(T);
            while (e.MoveNext()) { v = e.Current; }
            return v;
        }
    }
 
    class Program
    {
        // A method that accepts a delegate of type OperationDelegate<T> as a parameter.
        private static void DisplayResults<T>(IEnumerable<T> values, OperationDelegate<T> operation)
        {
            Console.WriteLine(operation(values));
        }
 
        static void Main()
        {
            int[] values = { 3, 7, 13, 8, 5 };
 
            DisplayResults(values, SequenceHelper.First); // 3
            DisplayResults(values, SequenceHelper.Last);  // 5
        }
    }
}

Generic Delegate Type Parameter Variance

  • Mark a type parameter used only on the return value as covariant (out).
  • Mark any type parameters used only on parameters as contravariant (in).
namespace ConsoleTest
{
    // A delegate that supports covariance.
    public delegate T Delegate1<out T>();
 
    // A delegate that supports contravariance.
    public delegate void Delegate2<in T>(T arg);
 
    class Program
    {
        // handlers
        public static T Method1<T>() { T n = default(T); return n; }
        public static void Method2<T>(T arg) { }
 
        static void Main()
        {
            // A delegate that supports covariance.
            Delegate1<string> d1 = Method1<string>;
 
            // Covariance enables this assignment.
            Delegate1<object> o1 = d1;
 
            // ...
 
            // A delegate that supports contravariance.
            Delegate2<object> d2 = Method2<object>;
 
            // Contravariance enables this assignment.
            Delegate2<string> o2 = d2;
        }
    }
}

Delegates vs. Interfaces

Sometimes, instead of a delegate, we can use an interface to provide the same functionality. Compare the following two code snippets:

Snippet #1:

namespace ConsoleTest
{
    public delegate string TranslatorHandler(string word);
 
    public class Translator
    {
        public static void Translate(string[] words, TranslatorHandler handler)
        {
            for (int i = 0; i < words.Length; ++i)
                words[i] = handler(words[i]);
        }
    }
 
    class Program
    {
        public static string SpanishTranslator(string word)
        {
            // ... translate the word
            return word;
        }
 
        static void Main()
        {
            string[] words = { "car", "dog", "octopus" };
            Translator.Translate(words, SpanishTranslator);
        }
    }
}

Snippet #2:

namespace ConsoleTest
{
    public interface ITranslator
    {
        string Translate(string word);
    }
 
    public class Translator
    {
        public static void Translate(string[] words, ITranslator handler)
        {
            for (int i = 0; i < words.Length; ++i)
                words[i] = handler.Translate(words[i]);
        }
    }
 
    class SpanishTranslator : ITranslator
    {
        public string Translate(string word)
        {
            // ... translate the word
            return word;
        }
    }
 
    class Program
    {
        static void Main()
        {
            string[] words = { "car", "dog", "octopus" };
            Translator.Translate(words, new SpanishTranslator());
        }
    }
}

The delegate may be a better choice if the interface defines only a single method or multicast capability is needed.

Also, the delegate is a better choice if the interface has to be implemented multiple times. For example, if we needed to provide PolishTranslator, GermanTranslator, and FrenchTranslator, we would have to create separate classes for each translator:

class PolishTranslator : ITranslator
{
    public string Translate(string word) { /*...translate...*/ return word; }
}
class GermanTranslator : ITranslator
{
    public string Translate(string word) { /*...translate...*/ return word; }
}
class FrenchTranslator : ITranslator
{
    public string Translate(string word) { /*...translate...*/ return word; }
}

Delegate Equality

  • Delegate types are incompatible with each other, even if their signatures are the same.
  • Delegate instances are considered equal if they have the same method targets.
  • Multicast delegates are considered equal if they reference the same methods in the same order.
notes/csharp/delegates.txt · Last modified: 2017/06/01 by leszek