65.9K
CodeProject 正在变化。 阅读更多。
Home

委托、事件、Lambda 表达式及其他

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (15投票s)

2016年2月26日

CPOL

16分钟阅读

viewsIcon

20226

downloadIcon

299

C# 中委托、事件、匿名方法和闭包的松耦合小示例

引言

这是一篇关于 C# 中委托、事件、匿名方法和闭包这四个主题的 11 个松耦合小示例的笔记。这篇笔记旨在回答以下问题:

  • 什么是委托?
  • 我们可以定义泛型委托吗?
  • 在 C# 程序中,我们可以在哪里定义委托?
  • 委托实例的 Target 属性是什么?
  • 什么是 Func<T, TResult>
  • 什么是 Action<T>
  • 什么是事件?
  • 什么是匿名方法?
  • 什么是 Lambda 表达式?
  • 什么是闭包?C# 有闭包吗?
  • C# 委托实例如何“记住”创建它们的方法的局部变量?
  • 委托实例对垃圾回收有影响吗?

这 11 个示例都非常简单。如果您没有时间仔细阅读它们,可以直接跳转到总结部分查看答案。如果您稍后有时间,可以回过头来再看看这些示例。

背景

附带的 Visual Studio 解决方案是一个简单的 WPF 应用程序。它使用 Visual Studio 2013 开发。

  • WPF 应用程序的用户界面在 MainWindow.xamlMainWindow.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)
  • staticAStaticClass 中创建了一个 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.dllSystem 命名空间中预定义的泛型委托。它封装了一个具有一个参数并返回 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.dllSystem 命名空间中预定义的泛型委托。它封装了一个具有单个参数且不返回值的…

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。如果事件被触发,Handler1Handler2 方法都会被调用来处理事件。

点击按钮,可以看到注册为事件处理程序的两个方法在事件触发时都被调用了。

示例 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 表达式“记住” 词法(静态)作用域 中的局部变量的行为被称为闭包行为;
      • 如果我们多次调用方法 MM 返回的每个委托实例都会记住一组独立的局部变量。
    • 是的,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日:初稿
© . All rights reserved.