Delegates and events#
A delegate is an object that knows how to call a method. A delegate type defines the kind of method that delegate instances can call. Specifically, it defines the method’s return type and its parameter types.
delegate int Transformer (int x);
class Test
{
static void Main()
{
Transformer t = Square; // Create delegate instance
int result = t(3); // Invoke delegate, t.invoke() also works.
Console.WriteLine (result); // 9
}
static int Square (int x) => x * x;
}
Using Delegate as utility functions#
In this example, we have a utility method named Transform that applies a transform to each element in an integer array. Our Transform method is a higher-order function because it’s a function that takes a function as an argument.
public delegate int Transformer (int x);
class Util
{
public static void Transform (int[] values, Transformer t)
{
for (int i = 0; i < values.Length; i++)
values[i] = t (values[i]);
}
}
class Test
{
static void Main()
{
int[] values = { 1, 2, 3 };
Util.Transform (values, Square); // Hook in the Square method
foreach (int i in values)
Console.Write (i + " "); // 1 4 9
}
static int Square (int x) => x * x;
}
Multicast Delegate#
All delegate instances have multicast capability. This means that a delegate instance can reference not just a single target method, but also a list of target methods. The + and += operators combine delegate instances. They are invoked in the order in which they are added.
SomeDelegate d = SomeMethod1;
d += SomeMethod2;
d() // both get invoked
d -= SomeMethod1;
d() // now only SomeMethod2
Generic Delegate#
A delegate type can contain generic type parameters.
public delegate T Transformer<T> (T arg);
With generic delegates, it becomes possible to write a small set of delegate types that are so general they can work for methods of any return type and any (reasonable) number of arguments.
Action#
Generic delegate type for methods with any parameters and no return value.
delegate voidAction();
delegate voidAction<inT1> (T1arg);
delegate voidAction<inT1, inT2> (T1arg1, T2arg2);
private static void ActionDelegateExample()
{
Action<string> act = ShowMessage;
act("C# Langauge")
}
private static void ShowMessage(string message)
{
Console.WriteLine(message);
}
Func#
Generic delegate type for methods with any parameters and a return value.
delegate TResult Func<out TResult> ();
delegate TResult Func<in T1, out TResult> (T1 arg);
delegate TResult Func<in T1, in T2, out TResult> (T1 arg1, T2 arg2);
public void FuncDelegateExample()
{
Func<string, string> convertMethod = UppercaseString;
Console.WriteLine(convertMethod("Dakota"));
}
private string UppercaseString(string inputString)
{
return inputString.ToUpper();
}
Predicate#
Generic delegate type for methods with a single parameter and a return type bool.
class List <T>
{
List <T> FindAll (Predicate <T> match);
T Find(Predicate <T> match);
}
bool GreaterThan10(int x) => return x > 10;
void Main() {
var listOfNumbers = new int [] {1, 2, 25, 3, 11}.ToList();
var firstMatch = listOfNumbers.Find(GreaterThan10);
}
Events#
When using delegates, two emergent roles commonly appear: broadcaster and subscriber. Broadcaster is a type that contains a delegate field and decides when to invoke the delegate. A subscriber decides when to start and stop listening by calling += and -= on the broadcaster’s delegate.
using System;
public class PriceChangedEventArgs : EventArgs
{
public readonly decimal LastPrice;
public readonly decimal NewPrice;
public PriceChangedEventArgs (decimal lastPrice, decimal newPrice)
{
LastPrice = lastPrice; NewPrice = newPrice;
}
}
public delegate void PriceChangedEvent(object source, PriceChangedEventArgs args);
public class Stock
{
string symbol;
decimal price;
public Stock(string symbol) => this.symbol = symbol;
public event PriceChangedEvent PriceChanged;
protected virtual void OnPriceChanged(PriceChangedEventArgs e)
{
PriceChanged?.Invoke (this, e);
}
public decimal Price
{
get => price;
set
{
if (price == value) return;
decimal oldPrice = price;
price = value;
OnPriceChanged(new PriceChangedEventArgs (oldPrice, price));
}
}
}
class Test
{
static void Main()
{
Stock stock = new Stock ("THPW");
stock.Price = 27.10M;
// Register with the PriceChanged event
stock.PriceChanged += stock_PriceChanged;
stock.Price = 31.59M;
}
static void stock_PriceChanged (object sender, PriceChangedEventArgs e)
{
if ((e.NewPrice - e.LastPrice) / e.LastPrice > 0.1M)
Console.WriteLine ("Alert, 10% stock price increase!");
}
}