This article gives 11 small examples in C# on delegates, events, anonymous methods, and closures. The examples cover topics such as basic delegates, generic delegates, delegate definition locations, delegate targets, Func, Action, events, anonymous methods, lambda expressions, closures, and the impact of delegate instances on garbage collection.
Introduction
This is a note of loosely coupled 11 small examples in the general subject of delegates, events, anonymous methods, and closures in C#. This note is intended to answer the following questions:
- What is a delegate?
- Can we define delegates with generic types?
- Where can we define delegates in a C# program?
- What is the
Target
property of a delegate instance? - What is
Func<T, TResult>
? - What is
Action<T>
? - What is an event?
- What is an anonymous method?
- What is a lambda expression?
- What is a closure? Does C# have closures?
- How can C# delegate instances "remember" the local variables of the method that creates them in-line?
- Does a delegate instance have any impact on garbage collection?
The 11 examples are really simple examples. If you do not have time to go through them, you can directly go to the summary section to check the answers. If you have some time later, you may come back for the examples.
Background
The attached Visual Studio solution is a simple WPF application. It is developed with Visual Studio 2013.
- The UI of the WPF application is implemented in MainWindow.xaml and MainWindow.xaml.cs files;
- Each of the classes in the Examples folder is one of the 11 examples.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Grid.Row="0" x:Name="btnBasicDelegateExample"
Click="btnBasicDelegateExample_Click">1. Basic Delegate Example</Button>
<Button Grid.Row="1" x:Name="btnGenericDelegateExample"
Click="btnGenericDelegateExample_Click">
2. Generic Delegate Example</Button>
<Button Grid.Row="2" x:Name="btnDelegateEverywhere"
Click="btnDelegateEverywhere_Click">3. Delegate Everywhere!</Button>
<Button Grid.Row="3" x:Name="btnDelegateTarget"
Click="btnDelegateTarget_Click">4. Delegate Target</Button>
<Button Grid.Row="4" x:Name="btnFuncExample"
Click="btnFuncExample_Click">5. Func Example</Button>
<Button Grid.Row="5" x:Name="btnActionExample"
Click="btnActionExample_Click">6. Action Example</Button>
<Button Grid.Row="6" x:Name="btnEventExample"
Click="btnEventExample_Click">7. Event Example</Button>
<Button Grid.Row="7" x:Name="btnAnonymousMethod"
Click="btnAnonymousMethod_Click">8. Anonymous Method</Button>
<Button Grid.Row="8" x:Name="btnLambdaExpression"
Click="btnLambdaExpression_Click">9. Lambda Expression</Button>
<Button Grid.Row="9" x:Name="btnClosureExample"
Click="btnClosureExample_Click">10. Closure Example</Button>
<Button Grid.Row="10" x:Name="btnClosureTargetExample"
Click="btnClosureTargetExample_Click">
11. Closure Delegate Target</Button>
</Grid>
The MainWindow.xaml declared 11 buttons. Each of the classes in the Examples folder implements a TryExample()
method. Clicking on each of the buttons will run the TryExample()
method of one of the examples.
using System.Windows;
using Delegate_Func_Action_M.Examples;
namespace Delegate_Func_Action_M
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnBasicDelegateExample_Click(object sender, RoutedEventArgs e)
{
new E_1_Basic_Delegate().TryExample();
}
private void btnGenericDelegateExample_Click(object sender, RoutedEventArgs e)
{
new E_2_Generic_Delegate().TryExample();
}
private void btnDelegateEverywhere_Click(object sender, RoutedEventArgs e)
{
new E_3_Delegate_Everywhere().TryExample();
}
private void btnDelegateTarget_Click(object sender, RoutedEventArgs e)
{
new E_4_Delegate_Target().TryExample();
}
private void btnFuncExample_Click(object sender, RoutedEventArgs e)
{
new E_5_Func_Example().TryExample();
}
private void btnActionExample_Click(object sender, RoutedEventArgs e)
{
new E_6_Action_Example().TryExample();
}
private void btnEventExample_Click(object sender, RoutedEventArgs e)
{
new E_7_Event_Example().TryExample();
}
private void btnAnonymousMethod_Click(object sender, RoutedEventArgs e)
{
new E_8_Anonymous_Method().TryExample();
}
private void btnLambdaExpression_Click(object sender, RoutedEventArgs e)
{
new E_9_Lambda_Expression().TryExample();
}
private void btnClosureExample_Click(object sender, RoutedEventArgs e)
{
new M_10_Closure().TryExample();
}
private void btnClosureTargetExample_Click(object sender, RoutedEventArgs e)
{
new M_11_Closure_Delegate_Target().TryExample();
}
}
}
Example 1 - Basic Delegate
People with C background can immediately find the similarity in functionality between delegates and function pointers. According to Microsoft documentation, a delegate is a type that represents references to methods with a particular parameter list and return type. The following is an example of the basic usage of delegates in C#.
using System.Text;
using System.Windows;
namespace Delegate_Func_Action_M.Examples
{
public delegate int PerformCalculation(int x, int y);
public class E_1_Basic_Delegate
{
private int Add(int i1, int i2)
{
return i1 + i2;
}
public void TryExample()
{
PerformCalculation calculate = Add;
StringBuilder sb = new StringBuilder("Result - ")
.Append(calculate(1, 2));
MessageBox.Show(sb.ToString(), this.GetType().Name);
}
}
}
- In the example, a delegate
delegate int PerformCalculation(int x, int y)
is defined. An instance of the PerformCalculation
can be used to reference any methods that takes two int
input parameters and returns an int
value; - The
Add
method is assigned to the delegate instance calculate
; - The
Add
method can be called through the delegate instance calculate
.
Click on the button, we can see that the Add
method is called successfully through the calculate
delegate instance.
Example 2 - Generic Delegate
A delegate is a type that represents references to methods with a particular parameter list and return type. It should not be a surprise that delegates support generic programming. The following is an example of a delegate with generic typed parameters.
using System.Text;
using System.Windows;
namespace Delegate_Func_Action_M.Examples
{
public delegate T GenericPerformCalculation<T>(T i1, T i2);
public class E_2_Generic_Delegate
{
private int AddInteger(int i1, int i2)
{
return i1 + i2;
}
private string AddString(string s1, string s2)
{
StringBuilder sb = new StringBuilder(s1)
.Append(" - ").Append(s2);
return sb.ToString();
}
public void TryExample()
{
GenericPerformCalculation<int> intDelegate = AddInteger;
GenericPerformCalculation<string> strDelegate = AddString;
StringBuilder sb = new StringBuilder("Call <string> delegate: ")
.Append(strDelegate("First", "Second")).Append("\n")
.Append("Call <int> delegate: ").Append(intDelegate(1, 2));
MessageBox.Show(sb.ToString(), this.GetType().Name);
}
}
}
- In the example, the delegate
GenericPerformCalculation<T>
is defined with the generic type T
; - The method
AddInteger
performs an integer addition; - The method
AddString
performs a string
addition (append); - Each method is assigned to an instance of the delegate
GenericPerformCalculation<T>
.
Click on the button, we can see that both delegate instances work fine. We can use generic types in delegates in C#.
Example 3 - Delegate Everywhere
This example is to show where we can define delegates in a C# program. According to Microsoft documentation, a delegate is a type
. So we can define delegates at the same level as classes, we can also define delegates inside a class. But we cannot define delegates inside a method.
using System.Text;
using System.Windows;
namespace Delegate_Func_Action_M.Examples
{
public delegate int TopLevelDelegate(int x, int y);
public class DelegateContainer
{
public delegate double InClassDelegate(double x, double y);
}
public class E_3_Delegate_Everywhere
{
private int IntAdd(int i, int j)
{
return i + j;
}
private double doubleAdd(double i, double j)
{
return i + j;
}
public void TryExample()
{
TopLevelDelegate intDelegate = IntAdd;
DelegateContainer.InClassDelegate doubleDelegate = doubleAdd;
StringBuilder sb = new StringBuilder("Result - ")
.Append(intDelegate(1, 2))
.Append(" - ")
.Append(doubleDelegate(1.1, 2.2));
MessageBox.Show(sb.ToString(), this.GetType().Name);
}
}
}
- In the example, the
TopLevelDelegate
is defined at the same level as the classes; - The
InClassDelegate
is defined in a class; - When
InClassDelegate
is used, it is used through the containing class name DelegateContainer.InClassDelegate
.
Click on the button, we can see that both delegate definitions work fine. It is not shown here, but if you try to define a delegate inside a method, you will see a compilation error.
Example 4 - Delegate Target
A delegate
is a type. A delegate
type has a property Target
. According to Microsoft documentation, the Target
property is used to "get the class instance on which the current delegate invokes the instance method". The following sentences are from the Microsoft documentation.
- An instance method is a method that is associated with an instance of a class;
- A
static
method is a method that is associated with the class itself; - If the delegate invokes one or more instance methods, this property returns the target of the last instance method in the invocation list.
using System.Text;
using System.Windows;
namespace Delegate_Func_Action_M.Examples
{
internal delegate void SetDelegate(string v);
internal static class AStaticClass
{
public static string i;
public static void setI(string v)
{
i = v;
}
}
internal class AnIntanceClass
{
public string i;
public void setI(string v)
{
i = v;
}
}
public class E_4_Delegate_Target
{
public void TryExample()
{
AnIntanceClass instance1 = new AnIntanceClass();
AnIntanceClass instance2 = new AnIntanceClass();
SetDelegate staticDelegate = AStaticClass.setI;
SetDelegate instance1Delegate = instance1.setI;
SetDelegate instance2Delegate = instance2.setI;
staticDelegate("Static string");
instance1Delegate("Instance 1 string");
instance2Delegate("Instance 2 string");
StringBuilder sb = new StringBuilder()
.Append("Static string is set to = ")
.Append(AStaticClass.i).Append("\n")
.Append("Instance 1 string is set to = ")
.Append(instance1.i).Append("\n")
.Append("Instance 2 string is set to = ")
.Append(instance2.i).Append("\n")
.Append("\n");
string staticDelegateTarget
= (staticDelegate.Target == null) ? "null" : "not null";
string instance1DelegateTarget
= (instance1Delegate.Target == null) ? "null" :
(instance1Delegate.Target == instance1) ? "instance1"
: "not instance1";
string instance2DelegateTarget
= (instance2Delegate.Target == null) ? "null" :
(instance2Delegate.Target == instance2) ? "instance2"
: "not instance2";
sb.Append("staticDelegate Target is ")
.Append(staticDelegateTarget).Append("\n")
.Append("instance1Delegate Target is ")
.Append(instance1DelegateTarget).Append("\n")
.Append("instance2Delegate Target is ")
.Append(instance2DelegateTarget).Append("\n");
MessageBox.Show(sb.ToString(), this.GetType().Name);
}
}
}
- In this example, a delegate
void SetDelegate(string v)
is defined; - A
static
method void setI(string v)
is created in the static
class AStaticClass
, which sets the value for the static variable i
; - An instance method
void setI(string v)
is created in the instance class AnIntanceClass
, which sets the value for the instance variable i
; - Three different delegate instances are declared. One references to the
static
method, the other two reference to the instance method on two different AnIntanceClass
instances.
Click on the button, we can see that the string
values on the static
class and the instances of the AnIntanceClass
class are set correctly. We can also see the following:
- The
Target
property of the delegate instance referencing the static
method is null
; - The
Target
property of the delegate instance referencing the instance method is the instance object through which the instance method is assigned to the delegate instance.
Example 5 - Func<T, TResult> Delegate
You may have seen the Func<T, TResult>
syntax in C# programs. You may have wondered what it is. According to Microsoft documentation, it is simply a pre-defined generic delegate in the System
namespace from the mscorlib.dll in the .NET framework. It encapsulates a method that has one parameter and returns a value of the type specified by the TResult
parameter.
using System;
using System.Text;
using System.Windows;
namespace Delegate_Func_Action_M.Examples
{
class E_5_Func_Example
{
private string decorateInt(int i)
{
StringBuilder sb = new StringBuilder(i.ToString())
.Append(" is decorated!");
return sb.ToString();
}
public void TryExample()
{
Func<int, string> aFunc = decorateInt;
StringBuilder sb = new StringBuilder(aFunc(100));
MessageBox.Show(sb.ToString(), this.GetType().Name);
}
}
}
- In the example, the
string decorateInt(int i)
method has a single int
input parameter and returns a string
value; - Since
Func<T, TResult>
is pre-defined by Microsoft, we can simply assign the decorateInt
method to an instance of the Func<int, string>
delegate.
Click on the button, we can see that the decorateInt
method is called successfully through the Func<int, string>
delegate.
Example 6 - Action<T> Delegate
Same as the Func<T, TResult>
delegate, the Action<span xmlns=""><T></span>
is also a pre-defined generic delegate in the System
namespace from the mscorlib.dll in the .NET framework. It encapsulates a method that has a single parameter and does not return a value.
using System;
using System.Text;
using System.Windows;
namespace Delegate_Func_Action_M.Examples
{
class E_6_Action_Example
{
private void DisplayMessage(string msg)
{
MessageBox.Show(msg, this.GetType().Name);
}
public void TryExample()
{
Action<string> action = DisplayMessage;
action("Message to display");
}
}
}
- In the example, the
void DisplayMessage(string msg)
has a single string
input parameter and does not return a value; - Since
Action<T>
is pre-defined by Microsoft, we can simply assign the DisplayMessage
method to an instance of the Action<string>
delegate.
Click on the button, we can see that the DisplayMessage
method is called successfully through the Action<string>
delegate.
Example 7 - Event Example
In C#, events are implemented on top of delegates. According to Microsoft documentation, Events enable a class or object to notify other classes or objects when something of interest occurs. The class that sends (or raises) the event is called the publisher and the classes that receive (or handle) the event are called subscribers.
- The publisher determines when an event is raised;
- The subscribers determine what action is taken in response to the event;
- An event can have multiple subscribers;
- A subscriber can handle multiple events from multiple publishers;
- Events that have no subscribers are never raised;
- Events are typically used to signal user actions such as button clicks or menu selections in graphical user interfaces;
- When an event has multiple subscribers, the event handlers are invoked synchronously when an event is raised.
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows;
namespace Delegate_Func_Action_M.Examples
{
public delegate void ADelegate(EventArgument arg);
public class EventArgument
{
public EventArgument()
{
ProcessMessage = new List<string>();
}
public List<string> ProcessMessage { get; private set; }
}
public class EventPublisher
{
public event ADelegate aEvent;
public EventArgument eventArgument { get; private set; }
public EventPublisher()
{
eventArgument = new EventArgument();
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void RaiseEvent()
{
if (aEvent != null)
{
aEvent(eventArgument);
}
}
}
public class EventSubscriber
{
private void Handler1(EventArgument arg)
{
arg.ProcessMessage.Add("Event handled by Handler1");
}
private void Handler2(EventArgument arg)
{
arg.ProcessMessage.Add("Event handled by Handler2");
}
public void SubscribleEvent(EventPublisher publisher)
{
publisher.aEvent += Handler1;
publisher.aEvent += Handler2;
}
}
public class E_7_Event_Example
{
public void TryExample()
{
EventPublisher publisher = new EventPublisher();
EventSubscriber subscriber = new EventSubscriber();
subscriber.SubscribleEvent(publisher);
publisher.RaiseEvent();
StringBuilder sb = new StringBuilder();
foreach (string s in publisher.eventArgument.ProcessMessage)
{
sb.Append(s).Append("\n");
}
MessageBox.Show(sb.ToString(), this.GetType().Name);
}
}
}
- In the example, a delegate
void ADelegate(EventArgument arg)
is defined; - In the
EventPublisher
class, an event aEvent
is declared based on the ADelegate
delegate. This event is fired in the RaiseEvent
method; - In the
EventSubscriber
class, the aEvent
can be subscribed in the SubscribleEvent
method. If the event is fired, both the Handler1
and Handler2
methods will be called to handle the event.
Click on the button, we can see that both methods registered as handlers of the event is called when the event fires.
Example 8 - Anonymous Method
According to Microsoft documentation, an anonymous function/method is an inline
statement or expression that can be used wherever a delegate type is expected. You can use it to initialize a named delegate or pass it instead of a named delegate type as a method parameter.
using System.Text;
using System.Windows;
namespace Delegate_Func_Action_M.Examples
{
public delegate int PerformAddition(int x, int y);
public class E_8_Anonymous_Method
{
public void TryExample()
{
PerformAddition addition = delegate(int i, int j)
{
return i + j;
};
StringBuilder sb = new StringBuilder("Result - ")
.Append(addition(1, 2));
MessageBox.Show(sb.ToString(), this.GetType().Name);
}
}
}
- In the example, a delegate
int PerformAddition(int x, int y)
is defined; - In the
TryExample
method, the instance of the PerformAddition
delegate is assigned an anonymous method created in-line using the delegate
syntax.
Click on the button, we can see that the anonymous method is called successfully through the delegate instance.
Example 9 - Lambda Expression
According to Microsoft documentation, a lambda expression is an anonymous function/method. But it uses a different syntax than the regular anonymous function/methods.
using System.Text;
using System.Windows;
namespace Delegate_Func_Action_M.Examples
{
public delegate int PerformMultiplication(int x, int y);
public class E_9_Lambda_Expression
{
public void TryExample()
{
PerformMultiplication multiply = (i, j) =>
{
return i * j;
};
StringBuilder sb = new StringBuilder("Result - ")
.Append(multiply(1, 2));
MessageBox.Show(sb.ToString(), this.GetType().Name);
}
}
}
- In the example, a delegate
int PerformMultiplication(int x, int y)
is defined; - In the
TryExample
method, the instance of the PerformMultiplication
delegate is assigned an anonymous function created with lambda expression syntax.
Click on the button, we can see that the lambda expression is called successfully though the delegate instance.
Example 10 - Closures in C#
If you have some functional programming experience, you should have already known about "closures". Since a delegate is a type in C#, and we can pass instances of delegates among different methods/functions, we will have "closures" in C#. What is unfortunate though is that I cannot find an official documentation from Microsoft on closures in C#. To introduce you to closures, I will use the document from Mozilla for JavaScript, since the idea is the same. Even from Mozilla, the one sentence definition for closures is still pretty fuzzy.
Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure 'remembers' the environment in which it was created.
If you do not understand this definition, it is fine, because I also feel that I can interpret it in a lot of different ways. To get a concrete idea on closures, we do better take a look at an example.
using System;
using System.Text;
using System.Windows;
namespace Delegate_Func_Action_M.Examples
{
internal class AnIntanceClass4Closure
{
private int Multiplier = 2;
public Func<int, int> GetAFuncDelegate()
{
int i = 0;
return j =>
{
i = i + Multiplier * j;
return i;
};
}
}
class M_10_Closure
{
public void TryExample()
{
AnIntanceClass4Closure instance = new AnIntanceClass4Closure();
Func<int, int> aFunc = instance.GetAFuncDelegate();
StringBuilder sb = new StringBuilder("i initial value = 0\n")
.Append("Add 1 * 2 = ").Append(aFunc(1)).Append("\n")
.Append("Add 2 * 2 = ").Append(aFunc(2)).Append("\n")
.Append("Add 3 * 2 = ").Append(aFunc(3)).Append("\n")
.Append("Add 4 * 2 = ").Append(aFunc(4)).Append("\n");
MessageBox.Show(sb.ToString(), this.GetType().Name);
}
}
}
- In the example, we have a class
AnIntanceClass4Closure
which has a method GetAFuncDelegate
; - The
GetAFuncDelegate
function returns an instance of the Func<int, int>
delegate. The delegate instance is created in-line inside the GetAFuncDelegate
by a lambda expression; - Because of the Lexical/static scoping rule, the lambda expression referenced the member variable
Multiplier
and the local variable i
declared in the GetAFuncDelegate
method.
It is a common knowledge that the local variable i
in the GetAFuncDelegate
method is created in the stack. When the method returns, the variable i
will be cleared out from the memory. But what will happen if we invoke the delegate instance returned by the GetAFuncDelegate
method? Will it be accessing a memory address that is already cleared?
Click on the button, we can see that the delegate instance is invoked just fine. If we check the result of the multiple invocations of the delegate instance, we can see that it "remembered" the local variable i
that should have already been cleared out from the memory in the sense that it is created in the stack and should be collected immediately after the method GetAFuncDelegate
returns.
- When we have a delegate instance created in-line by an anonymous function/lambda expression in a method, in the lexical scoping context, if the delegate instance references local variables declared in the method, the delegate instance can "remember" the local variables after the method that created it returns;
- This example shows that C# does have closures.
Example 11 - Closure Delegate Target
This example is intended to find some more information on how C# delegate instances remember the local variables. According to Microsoft documentation, the Target
property of a delegate instance is the class instance on which the current delegate invokes the instance method. But how can the class instance keep the information of local variables declared in a method after it returns?
using System;
using System.Text;
using System.Windows;
namespace Delegate_Func_Action_M.Examples
{
internal class AnIntanceClass4ClosureTarget
{
private int Multiplier = 2;
public Func<int, int> GetAFuncDelegate()
{
int i = 0;
return j =>
{
i = i + Multiplier * j;
return i;
};
}
}
public class M_11_Closure_Delegate_Target
{
public void TryExample()
{
AnIntanceClass4ClosureTarget instance = new AnIntanceClass4ClosureTarget();
Func<int, int> aFunc = instance.GetAFuncDelegate();
StringBuilder sb = new StringBuilder();
Object target = aFunc.Target;
sb.Append((target == null) ? "Target is null" :
"Target is not null").Append("\n")
.Append((target == instance) ? "Target is instance"
: "Target is not instance").Append("\n")
.Append("Target is ").Append(target.ToString());
MessageBox.Show(sb.ToString(), this.GetType().Name);
}
}
}
- In the example, the
AnIntanceClass4ClosureTarget
class is the same as the example 10; - In the
TryExample
method, we will try to take a look at the Target
property of the delegate instance returned by the GetAFuncDelegate
method.
Click on the button, we can find that the Target
property is not null
. It is not the instance of the AnIntanceClass4ClosureTarget
class. It is something like Delegate_Func_Action_M.Examples.AnIntanceClass4ClosureTarget+<>c__DisplayClass1
. This so called Delegate_Func_Action_M.Examples.AnIntanceClass4ClosureTarget+<>c__DisplayClass1
should have remembered the local variable i
. I have to be honest that I have not find an official document from Microsoft on how they let the delegate instances to remember the local variables. This example is just to verify some of my speculations.
Summary
- What is a delegate?
- A delegate loosely resembles a function pointer in C language;
- A delegate is a type that represents references to methods with a particular parameter list and return type in C#;
- In order to assign a method to a delegate instance, the types of the parameter list and the return type of the method need to exactly match the delegate definition;
- If the method is assigned to a delegate instance, it can be called/invoked through the delegate instance.
- Can we define delegates with generic types?
- Yes, we can define delegates with a generic parameter list and a generic return type.
- Where can we define delegates in a C# program?
- A delegate is a type in C#;
- A delegate can be defined at the same level as the classes;
- A delegate can be defined inside a class;
- A delegate cannot be defined inside a method.
- What is the
Target
property of a delegate instance?
- The
Target
property of a delegate instance is the class instance on which the current delegate invokes the instance method; - When a
static
method is assigned to a delegate instance, the Target
property is null
; - When an instance method is assigned to a delegate instance, the
Target
property is "generally" the class instance; - If the delegate instance invokes one or more instance methods, this property returns the target of the last instance method in the invocation list.
- What is
Func<T, TResult>
?
- The
Func<T, TResult>
is a pre-defined generic delegate type by Microsoft in the System
namespace; - It has exactly one input parameter and a return value;
- The
T
is the type of the input parameter; - The
TResult
is the type of the return value.
- What is
Action<T>
?
- The
Action<T>
is a pre-defined generic delegate type by Microsoft in the System
namespace; - It has exactly one input parameter and does not return a value;
- The
T
is the type of the input parameter.
- What is an event?
- An event is a special kind of instance of a delegate;
- Events enable a class or object to notify other classes or objects when something of interest occurs. The class that sends (or raises) the event is called the publisher and the classes that receive (or handle) the event are called subscribers;
- The publisher determines when an event is raised;
- The subscribers determine what action is taken in response to the event;
- An event can have multiple subscribers;
- A subscriber can handle multiple events from multiple publishers;
- Events that have no subscribers are never raised;
- Events are typically used to signal user actions such as button clicks or menu selections in graphical user interfaces;
- When an event has multiple subscribers, the event handlers are invoked synchronously when an event is raised.
- What is an anonymous method?
- An anonymous function/method is an
inline
statement or expression that can be used wherever a delegate type is expected; - You can use it to initialize a named delegate or pass it instead of a named delegate type as a method parameter;
- You define an anonymous method by the
delegate
syntax (see example 8).
- What is a lambda expression?
- A lambda expression is an anonymous method;
- A lambda expression style anonymous method is defined by
lambda
syntax (see example 9).
- What is a closure? Does C# have closures?
- Closures are well known subjects in functional programming. Unfortunately, I did not find an official documentation from Microsoft regarding to closures in C#;
- Borrow the definition from Mozilla for Javascript - "Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure 'remembers' the environment in which it was created". I hope this definition to be clear enough and you can understand it;
- In simpler terms, we can describe the closure behavior in the following sentences:
- We have a method that returns a delegate instance referencing an anonymous function/lambda expression defined in this method. For simplicity, let us call the method
M
and the anonymous function A
; - The method
M
declared some local variables; - The anonymous function
A
referenced the local variables in a lexical(static) scope; - We can obtain a delegate instance to the anonymous function
A
by calling the method M
. For simplicity, let us call the delegate instance D
; - We may think that the local variables declared in "M" would have been cleared/destroyed after
M
returns. But when we invoke the delegate instance D
, we will find that D
"remembers" the local variables declared in M
. - The behavior that anonymous functions/lambda expressions "remember" the local variables in a lexical(static) scope is called a closure behavior;
- If we call the method
M
multiple times, each of the delegate instances returned by M
remembers an independent set of local variables.
- Yes, C# has closures. You can take a look at the example 10 in this note.
- How can C# delegate instances "remember" the local variables of the method that creates them in-line?
- Unfortunately, I did not find an official documentation from Microsoft regarding to the implementation details on how C# delegate instances remember the local variables;
- In the example 11, by checking the
Target
property of the delegate instance, I find that the Target
instance is not the class instance that we use to get the delegate instance; - I have the impression that the "information" regarding to the local variables should be stored in the
Delegate_Func_Action_M.Examples.AnIntanceClass4ClosureTarget+<>c__DisplayClass1
. But I need Microsoft to confirm it; - I hope that Microsoft can release some details on how C# closures "remember" the local variables.
- Does a delegate instance have any impact on garbage collection?
- Yes, delegate instances have an impact on garbage collection;
- If we assign an instance method to a delegate instance, the delegate instance holds a reference to the class instance. As long as the delegate instance is still accessible, the class instance should not be garbage collected.
Points of Interest
- This is a note of loosely coupled 11 small examples on the general subject of delegates, events, anonymous methods, and closures;
- I could not find the official documentation on closures from Microsoft, so this note has some of my own speculations. I hope that Microsoft can release some official documentation on closures in C# on MSDN, so we do not need to rely on the un-official on-line blogs;
- I hope you like my posts and I hope this note can help you in one way or the other.
History
- 19th February, 2016: Initial revision