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

正常代码、Rx 和 YieldAwait 对比

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (10投票s)

2011 年 1 月 17 日

BSD

5分钟阅读

viewsIcon

33029

downloadIcon

261

对比使用普通事件处理程序、Rx(响应式扩展)和 YieldAwait 库时的代码风格。

上述应用程序的截图

ComparingYieldAwait/app_screenshot.gif

本文描述了什么?

在 C# 中,我们有几种方法可以等待事件,例如:

  1. 使用普通事件处理程序
  2. 使用lambda 函数作为事件处理程序
  3. 使用Rx(响应式扩展)
    我创建了一个名为 YieldAwait 的库,它允许您在需要等待事件的地方停止代码的执行,从而利用yield语句的功能。这意味着它为您提供了一种等待事件的新方式。所以我们现在有了另一种方法。
  4. 使用YieldAwait 库

因此,在本文中,我将分别比较使用这四种方法的代码风格。

什么是 YieldAwait 库?

YieldAwait 库正在 CodePlex 上开发。您可以在那里下载源代码和更多示例。它包含了使用此库等待事件的高级示例。

假设一个示例场景

例如,我们设想创建一个代码,通过等待Timer.Tick事件来闪烁Label UI 控件的背景颜色。闪烁的方式是:黑色 -> 灰色 -> 白色 -> 灰色 -> 黑色……

闪烁行为的最终目标应该如下面的图像所示

ComparingYieldAwait/blinking.gif

为了进行比较,我们使用与下面所示相同的默认窗体布局,其中包含一个Label UI 控件label1和一个Timer控件timer1

ComparingYieldAwait/design_form.png

ComparingYieldAwait/doc_outline.png

1. 使用普通事件处理程序

首先,这是通过在第 [C] 行使用普通事件处理程序来等待Timer.Tick事件,从而闪烁Label UI 控件的backcolor的代码。

 public partial class NormalEventHandlerForm : Form {

    public NormalEventHandlerForm() {
        InitializeComponent();
    }

    private int _Bright = 0; // [A]
    private int _Step = 10; // [B]

    private void timer1_Tick(object sender, EventArgs e) { // [C]

        _Bright += _Step; // [D]
        if (_Bright > 255) { // [E]
            _Step = -10;
            _Bright += _Step;
        } else if (_Bright < 0) {
            _Step = 10;
            _Bright += _Step;
        }

        label1.BackColor = Color.FromArgb(_Bright, _Bright, _Bright); // [F]
    }
} 

在这种情况下,您必须使用_Bright_Step两个字段变量来在每个Timer.Tick事件之间保留颜色闪烁信息。这段代码看起来非常典型,我们通常以这种方式编写事件驱动的程序。

2. 使用 lambda 函数作为事件处理程序

在本节中,lambda 函数被用作第 [C] 行的事件处理程序,这与第 1 节(事件处理程序)不同。

public partial class LambdaEventHandlerForm : Form {

    public LambdaEventHandlerForm() {
        InitializeComponent();
    }

    private void LambdaEventHandlerForm_Load(object sender, EventArgs e) {

        var bright = 0; // [A]
        var step = 10; // [B]

        timer1.Tick += (_sender, _e) => { // [C]

            bright += step; // [D]
            if (bright > 255) { // [E]
                step = -10;
                bright += step;
            } else if (bright < 0) {
                step = 10;
                bright += step;
            }

            label1.BackColor = Color.FromArgb(bright, bright, bright); // [F]
        };
    }
} 

在这种情况下,代码风格与第 1 节(事件处理程序)非常相似,但您无需定义字段变量。

3. 使用 Rx(响应式扩展)

我们也可以为这个示例使用 Rx(响应式扩展)的功能。Rx 在重复等待Timer.Tick事件的情况下也很有用。

public partial class RxForm : Form {

    public RxForm() {
        InitializeComponent();
    }

    private void RxForm_Load(object sender, EventArgs e) {

        var enum_bright = Enumerable // [G]
            .Range(0, 25 + 1)
            .Concat(Enumerable
                .Range(0, 25)
                .Reverse())
            .Select(_i => _i * 10)
            .ToArray();

        Observable
            .FromEvent<EventArgs>(timer1, "Tick") // [H]
            .Zip(enum_bright, (_e, _bright) => _bright) // [I]
            .Repeat()
            .Subscribe(_bright => {
                label1.BackColor = Color.FromArgb(_bright, _bright, _bright); // [F]
            });
    }
} 

代码风格与前面的章节截然不同,您将看到。如果您有使用 Rx 的经验,很容易理解代码是如何工作的。

在第 [G] 行,创建了一个名为enum_bright的数组,其中包含 [0, 10, ... 240, 250, 240, ... 10, 0] 的列表。使用 Rx 的Observable,在第 [H] 行捕获timer1.Tick事件,并在第 [I] 行将数组和事件组合起来。每次事件发生时,将调用第 [F] 行的代码,变量_bright将像 0, 10, ... 240, 250, 240, ... 10, 0 这样变化。因此,label1的背景颜色将从黑色闪烁到白色再回到黑色。

4. 使用 YieldAwait 库

本节使用了YieldAwait库。该库对于需要按顺序等待多个事件的此类情况非常有用。

public partial class YieldAwaitForm : Form {

    public YieldAwaitForm() {
        InitializeComponent();
    }

    IEnumerable<bool> TestFunc(EventWaiter waiter) {

        while (true) { // [J]

            for (var bright = 0; bright < 255; bright += 10) { // [K1]
                label1.BackColor = Color.FromArgb(bright, bright, bright); // [F1]
                yield return waiter.Wait(timer1, "Tick"); // [L1]
            }

            for (var bright = 255; bright > 0; bright -= 10) { // [K2]
                label1.BackColor = Color.FromArgb(bright, bright, bright); // [F2]
                yield return waiter.Wait(timer1, "Tick"); // [L2]
            }
        }
    }

    private void YieldAwaitForm_Load(object sender, EventArgs e) {
        new EventWaiter(TestFunc);
    }
} 

非常直接。您可以直接在代码中表达您想做的事情。您可以像您想的那样编写代码。即使您不知道如何有效地使用 Rx,也可以这样做。

代码有两个for循环。第 [K1] 行的循环用于从黑色到白色闪烁,第 [K2] 行的循环用于从白色到黑色闪烁。在 [K1]for循环中,变量bright将从0增加到250,例如010、...240250for循环每次将停止在第 [L1] 行,并在发生timer.Tick事件时再次运行。因此,label1的背景颜色将从黑色变为白色。 [K2]for循环以相同的方式工作,而 [J]while循环使闪烁永远持续。

如果您想了解如何使用这个库,CodePlex 上的项目页面有易于理解的解释和一个非常简单的示例代码。所以请也去看看那个页面。

5. 使用带 Yield 函数的普通事件处理程序

在看过第 3 节(Rx)和第 4 节(YieldAwait)的代码风格后,您可能会认为也可以使用带yield函数的普通事件处理程序,如下所示:

public partial class EventHandlerAndYieldForm : Form {

    public EventHandlerAndYieldForm() {
        InitializeComponent();
    }

    private IEnumerator<int> _BrightEnumerator;

    private void timer1_Tick(object sender, EventArgs e) { // [C]

        _BrightEnumerator.MoveNext();
        var bright = _BrightEnumerator.Current;
        label1.BackColor = Color.FromArgb(bright, bright, bright); // [F]
    }

    IEnumerator<int> _GetBrightEnumerator() {

        while (true) {

            for (var bright = 0; bright < 255; bright += 10) {
                yield return bright;
            }

            for (var bright = 255; bright > 0; bright -= 10) {
                yield return bright;
            }
        }
    }

    private void EventHandlerAndYieldForm_Load(object sender, EventArgs e) {
        _BrightEnumerator = _GetBrightEnumerator();
    }
} 

我认为它可读性较差,与第 4 节(YieldAwait)相比,但您也可以使用yield函数以这种方式编写代码。

关注点

首先,您可以用多种风格编写闪烁标签程序的代码。我不能说哪种好哪种不好。所有风格可能都有优点和缺点。

但是,我认为有些有趣的方面是:

  • 当您使用像第 3 节那样的 Rx 时,
    • 代码中很可能出现许多小的 lambda 函数,
    • 并且代码中使用的每个变量的作用域很可能很小。
    • 小作用域意味着复杂性低且易于阅读。
  • 当您使用像第 4 节那样的YieldAwait库时,
    • 您不使用任何 lambda 函数,
    • 并且代码看起来非常普通,因为它不使用任何技术性编码(如方法链或 lambda 函数……)而是使用yield语句。
    • 由于代码将完全按照所写内容逐行执行,因此可读性会更高。
    • 其用法和行为与使用 C# 5.0 的新关键字await非常相似。

您认为这些观点是什么?欢迎您的评论。

链接和更多信息

项目

技术解释

以前的论坛

历史

  • 2011/01/10
    • 撰写了第一版文章
  • 2011/01/18
    • 在第 4 节和第 5 节中添加了一些解释
    • 为示例应用程序添加了截图
  • 2011/01/19
    • 在“有趣之处”部分添加了一些解释
  • 2011/01/28
    • 添加了相关链接
© . All rights reserved.