Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Delegate Event Lambda Expressions and Miscellaneous

4.97/5 (15 votes)
26 Feb 2016CPOL16 min read 20K   276  
Loosely coupled small examples of delegates, events, anonymous methods, and closures in C#
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.

Image 1

  • 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.
XML
<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.

C#
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();
        }
    }
}

Image 2

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#.

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.

Image 3

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.

C#
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>.

Image 4

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.

C#
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.

Image 5

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.
C#
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;

        // A static set method
        public static void setI(string v)
        {
            i = v;
        }
    }

    internal class AnIntanceClass
    {
        public string i;

        // An instance set method
        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;

            // Use the delegate to set the strings
            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");

            // Check out the delegate target
            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.

Image 6

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.

C#
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.

Image 7

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.

C#
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.

Image 8

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.
C#
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.

Image 9

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.

C#
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.

Image 10

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.

C#
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.

Image 11

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.

C#
using System;
using System.Text;
using System.Windows;

namespace Delegate_Func_Action_M.Examples
{
    internal class AnIntanceClass4Closure
    {
        private int Multiplier = 2;

        // This method returns a Func delegate
        public Func<int, int> GetAFuncDelegate()
        {
            // variable declared in the method
            int i = 0;

            return j =>
            {
                i = i + Multiplier * j;
                return i;
            };
        }
    }

    class M_10_Closure
    {
        public void TryExample()
        {
            // Get the delegate
            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?

Image 12

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?

C#
using System;
using System.Text;
using System.Windows;

namespace Delegate_Func_Action_M.Examples
{
    internal class AnIntanceClass4ClosureTarget
    {
        private int Multiplier = 2;

        // This method returns a Func delegate
        public Func<int, int> GetAFuncDelegate()
        {
            // variable declared in the method
            int i = 0;

            return j =>
            {
                i = i + Multiplier * j;
                return i;
            };
        }
    }

    public class M_11_Closure_Delegate_Target
    {
        public void TryExample()
        {
            // Get the delegate
            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.

Image 13

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)