C# Learning (3): Deep Understanding of Delegates, Anonymous Methods, and Lambda Expressions
Introduction The concept of lambda is built on delegates. Delegates implement type-safe callback methods. In .NET, callbacks are everywhere, so delegates are everywhere as well. The event model is built on the delegate mechanism, and a Lambda expression is essentially an anonymous delegate. This article will complete a journey through delegates, comprehensively covering delegates and their core topics, going through delegates, delegate chains, events, anonymous methods, and Lambda expressions one by one. Definition of Delegates To understand delegates, start with their definition. A delegate is typically declared as: public deleg
目录11 个章节
Introduction
The concept of lambda is built on delegates. Delegates implement type-safe callback methods. In .NET, callbacks are everywhere, so delegates are everywhere as well. The event model is built on the delegate mechanism, and a Lambda expression is essentially an anonymous delegate. This article will complete a journey through delegates, comprehensively covering delegates and their core topics, going through delegates, delegate chains, events, anonymous methods, and Lambda expressions one by one.
Definition of Delegates
To understand delegates, start with their definition. A delegate is typically declared as:
public delegate void CalculateDelegate(Int32 x, Int32 y);
The keyword delegate is used to declare a delegate type CalculateDelegate. An access modifier can be added to it. By default its return type is void, and it accepts two Int32 parameters x and y. However, a delegate is not equivalent to a method; it is a reference type, similar to a function pointer in C++. This will be explained later in the section on the nature of delegates.
The following example will show how to implement a calculator simulation program using delegates, and on that basis we will understand the definition, creation, and application of delegates:
using System;
namespace DelegateAndEvent
{
public class DeepInto
{
//声明一个委托
public delegate void CalculateDelegate(Int32 x, Int32 y);
//创建与委托关联的方法,二者具有相同的返回值类型和参数列表
public static void Add(Int32 x, Int32 y)
{
Console.WriteLine(x + y);
}
//定义委托类型变量
private static CalculateDelegate myDelegate;
public static void Run()
{
//进行委托绑定
myDelegate = new CalculateDelegate(Add);
//回调 Add 方法
myDelegate(100, 200);
}
}
}
In the example above, a CalculateDelegate delegate type is declared inside the class DelegateEx. It has exactly the same return type and parameter list as the associated method Add; otherwise a compile-time error will occur. Passing the method Add to the CalculateDelegate constructor means assigning the method Add to the CalculateDelegate delegate, and assigning that reference to the myDelegate variable, which means the myDeleage variable holds a reference pointing to the Add method, thereby implementing a callback to Add.
Thus, a delegate represents the signature of its callback method. You can pass methods as parameters and dynamically change method invocation based on the method that is passed in. As long as you provide a method with the same signature as the delegate, it can be bound to the delegate, for example:
public static void Subtract(Int32 x, Int32 y) {
Console.WriteLine(x - y);
}
Likewise, you can assign the method Subtract to the delegate and implement a method callback via parameter passing, for example:
public static void Main()
{
//进行委托绑定
myDelegate = new CalculateDelegate(Subtract);
myDelegate(100, 200);
}
Multicast Delegates and Delegate Chains
In the delegate implementation above, the Add method and Subtract can be bound to the same delegate type myDelegate. From this it is easy to imagine binding multiple methods to a single delegate variable, so that when one method is invoked, all methods bound to it can be executed in sequence. This technique is called multicast delegates. .NET provides quite concise syntax for creating delegate chains: the += and -= operators perform binding and unbinding respectively. Binding multiple methods to one delegate variable forms a delegate chain; when it is invoked, all bound callback methods will be called in sequence. For example:
myDelegate = new CalculateDelegate(Add);
myDelegate += new CalculateDelegate(Subtract);
myDelegate += new CalculateDelegate(Multiply);
myDelegate(100, 200);
The execution above will output the three results 300, -100, and 20000 in sequence on the console. This shows that a multicast delegate calls all bound methods in the order of the delegate chain. Likewise, the -= operator can unbind from the delegate chain, for example:
myDelegate -= new CalculateDelegate(Add);
myDelegate(100, 200);
The result will be that only -100 and 20000 are output, showing that the Add method was unbound via the -= operation. In fact, the += and -= operations call Deleagate.Combine and Deleagate.Remove respectively, as can be seen from the corresponding IL:
.method public hidebysig static void Main() cil managed
{
.entrypoint
// 代码大小 151 (0x97)
.maxstack 4
IL_0000: nop
IL_0001: ldnull
IL_0002: ldftn void
InsideDotNet.NewFeature.CSharp3.DelegateEx::Add(int32, int32)
//部分省略……
IL_0023: call class [mscorlib]System.Delegate [mscorlib]System.Delegate:: Combine(class [ms
corlib]System.Delegate, class [mscorlib]System.Delegate)
//部分省略……
IL_0043: call class [mscorlib]System.Delegate [mscorlib]System.Delegate:: Combine(class [ms
corlib]System.Delegate, class [mscorlib]System.Delegate)
//部分省略……
IL_0075: call class [mscorlib]System.Delegate [mscorlib]System.Delegate:: Remove(class [ms
corlib]System.Delegate, class [mscorlib]System.Delegate)
//部分省略……
IL_0095: nop
IL_0096: ret
} // end of method DelegateEx::Main
Therefore, the operations above are actually equivalent to:
public static void Main()
{
myDelegate = (CalculateDelegate)Delegate.Combine(new CalculateDelegate(Add),
new CalculateDelegate(Subtract), new CalculateDelegate(Multiply));
myDelegate(100, 200);
myDelegate = (CalculateDelegate)Delegate.Remove(myDelegate,
new CalculateDelegate(Add));
myDelegate(100, 200);
}
In addition, the return type of a multicast delegate is generally void. When the delegate type is a non-void type, the multicast delegate will return the execution result of the last invoked method, so it is not recommended in practical applications.
The Nature of Delegates
A delegate is still essentially a class. Such concise syntax is possible precisely because the CLR and the compiler complete a series of operations behind the scenes. Compiling the above CalculateDelegate delegate to IL will make this clearer, as shown in the figure:

So a delegate is still essentially a class that inherits from the System.MulticastDelegate class. That class maintains a linked list of delegates; when a multicast delegate is invoked, calls are made according to the order of delegates in the delegate list. It also includes a constructor that takes two parameters and 3 important methods: BeginInvoke, EndInvoke, and Invoke.
First, let's look at the constructor of CalculateDelegate, which includes two parameters: the first parameter represents an object reference pointing to the instance on which the current delegate invokes the callback function—in this example, it points to a DelegateEx object; the second parameter identifies the callback method, namely the Add method. Therefore, when creating an instance of a delegate type, it will be initialized with a reference pointing to an object and an integer that identifies the callback method; this is done by the compiler. So how is a callback method executed? Continuing to analyze the delegate invocation with IL code will reveal the clue (here we only analyze the case when the delegate is associated with the Add method):
.method public hidebysig static void Main() cil managed
{
.entrypoint
// 代码大小 37 (0x25)
.maxstack 8
IL_0000: nop
IL_0001: ldnull
IL_0002: ldftn void
InsideDotNet.NewFeature.CSharp3.DelegateEx::Add(int32, int32)
IL_0008: newobj instance void InsideDotNet.NewFeature.CSharp3.DelegateEx/ CalculateDelega
te::.ctor(object, native int)
IL_000d: stsfld class InsideDotNet.NewFeature.CSharp3.DelegateEx/ CalculateDelegate InsideD
otNet.NewFeature.CSharp3.DelegateEx::myDelegate
IL_0012: ldsfld class
InsideDotNet.NewFeature.CSharp3.DelegateEx/Calculate Delegate InsideDotNet.NewFeature.CSharp
3.DelegateEx::myDelegate
IL_0017: ldc.i4.s 100
IL_0019: ldc.i4 0xc8
IL_001e: callvirt instance void InsideDotNet.NewFeature.CSharp3.DelegateEx/ CalculateDelegat
e::Invoke(int32, int32)
IL_0023: nop
IL_0024: ret} // end of method DelegateEx::Main
As can be seen in the IL code, first the constructor of CalculateDelegate is called to create a myDelegate instance, then the callback method invocation is performed via CalculateDelegate::Invoke, showing that it is the Invoke method that actually performs the call. Therefore, you can also explicitly invoke it in code via Invoke, for example:
myDelegate.Invoke(100, 200
The execution process is the same as implicit invocation. Note that in .NET 1.0 the C# compiler did not allow explicit invocation; this restriction was corrected in later versions.
In addition, the Invoke method calls the callback method directly on the current thread. In an asynchronous programming environment, besides the Invoke method, BeginInvoke and EndInvoke methods are also generated to accomplish certain work. That is the role of the other two methods in the delegate class.
Delegates and Events
The .NET event model is built on the delegate mechanism. Only with a thorough understanding of delegates can you clearly analyze events. It can be said that events are an encapsulation of delegates. From the delegate examples we can see that the client can freely operate on the delegate, which to some extent undermines the object-oriented encapsulation mechanism; therefore events implement encapsulation of delegates. Below, we adapt the delegate example to complete the process of defining an event:
using System;
namespace DelegateAndEvent
{
public class Calculator
{
//定义一个 CalculateEventArgs,
//用于存放事件引发时向处理程序传递的状态信息
public class CalculateEventArgs : EventArgs
{
public readonly Int32 x, y;
public CalculateEventArgs(Int32 x, Int32 y)
{
this.x = x;
this.y = y;
}
}
//声明事件委托
public delegate void CalculateEventHandler(object sender, CalculateEventArgs e);
//定义事件成员,提供外部绑定
public event CalculateEventHandler MyCalculate;
//提供受保护的虚方法,可以由子类覆写来拒绝监视
protected virtual void OnCalculate(CalculateEventArgs e)
{
if (MyCalculate != null)
{
MyCalculate(this, e);
}
}
//进行计算,调用该方法表示有新的计算发生
public void Calculate(Int32 x, Int32 y)
{
CalculateEventArgs e = new CalculateEventArgs(x, y);
//通知所有的事件的注册者
OnCalculate(e);
}
}
}
In the example, the calculator simulation program has been briefly modified. From a comparison of the two you can appreciate the complete process of defining an event, which mainly includes:
- Define an inner event argument type used to hold the state information passed to the event handler when the event is raised. EventArgs is the base class of event data classes.
- Declare the event delegate, which mainly includes two parameters: one representing the event sender object, and one representing the event argument class object.
- Define the event member.
- Define the method responsible for notifying that the event has been raised. It is implemented as a protected virtual method so that it can be overridden in a derived class to refuse to monitor the event.
- Define a method that raises the event; for example, when Calculate is called, it indicates that a new calculation has occurred.
The complete program for an event is thus defined. Then, you still need to define an event trigger program to listen for the event:
//定义事件触发者
public class CalculatorManager
{
//定义消息通知方法
public void Add(object sender, Calculator.CalculateEventArgs e)
{
Console.WriteLine(e.x + e.y);
}
public void Substract(object sender, Calculator.CalculateEventArgs e)
{
Console.WriteLine(e.x - e.y);
}
}
Finally, implement an event handler:
public class Test_Calculator
{
public static void Run()
{
Calculator calculator = new Calculator();
//事件触发者
CalculatorManager cm = new CalculatorManager();
//事件绑定
calculator.MyCalculate += cm.Add;
calculator.Calculate(100, 200);
calculator.MyCalculate += cm.Substract;
calculator.Calculate(100, 200);
//事件注销
calculator.MyCalculate -= cm.Add;
calculator.Calculate(100, 200);
}
}
If you have some knowledge of design patterns, the implementation above is essentially an application of the Observer pattern through delegates. In .NET, the application of the Observer pattern strictly follows the relevant conventions. In Windows Form application development, a Button's Click corresponds to an event response, for example:
this.button1.Click += new System.EventHandler(this.button1_Clic
This is used to bind the button1_Click method to button1's Click event. When the button is pressed, the button1_Click method will be triggered:
private void button1_Click(object sender, EventArgs e) { }
Anonymous Methods
Anonymous methods are placed inline at the point where a delegate object is used, avoiding the creation of a delegate to associate a callback method—that is, the delegate invokes an anonymous method, directly associating the method code with the delegate instance, which has the benefit of being concise and intuitive in syntax. For example, binding a Click event with an anonymous method becomes very simple:
button1.Click += delegate
{
MessageBox.Show("Hello world.");
};
Therefore, it is necessary to implement the delegate example from the beginning of this section using anonymous methods, to understand its implementation process and underlying essence, for example:
class AnonymousMethodEx
{
delegate void CalculateDelegate(Int32 x, Int32 y);
public static void Run()
{
//匿名方法
CalculateDelegate mySubstractDelegate = delegate(Int32 x, Int32 y) { Console.WriteLine(x - y); };
CalculateDelegate myAddDelegate = delegate(Int32 x, Int32 y) { Console.WriteLine(x + y); };
mySubstractDelegate(100, 200);
}
}
In fact, anonymous methods and delegates are equivalent at the IL level. The compiler adds two static members and static methods for the anonymous method, as shown in the figure:

The two static members and static methods generated by the compiler assist in implementing a syntactic structure the same as that of delegate invocation. This is the underlying truth of anonymous methods.
Lambda Expressions
Lambda expressions are a core concept of Functional Programming. C# 3.0 has also introduced Lambda expressions to achieve more concise syntax and to provide the syntactic foundation for LINQ, which will be covered in Chapter 12 of this book. Applying Lambda expressions once again to implement the same process, the code is:
using System;
namespace Lambda
{
public class LambdaExpressionEx
{
delegate void CalculateDelegate(Int32 x, Int32 y);
public static void Run()
{
CalculateDelegate myDelegate = (x, y) => Console.WriteLine(x - y);
myDelegate(100, 200);
}
}
}
Analyzing the IL code of the Lambda expression, we can see that the compiler likewise automatically generates the corresponding static members and static methods. A Lambda expression is still essentially a delegate. It is the compiler that brings all this convenience; we will not further analyze the details at the IL level here.
Rules
- Delegates implement an object-oriented, type-safe method callback mechanism.
- Using Delegate as the suffix for delegate types and EventHandle as the suffix for event delegates is the standard naming convention.
- The return value of a multicast delegate is generally void; returning a non-void type from a multicast delegate is not recommended.
- Anonymous methods and Lambda expressions provide more concise syntax, and these new features are mainly implemented by the compiler, with no essential change at the IL level.
- .NET events are an application of the Observer pattern through delegates, implemented based on .NET conventions, reflecting better coupling and flexibility.
Conclusion
From the layer-by-layer evolution from delegates to Lambda expressions, we can see the continuous evolution and development of .NET at the language level. It is precisely these advances that have driven technology forward, making .NET more compatible and optimized as a language. For technical developers, this kind of progress is exactly what we hope for. However, fundamentally understanding and knowing delegates is the foundation of everything; otherwise, evolution in syntax will only make understanding more confusing. The discussion in this section is intended to provide a foundation for understanding this material and to establish a relatively comprehensive concept.
Afterword
Note: This article is a reprint and reformatting of a tutorial by Microsoft C# MVP Wang Tao. This article gives a good introduction to delegates, anonymous methods, and lambda expressions, and also serves as a summary of the previous two notes.
Welcome to Discuss
Please leave a message in the WeChat official account backend for discussion. I will reply to every message~
- WeChat Official Account: 画星星高手
- Coding livestream: https://live.bilibili.com/11883038
- Zhihu: https://www.zhihu.com/people/dealiaxy
- Jianshu: https://www.jianshu.com/u/965b95853b9f
_EOF_
C# Learning (3): Deep Understanding of Delegates, Anonymous Methods, and Lambda Expressions
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
C#中的Attribute与Python-TypeScript的装饰器是同个东西吗
前言 最近成功把「前端带师」带入C#的坑~~(实际是前端带师开始从cocos转unity游戏开发了)~~ 某天,「前端带师」看到这段代码后问了个问题:[这个是装饰器]? [HttpGet] public Response Get() { return ... } 我第一反应觉得不是,这玩意在C#中叫“特性”(英文名Attribute,下文统称为特性),在Java中叫注解,虽然写法和Pyth
2022-06-26 · C# · 约 1 分钟
C#爬虫:CatSpider#-开发笔记
前言 (PS:我这里用了#号代替了Sharp这个单词) CatSpider是毕设里的数据采集模块,本来爬虫类的应用肯定使用python来开发嘛,不过用request_html做解析的时候,python的动态类型真的把我恶心到了,而且感觉这个库也不是很成熟,html5lib也不好用,也没心思去深入了,之前看到有大佬用.net core平台做爬虫,于是我也来试试,没想到效果贼好,特别是配合LinqPa
2022-05-27 · C# · 约 1 分钟
C#集成ViewFaceCore人脸检测识别库
人脸检测与识别现在已经很成熟了,C# 上有 ViewFaceCore 这个很方便的库,但这种涉及到 native 调用的库,一般会有一些坑,本文记录一下开发和部署的过程。
2023-09-20 · C# · 约 1 分钟
C#实现解析Markdown文档并处理图片链接
前言 又是好久没写博客了 其实也不是没写,是最近在「做一个博客」,从2月21日开始,大概一个多星期的时间,疯狂刷进度,边写代码边写了一整系列的博客开发笔记,目前为止已经写了16篇了,然后上3月之后工作有点忙~~加上有点沉迷原神~~,然后进度就暂时慢下来了 事实证明做什么事情一旦停下来就会产生惰性,于是停了这么久,我终于下定决心继续更新! 这次开发的博客用的是C#语言,搭配.netcore技术栈;前
2022-03-19 · C# · 约 1 分钟
讨论
共 0 条加载评论中…