All notes
Delega

Intro

Declaration. A delegate is a reference type that can be used to encapsulate a named or an anonymous method. It has a return value and any number of parameters of any type (see below).

Instantiation. A delegate can be instantiated by associating it either with a named or anonymous method. Any method from any accessible class or struct that matches the delegate type can be assigned to the delegate. The method can be either static or an instance method.

It allows a method to accept a delegate as a parameter, and call the delegate at some later time. This is known as an asynchronous callback, and is a common method of notifying a caller when a long process has completed.



// Declaration.
delegate double MathAction(double num);

class DelegateTest
{
    // Regular method that matches signature:
    static double Double(double input)
    {
        return input * 2;
    }

    static void Main()
    {
        // Instantiate delegate with named method:
        MathAction ma = Double;
        // Invoke delegate ma:
        double multByTwo = ma(4.5);
        Console.WriteLine("multByTwo: {0}", multByTwo);

        // Instantiate delegate with anonymous method:
        MathAction ma2 = delegate(double input)
        {
            return input * input;
        };
        double square = ma2(5);
        Console.WriteLine("square: {0}", square);

        // Instantiate delegate with lambda expression
        MathAction ma3 = s => s * s * s;
        double cube = ma3(4.375);
        Console.WriteLine("cube: {0}", cube);
    }
    // Output:
    // multByTwo: 9
    // square: 25
    // cube: 83.740234375
}

Delegates have the following properties:

In the context of method overloading, the signature of a method does not include the return value. But in the context of delegates, the signature does include the return value. In other words, a method must have the same return type as the delegate.

This ability to refer to a method as a parameter makes delegates ideal for defining callback methods.

Multicasting

A delegate can call more than one method when invoked. This is referred to as multicasting.


public delegate void Del(string message);

public static void DelegateMethod(string message)
{
    System.Console.WriteLine(message);
}
public class MethodClass
{
    public void Method1(string message) { }
    public void Method2(string message) { }
}

MethodClass obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;

//Both types of assignment are valid.
Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;

// Now Method1, Method2, and DelegateMethod will be called in order in calling allMethodsDelegate.

//remove Method1
allMethodsDelegate -= d1;

// copy AllMethodsDelegate while removing d2
Del oneMethodDelegate = allMethodsDelegate - d2;

Although the delegate can use an out parameter, we do not recommend its use with multicast event delegates because you cannot know which delegate will be called (wcfNote: only the last delegate method's return value is returned).

System.Delegate, System.MulticastDelegate

Delegate types are derived from System.Delegate.

Delegates with more than one method in their invocation list derive from MulticastDelegate, which is a subclass of System.Delegate.


// to find the number of methods in a delegate's invocation list:
int invocationCount = d1.GetInvocationList().GetLength(0);

Comparing delegates of two different types assigned at compile-time will result in a compilation error. If the delegate instances are statically of the type System.Delegate, then the comparison is allowed, but will return false at run time.


delegate void Delegate1();
delegate void Delegate2();

static void method(Delegate1 d, Delegate2 e, System.Delegate f)
{
    // Compile-time error.
    //Console.WriteLine(d == e);

    // OK at compile-time. False if the run-time type of f 
    // is not the same as that of d.
    System.Console.WriteLine(d == f);
}

Action


// Encapsulates a method that has no parameters and does not return a value.
public delegate void Action();

Variances in Delegates

Covarian and Contravariance

Wikipedia: covariance and contravariance.

Within the type system of a programming language, a typing rule or a type constructor is:

  covariant if it preserves the ordering of types (≤, "is subtype of"), which orders types from more specific to more generic, e.g. Cat < Animal;
  contravariant if it reverses this ordering;
  bivariant if both of these apply (i.e., both I<A> ≤ I<B> and I<B> ≤ I<A> at the same time);
  invariant or nonvariant if neither of these applies.

For example, in C#:

  IEnumerable<Cat> is a subtype of IEnumerable<Animal>. The subtyping is preserved because IEnumerable<T> is covariant on T.
  Action<Animal> is a subtype of Action<Cat>. The subtyping is reversed because Action<T> is contravariant on T.
  Neither IList<Cat> nor IList<Animal> is a subtype of the other, because IList<T> is invariant on T.

The variance of a C# interface is determined by in/out annotations on its type parameters; the above interfaces are declared as IEnumerable<out T>, Action<in T>, and IList<T>. Types with more than one type parameter may specify different variances on each type parameter. For example, the delegate type Func<in T, out TResult> represents a function with a contravariant input parameter of type T and a covariant return value of type TResult.

The typing rules for interface variance ensure type safety. For example, an Action<T> represents a first-class function expecting an argument of type T, and a function that can handle any type of animal can always be used instead of one that can only handle cats.

These terms come from the notion of covariant and contravariant functors in category theory.

In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments. Covariance preserves assignment compatibility and contravariance reverses it.

The following code demonstrates the difference between assignment compatibility, covariance, and contravariance.



// Assignment compatibility. 
string str = "test";
// An object of a more derived type is assigned to an object of a less derived type. 
object obj = str;

// Covariance. 
// An object that is instantiated with a more derived type argument 
// is assigned to an object instantiated with a less derived type argument. 
// Assignment compatibility is preserved. 
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings;

// Contravariance.           
// Assume that the following method is in the class: 
// static void SetObject(object o) { } 
Action<object> actObject = SetObject;
// An object that is instantiated with a less derived type argument 
// is assigned to an object instantiated with a more derived type argument. 
// Assignment compatibility is reversed. 
Action<string> actString = actObject;

Covariance is not type safe, as shown in the following code example:


object[] array = new String[10];
// The following statement produces a run-time exception.
// array[0] = 10;

static object GetObject() { return null; }
static void SetObject(object obj) { }

static string GetString() { return ""; }
static void SetString(string str) { }

static void Test()
{
    // Covariance. A delegate specifies a return type as object,
    // but you can assign a method that returns a string.
    Func<object> del = GetString;

    // Contravariance. A delegate specifies a parameter type as string,
    // but you can assign a method that takes an object.
    Action<string> del2 = SetObject;
}

In delegates

covariance and contravariance provide flexibility for matching a delegate type with a method signature. Covariance permits a method to have return type that is more derived than that defined in the delegate. Contravariance permits a method that has parameter types that are less derived than those in the delegate type.

Introduced since .NET Framework 3.5.

Co-variance


class Mammals{}
class Dogs : Mammals{}

class Program
{
    // Define the delegate.
    public delegate Mammals HandlerMethod();

    public static Mammals MammalsHandler()
    {
        return null;
    }

    public static Dogs DogsHandler()
    {
        return null;
    }

    static void Test()
    {
        HandlerMethod handlerMammals = MammalsHandler;

        // Covariance enables this assignment.
        HandlerMethod handlerDogs = DogsHandler;
    }
}

Contra-variance


// Event hander that accepts a parameter of the EventArgs type.
private void MultiHandler(object sender, System.EventArgs e)
{
    label1.Text = System.DateTime.Now.ToString();
}

public Form1()
{
    InitializeComponent();

    // You can use a method that has an EventArgs parameter,
    // although the event expects the KeyEventArgs parameter.
    this.button1.KeyDown += this.MultiHandler;

    // You can use the same method 
    // for an event that expects the MouseEventArgs parameter.
    this.button1.MouseClick += this.MultiHandler;

}

Func and Action



/////////////// Covariance

// Simple hierarchy of classes.
public class Person { }
public class Employee : Person { }
class Program
{
    static Employee FindByTitle(String title)
    {
        // This is a stub for a method that returns
        // an employee that has the specified title.
        return new Employee();
    }

    static void Test()
    {
        // Create an instance of the delegate without using variance.
        Func<String, Employee> findEmployee = FindByTitle;

        // The delegate expects a method to return Person,
        // but you can assign it a method that returns Employee.
        Func<String, Person> findPerson = FindByTitle;

        // You can also assign a delegate 
        // that returns a more derived type 
        // to a delegate that returns a less derived type.
        findPerson = findEmployee;

    }
}

/////////////// Contravariance

public class Person { }
public class Employee : Person { }
class Program
{
    static void AddToContacts(Person person)
    {
        // This method adds a Person object
        // to a contact list.
    }

    static void Test()
    {
        // Create an instance of the delegate without using variance.
        Action<Person> addPersonToContacts = AddToContacts;

        // The Action delegate expects 
        // a method that has an Employee parameter,
        // but you can assign it a method that has a Person parameter
        // because Employee derives from Person.
        Action<Employee> addEmployeeToContacts = AddToContacts;

        // You can also assign a delegate 
        // that accepts a less derived parameter to a delegate 
        // that accepts a more derived parameter.
        addEmployeeToContacts = addPersonToContacts;
    }
}