委托、事件、Lambda 表达式及其他
C# 中委托、事件、匿名方法和闭包的松耦合小示例
引言
这是一篇关于 C# 中委托、事件、匿名方法和闭包这四个主题的 11 个松耦合小示例的笔记。这篇笔记旨在回答以下问题:
- 什么是委托?
- 我们可以定义泛型委托吗?
- 在 C# 程序中,我们可以在哪里定义委托?
- 委托实例的
Target
属性是什么? - 什么是
Func<T, TResult>
? - 什么是
Action<T>
? - 什么是事件?
- 什么是匿名方法?
- 什么是 Lambda 表达式?
- 什么是闭包?C# 有闭包吗?
- C# 委托实例如何“记住”创建它们的方法的局部变量?
- 委托实例对垃圾回收有影响吗?
这 11 个示例都非常简单。如果您没有时间仔细阅读它们,可以直接跳转到总结部分查看答案。如果您稍后有时间,可以回过头来再看看这些示例。
背景
附带的 Visual Studio 解决方案是一个简单的 WPF 应用程序。它使用 Visual Studio 2013 开发。
- WPF 应用程序的用户界面在 MainWindow.xaml 和 MainWindow.xaml.cs 文件中实现;
- Examples 文件夹中的每个类都代表 11 个示例中的一个。
<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>
MainWindow.xaml 中声明了 11 个按钮。Examples 文件夹中的每个类都实现了一个 TryExample()
方法。点击其中一个按钮将运行其中一个示例的 TryExample()
方法。
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();
}
}
}
示例 1 - 基本委托
熟悉 C 语言的人可以立即发现委托和函数指针在功能上的相似性。根据 Microsoft 文档,委托是一种类型,它表示对具有特定参数列表和返回类型的方法的引用。以下是 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);
}
}
}
- 在示例中,定义了一个委托
delegate int PerformCalculation(int x, int y)
。PerformCalculation
的一个实例可以引用任何接受两个int
输入参数并返回一个int
值的… Add
方法被赋给委托实例calculate
;- 可以通过委托实例
calculate
调用Add
方法。
点击按钮,可以看到 Add
方法已通过 calculate
委托实例成功调用。
示例 2 - 泛型委托
委托是一种类型,它表示对具有特定参数列表和返回类型的方法的引用。委托支持泛型编程并不奇怪。以下是带有泛型类型参数的委托的示例。
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);
}
}
}
- 在示例中,委托
GenericPerformCalculation<T>
使用泛型类型T
定义; AddInteger
方法执行整数加法;AddString
方法执行字符串相加(追加);- 每个方法都被赋给
GenericPerformCalculation<T>
委托的一个实例。
点击按钮,可以看到两个委托实例都能正常工作。我们可以在 C# 中使用委托的泛型类型。
示例 3 - 随处可见的委托
此示例旨在展示我们可以在 C# 程序中何处定义委托。根据 Microsoft 文档,委托是一种 type
。因此,我们可以将委托定义在与类相同的级别,也可以将委托定义在类内部。但是,我们不能在方法内部定义委托。
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);
}
}
}
- 在示例中,
TopLevelDelegate
定义在与类相同的级别; InClassDelegate
定义在类中;- 使用
InClassDelegate
时,通过包含类名DelegateContainer.InClassDelegate
来使用。
点击按钮,可以看到两个委托定义都工作正常。此处未显示,但如果您尝试在方法内定义委托,将会看到编译错误。
示例 4 - 委托 Target
delegate
是一种类型。delegate
类型有一个 Target
属性。根据 Microsoft 文档,Target
属性用于“获取当前委托调用实例方法的类实例”。以下句子摘自 Microsoft 文档。
- 实例方法是与类实例相关联的方法;
static
方法是与类本身相关联的方法;- 如果委托调用一个或多个实例方法,则此属性返回调用列表中的最后一个实例方法的目标。
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);
}
}
}
- 在此示例中,定义了一个委托
void SetDelegate(string v)
; - 在
static
类AStaticClass
中创建了一个static
方法void setI(string v)
,该方法设置静态变量i
的值; - 在实例类
AnIntanceClass
中创建了一个实例方法void setI(string v)
,该方法设置实例变量i
的值; - 声明了三个不同的委托实例。一个引用
static
方法,另外两个引用两个不同AnIntanceClass
实例上的实例方法。
点击按钮,可以看到 static
类和 AnIntanceClass
类实例上的 string
值已正确设置。我们还可以看到以下内容:
- 引用
static
方法的委托实例的Target
属性为null
; - 引用实例方法的委托实例的
Target
属性是用于将实例方法赋给委托实例的对象。
示例 5 - Func<T, TResult> 委托
您可能在 C# 程序中见过 Func<T, TResult>
语法。您可能想知道它是什么。根据 Microsoft 文档,它只是 .NET 框架 mscorlib.dll 中 System
命名空间中预定义的泛型委托。它封装了一个具有一个参数并返回 TResult
参数指定的类型的值的方法。
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);
}
}
}
- 在示例中,
string decorateInt(int i)
方法具有单个int
输入参数并返回string
值; - 由于
Func<T, TResult>
是由 Microsoft 预定义的,我们可以直接将decorateInt
方法赋给Func<int, string>
委托的一个实例。
点击按钮,可以看到 decorateInt
方法已通过 Func<int, string>
委托成功调用。
示例 6 - Action<T> 委托
与 Func<T, TResult>
委托一样,Action<span xmlns=""><T></span>
也是 .NET 框架 mscorlib.dll 中 System
命名空间中预定义的泛型委托。它封装了一个具有单个参数且不返回值的…
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");
}
}
}
- 在示例中,
void DisplayMessage(string msg)
具有单个string
输入参数且不返回值; - 由于
Action<T>
是由 Microsoft 预定义的,我们可以直接将DisplayMessage
方法赋给Action<string>
委托的一个实例。
点击按钮,可以看到 DisplayMessage
方法已通过 Action<string>
委托成功调用。
示例 7 - 事件示例
在 C# 中,事件是建立在委托之上的。根据 Microsoft 文档,事件使一个类或对象能在发生感兴趣的事情时通知其他类或对象。发送(或引发)事件的类称为发布者,接收(或处理)事件的类称为订阅者。
- 发布者决定何时引发事件;
- 订阅者决定如何响应事件;
- 一个事件可以有多个订阅者;
- 订阅者可以处理来自多个发布者的多个事件;
- 没有订阅者的事件永远不会被引发;
- 事件通常用于在图形用户界面中发出用户操作的信号,例如按钮点击或菜单选择;
- 当事件有多个订阅者时,事件处理程序在事件被引发时会同步调用。
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);
}
}
}
- 在示例中,定义了一个委托
void ADelegate(EventArgument arg)
; - 在
EventPublisher
类中,声明了一个基于ADelegate
委托的事件aEvent
。该事件在RaiseEvent
方法中触发; - 在
EventSubscriber
类中,可以在SubscribleEvent
方法中订阅aEvent
。如果事件被触发,Handler1
和Handler2
方法都会被调用来处理事件。
点击按钮,可以看到注册为事件处理程序的两个方法在事件触发时都被调用了。
示例 8 - 匿名方法
根据 Microsoft 文档,匿名函数/方法是可以在需要委托类型的任何地方使用的 内联
语句或表达式。您可以使用它来初始化命名委托,或将其作为方法参数传递,而不是命名委托类型。
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);
}
}
}
- 在示例中,定义了一个委托
int PerformAddition(int x, int y)
; - 在
TryExample
方法中,PerformAddition
委托的实例被赋给一个使用delegate
语法内联创建的匿名方法。
点击按钮,可以看到匿名方法已通过委托实例成功调用。
示例 9 - Lambda 表达式
根据 Microsoft 文档,Lambda 表达式是匿名函数/方法。但它使用的语法与常规匿名函数/方法不同。
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);
}
}
}
- 在示例中,定义了一个委托
int PerformMultiplication(int x, int y)
; - 在
TryExample
方法中,PerformMultiplication
委托的实例被赋给一个使用 Lambda 表达式语法创建的匿名函数。
点击按钮,可以看到 Lambda 表达式已通过委托实例成功调用。
示例 10 - C# 中的闭包
如果您有函数式编程经验,您应该已经知道“闭包”了。由于委托在 C# 中是一种类型,并且我们可以在不同的方法/函数之间传递委托实例,因此 C# 中会有“闭包”。令人遗憾的是,我找不到 Microsoft 关于 C# 闭包的官方文档。为了向您介绍闭包,我将使用 Mozilla 关于 JavaScript 的文档,因为概念是相同的。即使是从 Mozilla 来看,对闭包的一句话定义仍然相当模糊。
闭包是引用独立(自由)变量的函数。换句话说,闭包中定义的函数‘记住’了它被创建时的环境。
如果您不理解这个定义,没关系,因为我也觉得它的解释有很多种方式。为了对闭包有一个具体的认识,我们最好看一个例子。
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);
}
}
}
- 在示例中,我们有一个类
AnIntanceClass4Closure
,它有一个方法GetAFuncDelegate
; GetAFuncDelegate
函数返回一个Func<int, int>
委托的实例。该委托实例是在GetAFuncDelegate
内部使用 Lambda 表达式内联创建的;- 由于 词法/静态作用域 规则,Lambda 表达式引用了
GetAFuncDelegate
方法中声明的成员变量Multiplier
和局部变量i
。
普遍的认识是,GetAFuncDelegate
方法中的局部变量 i
是在 栈 中创建的。当方法返回时,变量 i
将从内存中清除。但是,如果我们调用 GetAFuncDelegate
方法返回的委托实例,会发生什么?它是否会访问一个已经被清空的内存地址?
点击按钮,我们可以看到委托实例被正常调用。如果我们检查委托实例的多次调用的结果,我们可以看到它“记住”了局部变量 i
,这个变量本应在方法 GetAFuncDelegate
返回后就被从内存中清除(从它在栈中创建并且应该在方法返回后立即被回收的角度来看)。
- 当我们有一个委托实例,它是由方法中的匿名函数/Lambda 表达式内联创建的,在词法作用域的上下文中,如果委托实例引用了在方法中声明的局部变量,那么在创建该方法的函数返回后,委托实例可以“记住”这些局部变量。
- 这个例子表明 C# 确实有闭包。
示例 11 - 闭包委托 Target
此示例旨在查找更多关于 C# 委托实例如何记住局部变量的信息。根据 Microsoft 文档,委托实例的 Target
属性是当前委托调用实例方法的类实例。但是,类实例如何能在方法返回后保留方法中声明的局部变量的信息呢?
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);
}
}
}
- 在示例中,
AnIntanceClass4ClosureTarget
类与示例 10 中的类相同; - 在
TryExample
方法中,我们将尝试查看GetAFuncDelegate
方法返回的委托实例的Target
属性。
点击按钮,我们可以发现 Target
属性不是 null
。它不是 AnIntanceClass4ClosureTarget
类的实例。它看起来像 Delegate_Func_Action_M.Examples.AnIntanceClass4ClosureTarget+<>c__DisplayClass1
。这个所谓的 Delegate_Func_Action_M.Examples.AnIntanceClass4ClosureTarget+<>c__DisplayClass1
应该记住了局部变量 i
。我必须诚实地说,我还没有找到 Microsoft 的官方文档说明他们是如何让委托实例记住局部变量的。此示例只是为了验证我的一些推测。
摘要
- 什么是委托?
- 委托在某种程度上类似于 C 语言中的函数指针;
- 在 C# 中,委托是一种类型,它表示对具有特定参数列表和返回类型的方法的引用;
- 为了将一个方法赋给一个委托实例,该方法的参数列表和返回类型的类型需要与委托定义完全匹配;
- 如果方法被赋给一个委托实例,可以通过委托实例来调用/执行该方法。
- 我们可以定义泛型委托吗?
- 是的,我们可以定义具有泛型参数列表和泛型返回类型的委托。
- 在 C# 程序中,我们可以在哪里定义委托?
- 委托是 C# 中的一种类型;
- 委托可以定义在与类相同的级别;
- 委托可以定义在类内部;
- 委托不能定义在方法内部。
- 委托实例的
Target
属性是什么?- 委托实例的
Target
属性是当前委托调用实例方法的类实例; - 当一个
static
方法被赋给一个委托实例时,Target
属性为null
; - 当一个实例方法被赋给一个委托实例时,
Target
属性“通常”是类实例; - 如果委托实例调用一个或多个实例方法,则此属性返回调用列表中的最后一个实例方法的目标。
- 委托实例的
- 什么是
Func<T, TResult>
?Func<T, TResult>
是 Microsoft 在System
命名空间中预定义的一个泛型委托类型;- 它正好有一个输入参数和一个返回值;
T
是输入参数的类型;TResult
是返回值的类型。
- 什么是
Action<T>
?Action<T>
是 Microsoft 在System
命名空间中预定义的一个泛型委托类型;- 它正好有一个输入参数且不返回值;
T
是输入参数的类型。
- 什么是事件?
- 事件是一种特殊的委托实例;
- 事件使一个类或对象能在发生感兴趣的事情时通知其他类或对象。发送(或引发)事件的类称为发布者,接收(或处理)事件的类称为订阅者;
- 发布者决定何时引发事件;
- 订阅者决定如何响应事件;
- 一个事件可以有多个订阅者;
- 订阅者可以处理来自多个发布者的多个事件;
- 没有订阅者的事件永远不会被引发;
- 事件通常用于在图形用户界面中发出用户操作的信号,例如按钮点击或菜单选择;
- 当事件有多个订阅者时,事件处理程序在事件被引发时会同步调用。
- 什么是匿名方法?
- 匿名函数/方法是可以在需要委托类型的任何地方使用的
内联
语句或表达式; - 您可以使用它来初始化命名委托,或将其作为方法参数传递,而不是命名委托类型;
- 您可以使用
delegate
语法定义匿名方法(参见示例 8)。
- 匿名函数/方法是可以在需要委托类型的任何地方使用的
- 什么是 Lambda 表达式?
- Lambda 表达式是匿名方法;
- Lambda 表达式风格的匿名方法使用
lambda
语法定义(参见示例 9)。
- 什么是闭包?C# 有闭包吗?
- 闭包是函数式编程中广为人知的主题。不幸的是,我找不到 Microsoft 关于 C# 闭包的官方文档;
- 借用 Mozilla 关于 Javascript 的定义——“闭包是引用独立(自由)变量的函数。换句话说,闭包中定义的函数‘记住’了它被创建时的环境”。我希望这个定义足够清晰,您能够理解;
- 简而言之,我们可以用以下句子来描述闭包的行为:
- 我们有一个方法,它返回一个委托实例,该实例引用在该方法中定义的匿名函数/Lambda 表达式。为简单起见,我们称该方法为
M
,称匿名函数为A
; - 方法
M
声明了一些局部变量; - 匿名函数
A
在 词法(静态)作用域 中引用了局部变量; - 通过调用方法
M
,我们可以获得匿名函数A
的委托实例。为简单起见,我们称该委托实例为D
; - 我们可能会认为“M”中声明的局部变量在
M
返回后会被清除/销毁。但是,当我们调用委托实例D
时,我们会发现D
“记住”了在M
中声明的局部变量。 - 匿名函数/Lambda 表达式“记住” 词法(静态)作用域 中的局部变量的行为被称为闭包行为;
- 如果我们多次调用方法
M
,M
返回的每个委托实例都会记住一组独立的局部变量。
- 我们有一个方法,它返回一个委托实例,该实例引用在该方法中定义的匿名函数/Lambda 表达式。为简单起见,我们称该方法为
- 是的,C# 有闭包。您可以在本笔记的示例 10 中查看。
- C# 委托实例如何“记住”创建它们的方法的局部变量?
- 不幸的是,我找不到 Microsoft 关于 C# 委托实例如何记住局部变量的实现细节的官方文档;
- 在示例 11 中,通过检查委托实例的
Target
属性,我发现Target
实例不是我们用来获取委托实例的类实例; - 我感觉关于局部变量的“信息”应该存储在
Delegate_Func_Action_M.Examples.AnIntanceClass4ClosureTarget+<>c__DisplayClass1
中。但我需要 Microsoft 来确认; - 我希望 Microsoft 能发布一些关于 C# 闭包如何“记住”局部变量的细节。
- 委托实例对垃圾回收有影响吗?
- 是的,委托实例会影响垃圾回收;
- 如果我们把一个实例方法赋给一个委托实例,委托实例会持有对类实例的引用。只要委托实例仍然可访问,类实例就不应该被垃圾回收。
关注点
- 这是一篇关于委托、事件、匿名方法和闭包这四个主题的 11 个松耦合小示例的笔记;
- 我找不到 Microsoft 关于闭包的官方文档,因此这篇笔记包含一些我自己的推测。我希望 Microsoft 能够在 MSDN 上发布一些关于 C# 闭包的官方文档,这样我们就无需依赖非官方的在线博客了;
- 希望您喜欢我的帖子,也希望这篇笔记能以某种方式帮助到您。
历史
- 2016年2月19日:初稿