November 13, 2018

807 words 4 mins read

Quick Reference Delegates

Quick Reference Delegates

The essentials of delegates.

Delegates

  • Delegates encapsulate methods.
  • In effect they allow methods to be handled like Types, for instance as a parameter.
  • They can be ‘chained’ together (multicast).
  • Form the basis of events.
  • They are central to LINQ.
  • Can define callback methods.
  • Can define behaviours for implementing the Strategy Pattern.

See Demystifying Delegates Part 1

Declaration

// Generic example
public delegate return-type myDelegate(<ParameterList>);

// Concrete example
public delegate void responseBehaviour(Person borrower);

Matching Methods

Any method that has the same return type and parameter list as a delegate matches that delegate.

 // matchingMethod1 matches myDelegate delegate
public return-type matchingMethod1(<ParameterList>) { ... }

 // matchingMethod2 matches myDelegate delegate
public return-type matchingMethod2(<ParameterList>) { ... }

// askPolitely matches responseBehaviour delegate
public void askPolitely(Person borrower) {
    borrower.say("Y-- Excuse me. You-- I believe you have my stapler?");
}

// theLastStraw matches responseBehaviour delegate
public void theLastStraw(Person borrower) {
    borrower.building.burn();
}

Using delegates for parameter types

When defining method parameters, the delegate can be used like any other type.

// Generic example
public void myMethod(myDelegate paramName) { ... }

// encapsulate matchingMethod1 in myDelegate and pass into myMethod
myMethod(new myDelegate(matchingMethod1));
// alternative syntax for matchingMethod2
myMethod(matchingMethod2);

// Concrete example
public void borrowStapler(responseBehaviour lateResponse) { ... }

// encapsulate askPolitely in responseBehaviour delegate and pass into borrowStapler
borrowStapler(new responseBehaviour(askPolitely));
// alternative syntax for theLastStraw
borrowStapler(theLastStraw);

Built-in Delegate types

There are two built-in generic delegate types: Action and Func (in System). Action and Func delegates can cover many situations meaning you don’t need to create your own. This can be an advantage as it means less coding, but using your own can add context to your code, thus making it easier to read, modify and use. There is a third built-in type Predicate which pre-dates Func. This is similar to (and can be replaced by) Func but provides some additional context.

Action

An Action is a built-in delegate that accepts zero to 16 parameters (of varying type) and has no return type.

// Func declarations
public delegate void Action<>()
public delegate void Action<in T>(T arg)
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2)
...
public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16)

Using Action means you don’t have to declare your own delegates, which reduces code. Using your own delegates may communicate intent better.

// Concrete example
using System; // Action is in system

public void outputUpper(string s) {
    Console.WriteLine(s.ToUpper());
}

// Using Action
public void useAction(Action<string> xformString) {
    xformString("Hello"); // outputs HELLO
}
useAction(outputUpper);

// Roll your own
public delegate void myOwnDelegate(string s);
public void useOwnDelegate(myOwnDelegate xformString) {
    xformString("Hello"); // outputs 5 - the length of Hello    
}
useOwnDelegate(outputUpper);

Func

A Func is a built-in delegate that accepts zero to 16 parameters (of varying type) and has a return type.

// Func declarations
public delegate TResult Func<out TResult>()
public delegate TResult Func<in T, out TResult>(T arg)
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2)
...
public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16)

Using Func means you don’t have to declare your own delegates, which reduces code. Using your own delegates may communicate intent better.

// Concrete example
using System; // Func is in system

public int stringlength(string s) {
    return s.Length;
}

// Using Func
public void useFunc(Func<string, int> stoi) {
    Console.WriteLine(stoi("Hello")); // outputs 5 - the length of Hello    
}
useFunc(stringlength);

// Roll your own
public delegate int myOwnDelegate(string s);
public void useOwnDelegate(myOwnDelegate stoi) {
    Console.WriteLine(stoi("Hello")); // outputs 5 - the length of Hello    
}
useOwnDelegate(stringlength);

Predicate

Predicate accepts one generic parameter and returns a boolean. So a Predicate is the same as Func<T, bool>. Many treat Predicates as being superseded by Func, however as MS Docs say “[A predicate] represents the method that defines a set of criteria and determines whether the specified object meets those criteria.”. In which is a roundabout way of saying predicates are useful for filtering, where-as Func<T, bool> could be used for anything. So using a predicate over Func<T, Bool> may help to communicate intent better.

// Definition
public delegate bool Predicate<T>(T arg)
    
// Concrete example
using System;
Predicate<int> filter = GreaterThan10;
public bool GreaterThan10(int i) {
    return i > 10;
}
comments powered by Disqus