没有 lambda 表达式的 C#

C# without lambda expression

我有 lambda 表达式:

cake.PropertyChanged += (mSender, eventArgs)
               => listOfFinishedOrders.Items.Insert(count,cake.NameOfCake + eventArgs.PropertyName);

不用lambda表达式怎么写?

我不确定在没有 lambda 的情况下编写此代码是个好主意,因为它是一个单行代码(lambda 的理想用例)。但是,如果 lambda 主体更长,您可以这样做:

class MyClass
{
    public MyClass() { cake.PropertyChanged += HandleCakeChanged; }
    void HandleCakeChanged(object sender, EventArgs args)
    {
        // do stuff
    }
}

如果您不想在那里使用 lambda,则必须使用单独的方法。到那时,您将 运行 遇到这样的问题,即您使用的变量可能会或可能不会被您的方法访问,因为它们是在本地声明的。如果 listOfFinishedOrders and/or count 是这种情况,您将必须将它们存储在方法也可以访问它们的地方(通常作为 class 中的私有成员):

public class SomeClass
{
    // private members for those local variables
    private int count;
    private ListBox listOfFinishedOrders;

    public void SomeMethod ()
    {
        // Some code that has `cake`, `listOfFinishedOrders` and `count`
        // as local variables. This is also where the lambda was before.

        // store the values in the type
        this.count = count;
        this.listOfFinishedOrders = listOfFinishedOrders;

        cake.PropertyChanged += CakeChanged;
    }

    private void CakeChanged (object sender, EventArgs e)
    {
        Cake cake = sender as Cake; // sender is the original cake

        // `listOfFinishedOrders` and `count` are instance members
        listOfFinishedOrders.Items.Insert(count, cake.NameOfCake + e.PropertyName);
    }
}

当然,此解决方案假定 countlistOfFinishedOrders 只有一个值,但情况可能并非如此(特别是如果您有多个蛋糕,您也想附加事件—然而开箱即用,因为蛋糕是由事件发送者决定的)。

根据您的实际情况,可能有更简洁的方法来解决它,介于 Peter 的解决方案(非常接近地模仿 lambda 的功能)和我的解决方案(假设一个简单的环境)之间。

在这种 lambda 表达式(一种非常常见的表达式)的用法中,真正发生的只是代码声明了一个匿名方法。 C# 编译器通过创建一个真正的方法来处理这个问题,尽管该方法在源代码中没有可见的名称(因此 "anonymous")。要摆脱 lambda 语法(再次强调,为什么?),"all" 您需要做的是将匿名方法主体移动到命名方法。

在最简单的情况下,新命名的方法可以放在同一个class中。这在没有捕获变量时有效;该方法中的所有代码仅使用在 class 的任何普通方法中可见的内容。如果是这样,那么您可以这样做:

void M()
{
    // some other stuff

    cake.PropertyChanged += N;

    // some other stuff
}

void N(object mSender, EventArgs eventArgs)
{
    listOfFinishedOrders.Items.Insert(count, cake.NameOfCake + eventArgs.PropertyName);
}

然而,在许多情况下,一个或多个变量是 "captured" 通过匿名方法。在这种情况下,C# 编译器将生成一个 class 来与(并包含)匿名方法一起使用。在此 class 中还将转到字段以支持 "captured".

的变量

确实,在您的示例中,匿名方法主体中使用的一个或多个变量似乎很可能是捕获变量。

您可以自己模拟 C# 编译器的行为:

class Captured1
{
    private readonly int count;
    private readonly Cake cake;
    private readonly ListBox listOfFinishedOrders;

    public Captured1(int count, Cake cake, ListBox listOfFinishedOrders)
    {
        this.count = count;
        this.cake = cake;
        this.listOfFinishedOrders = listOfFinishedOrders;
    }

    public void N(object mSender, EventArgs eventArgs)
    {
        listOfFinishedOrders.Items.Insert(count,
            cake.NameOfCake + eventArgs.PropertyName);
    }
}

void M()
{
    // some other stuff

    Captured1 c = new Captured1(count, cake, listOfFinishedOrders);

    cake.PropertyChanged += c.N;

    // some other stuff
}

注:上面我也有一些随意,猜测了CakeListBox等类型。你没有在你的问题中提供足够的上下文让任何人确定这些变量是什么,所以我只是尽力而为。如果我猜错了,我假设你可以在自己的代码中提供正确的类型。

现在,在上面,请注意所有字段都是只读的。创建实例时,它们会被复制到新的 class。但这可能是也可能不是您真正想要的。特别是,在真实的 lambda 场景中,即使在创建了 lambda 委托之后,lambda 方法主体中的代码也会观察到捕获变量的变化。

没有 a good, minimal, complete code example 我不愿意推测这个特定的例子。它可能根本不适用于您的场景。

但总的来说:您可以通过使字段 public 而不是 readonly 来解决这种情况,以便它可以在实例所在的上下文中用作实际变量被建造。这将允许对变量的更改在 lambda 方法体内可见,并允许对在 lambda 方法体内发生的变量的更改对调用 lambda 委托后执行的代码可见。

我会注意到您的事件是 cake 对象本身的成员。这强烈暗示(虽然再次缺乏好的代码示例并不能保证)mSender 对象实际上总是 cake 对象。如果是这样,您可以通过不包括 cake 字段并将 N() 方法更改为如下所示来简化 Captured1 class:

public void N(object mSender, EventArgs eventArgs)
{
    listOfFinishedOrders.Items.Insert(count,
        ((Cake)mSender).NameOfCake + eventArgs.PropertyName);
}


以上所有内容都是编译器实际操作的简化版本。该场景有多种变体,要在此处列举所有可能性,本质上将涉及重新实现编译器行为,这对于 Whosebug 来说是一项过于广泛的任务。但我希望以上内容能让您对基本方法有足够的了解:

  • 将匿名方法主体移动到命名方法中。如果能编译无误,那么……太好了,大功告成!
  • 如果这不起作用,则指定方法中的一个或多个变量不再有效(已将它们移出它们定义的范围)。在这种情况下,您将需要创建一个助手 class(如上面的 Captured1),您可以在其中存储变量值。命名方法也将进入那个新的 class。创建 class 的一个实例并使用命名方法订阅事件(或其他任何东西......lambda 用于许多其他方式,并且这种基本技术适用于任何这些场景)。