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

C# 委托、匿名函数和 Lambda 表达式基础入门教程

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (63投票s)

2012 年 12 月 6 日

CPOL

6分钟阅读

viewsIcon

92156

downloadIcon

895

本文讨论委托以及如何使用函数、匿名函数和 Lambda 表达式编写委托处理程序。

引言

在本文中,您将了解委托以及如何使用函数、匿名函数和 Lambda 表达式编写委托处理程序。本文从初学者的角度撰写,包含与相关主题非常入门级的内容。

背景

很多时候,当应用程序的某一部分/对象发生更改时,我们需要应用程序的其他部分/对象来执行某些操作。实现这一目标的一种方法是让一个对象持续轮询另一个对象,并在另一个对象的状态更改时做出响应。然而,这种方法并不优雅,它会消耗大量的 CPU 资源。此外,两次连续轮询请求之间会存在一个时间间隔,如果在此期间另一个对象的状态发生了变化,那么我们将稍后才知道,即信息延迟。

现在,解决这个问题的方法称为观察者模式。在这里,想要监听状态更改的对象可以订阅其状态将要更改的对象。如果我们需要将这种机制应用到 C# 应用程序中,那么我们需要同时考虑该模式的两个方面。一是将状态更改从一个对象发送到监听器(所有监听器),二是接收对象的当前状态。

委托是 C# 中实现第一部分的方法,即跟踪所有监听器并在状态发生更改时向它们发送通知。匿名方法和Lambda表达式则用于处理来自委托的响应。

Using the Code

让我们尝试实现一个名为 BatteryLevel 的简单类应用程序来理解这些主题。该类将跟踪设备的当前电池电量。其他类可以订阅该类提供的委托来监听电池电量变化。让我们来看看这个类在没有引入委托之前的情况。

class BatteryLevel
{
    int currentBatteryLevel;

    public int CurrentBatteryLevel
    {
        get
        {
            return currentBatteryLevel;
        }
        set
        {
            // Some process will call this setter to set the battery level
            currentBatteryLevel = value;
        }
    }
}

要实现委托及其处理程序,主要涉及四个步骤:

  1. 声明委托
  2. 创建委托
  3. 将委托与处理函数关联
  4. 调用委托

现在,在向此类添加委托之前,让我们先弄清楚一些基本概念。委托是我们可以定义的类型。定义将声明委托并指定该委托可以保存和调用的方法签名。典型的委托声明如下所示:

[Access Modifier] delegate [Return type] [DELEGATE_NAME]([Function arguments]);

因此,为了声明我们类的委托,假设我们希望处理函数返回void并接受一个表示新电池电量的整数值。声明将如下所示:

// This is the delegate type, this will specify the name of the delegate
// and the function signature of the function that this delegate can call
public delegate void BatteryLevelBroadcaster(int batteryLevel);

委托可以保存对多个方法的引用,当调用委托时,它将按顺序逐个调用所有方法。C++ 程序员可以将委托视为类型安全函数指针的列表。

现在我们已经了解了我们四步过程的第一部分。现在,如果我们继续进行第二部分,即创建委托,我们只需要创建委托类型并为其分配一些处理函数。如果委托对象没有关联的处理程序,它将为null。所以,让我们先创建一个委托类型:

// This is the instance of our delegate type which will hold
// list of functions to call too
public BatteryLevelBroadcaster batteryLevelBroadcaster;

现在我们已经了解了我们四步过程的第二部分。现在,如果我们继续进行第四部分,即调用委托。调用与此委托关联的所有函数的典型方法是像调用函数一样调用委托。

//let's check whether someone has registered for listening or not
if (batteryLevelBroadcaster != null)
{
    // Let's call all the functions with new battery level value
    batteryLevelBroadcaster(currentBatteryLevel);
}

因此,现在带有委托的BatteryLevel类定义将如下所示:

class BatteryLevel
{
    int currentBatteryLevel;

    // This is the delegate type, this will specify the name of the delegate
    // and the function signature of the function that this delegate can call
    public delegate void BatteryLevelBroadcaster(int batteryLevel);

    // This is the instance of our delegate type which will hold
    // list of functions to call too
    public BatteryLevelBroadcaster batteryLevelBroadcaster;

    public int CurrentBatteryLevel
    {
        get
        {
            return currentBatteryLevel;
        }
        set
        {
            // Some process will call this setter to set the battery level
            currentBatteryLevel = value;

            //let's check whether someone has registered for listening or not
            if (batteryLevelBroadcaster != null)
            {
                // Let's call all the functions with new battery level value
                batteryLevelBroadcaster(currentBatteryLevel);
            }
        }
    }
}

现在我们已经看到了实现委托的调用方对象部分,即声明、创建和调用委托。现在让我们看看如何编写处理部分,即如何将函数与委托关联以及处理函数的体。

要将函数与委托关联,我们只需在构造函数中传递函数名来创建委托类型。然后,我们可以将此委托类型分配给类公开的委托。

static void Main(string[] args)
{
    // let's say we have a module that notifies the user with low battery
    // here, we are emulating it.
    BatteryLevel batteryLevel = new BatteryLevel();

    // Let's subscribe to the battery level delegate to know the current level
    batteryLevel.batteryLevelBroadcaster += 
           new BatteryLevel.BatteryLevelBroadcaster(BatteryLevelIndicator);
    
    // Now let us simulate the hardware function that will set the battery level
    batteryLevel.CurrentBatteryLevel = 35;
    batteryLevel.CurrentBatteryLevel = 30;
    batteryLevel.CurrentBatteryLevel = 25;
    batteryLevel.CurrentBatteryLevel = 20;
    batteryLevel.CurrentBatteryLevel = 15;
    // Once the above code is run, for each updation, we should get the
    // message written in our delegate function
}

// This is the function that will let the user know the current battery level
public static void BatteryLevelIndicator(int newValue)
{
    Console.WriteLine("New battery level is: {0}", newValue);
}

上面的代码只是创建了一个函数,然后将该函数作为处理函数附加到类公开的委托。函数签名必须与委托签名匹配,否则代码将无法编译。

我们还设置了电池电量值来测试应用程序。每当我们调用 set 时,我们的处理函数都将被调用,它将在控制台上打印消息。

注意:我们使用了+=来分配委托,因为我们需要我们的函数添加到已添加到此委托的函数列表中。如果我们仅使用=符号,它将清除现有函数的列表,并将此函数作为与委托关联的唯一函数。

因此,我们已经看到了我们四步过程的第三部分,即如何将处理函数与委托关联。当我们运行应用程序时,我们可以看到通过委托调用处理函数。

匿名函数

匿名函数是没有函数名称的函数。C# 提供了创建无名函数的能力。但是,如果一个函数没有名字,它将如何被调用呢?实际上,在代码中没有办法调用匿名函数,因为函数的名称是调用该函数的句柄。

但是我们刚刚看到,委托会保留所有处理函数的句柄/引用。委托在调用它们时不需要函数名,因此匿名函数仅与委托一起使用才有意义。

匿名函数的作用是,它将处理程序挂接到委托的代码和处理程序函数的体合并到了一起,而不是分开编写。因此,如果我想使用匿名函数来实现上述功能,我只需像这样编写:

static void Main(string[] args)
{
    // let's say we have a module that notifies the user with low battery
    // here, we are emulating it.
    BatteryLevel batteryLevel = new BatteryLevel();

    // Here, we have another module that is listening to the
    // battery level change, but this time let's implement this using the
    // anonymous function
    batteryLevel.batteryLevelBroadcaster += delegate(int newValue) 
     { Console.WriteLine("(ANOMYMOUS)New battery level is: {0}", newValue);};

    // Now let us simulate the hardware function that will set the battery level
    batteryLevel.CurrentBatteryLevel = 35;
    batteryLevel.CurrentBatteryLevel = 30;
    batteryLevel.CurrentBatteryLevel = 25;
    batteryLevel.CurrentBatteryLevel = 20;
    batteryLevel.CurrentBatteryLevel = 15;
    // Once the above code is run, for each updation, we should get the
    // message written in our delegate function
}

这将产生与早期版本相同的结果,但它不需要程序员编写单独的函数。这在处理函数的代码很少时非常有用,并且使用此方法可以使代码比早期版本更简洁。

Lambda 表达式

较新版本的 C# 引入了 Lambda 表达式,这在很大程度上使匿名函数变得过时。就本文而言,让我们理解 Lambda 表达式是创建本地匿名函数的另一种方式,可以与委托一起使用。

注意:Lambda 表达式相比匿名函数还有其他优点,例如它可以直接分配给表达式树,但本文将保持简单,不会讨论这些。

Lambda 表达式可以看作是匿名方法的替代方案,具有以下语法:

[METHOD PARAMETERS] => [METHOD BODY]

因此,如果我们使用 Lambda 表达式编写类似的代码,我们可以这样做:

static void Main(string[] args)
{
    // let's say we have a module that notifies the user with low battery
    // here, we are emulating it.
    BatteryLevel batteryLevel = new BatteryLevel();

    // And finally, there is the third approach where we can use Lambda expression
    // to subscribe to the delegates
    batteryLevel.batteryLevelBroadcaster += 
    (newValue) => Console.WriteLine("(LAMBDA)New battery level is: {0}", newValue);
    // ABOVE LINE WILL WORK ONLY IN VS2008 and above

    // Now let us simulate the hardware function that will set the battery level
    batteryLevel.CurrentBatteryLevel = 35;
    batteryLevel.CurrentBatteryLevel = 30;
    batteryLevel.CurrentBatteryLevel = 25;
    batteryLevel.CurrentBatteryLevel = 20;
    batteryLevel.CurrentBatteryLevel = 15;
    // Once the above code is run, for each updation, we should get the
    // message written in our delegate function
}

注意:在上面的代码中,我们也可以选择在表达式中指定类型参数,而不是使用隐式的类型参数。

batteryLevel.batteryLevelBroadcaster += 
    (int newValue) => Console.WriteLine("(LAMBDA)New battery level is: {0}", newValue);

上述代码的结果与早期版本相同,但代码比早期版本(带有处理函数和匿名方法的版本)更简洁。

看点

在这篇短文中,我们试图探讨什么是委托,以及如何实现自定义委托及其处理程序。委托已经存在相当长一段时间了,这篇文章似乎有些晚或过时了。我写这篇文章是因为 Codeproject 问答部分有人似乎对这个话题感到困惑。本文是从初学者的角度撰写的。我希望这能有所启发。

历史

  • 2012 年 12 月 6 日:初版
© . All rights reserved.