C#事件处理与YieldAwait库的应用

C#中,有多种方式来等待事件的发生,例如使用常规的事件处理器、Lambda函数作为事件处理器、Rx(响应式扩展),以及YieldAwait库。本文将介绍YieldAwait库,并比较使用这四种方式时代码风格的差异。

YieldAwait库简介

YieldAwait库是一个允许在代码中的任何地方暂停执行以等待事件的库,它利用了yield语句的功能。这意味着它为提供了一种新的方式来等待事件。因此,现在有了另一种等待事件的方式。

示例场景

作为示例,考虑一个场景,即创建一个代码,通过等待Timer.Tick事件来使Label UI控件的背景色闪烁。闪烁的方式是黑色 -> 灰色 -> 白色 -> 灰色 -> 黑色...

为了比较,使用相同的默认表单布局,包含一个Label UI控件label1和一个Timer控件timer1,如下所示:

以下是使用常规事件处理器在C#中通过等待Timer.Tick事件来使Label UI控件的背景色闪烁的代码。

public partial class NormalEventHandlerForm : Form { public NormalEventHandlerForm() { InitializeComponent(); } private int _Bright = 0; private int _Step = 10; private void timer1_Tick(object sender, EventArgs e) { _Bright += _Step; if (_Bright > 255) { _Step = -10; _Bright += _Step; } else if (_Bright < 0) { _Step = 10; _Bright += _Step; } label1.BackColor = Color.FromArgb(_Bright, _Bright, _Bright); } }

在这种情况下,需要使用_Bright和_Step这两个字段变量来在每个Timer.Tick事件之间保持颜色闪烁的信息。这种代码看起来非常典型,在常规的事件驱动编程中就是这样编写的。

在这一部分,Lambda函数被用作事件处理器,这与第1部分(事件处理器)不同。

public partial class LambdaEventHandlerForm : Form { public LambdaEventHandlerForm() { InitializeComponent(); } private void LambdaEventHandlerForm_Load(object sender, EventArgs e) { var bright = 0; var step = 10; timer1.Tick += (_sender, _e) => { bright += step; if (bright > 255) { step = -10; bright += step; } else if (bright < 0) { step = 10; bright += step; } label1.BackColor = Color.FromArgb(bright, bright, bright); }; } }

在这种情况下,代码风格与第1部分(事件处理器)非常相似,但不需要定义字段变量。

也可以利用Rx(响应式扩展)的强大功能来处理这个示例。Rx在处理Timer.Tick事件的重复等待中非常有用。

public partial class RxForm : Form { public RxForm() { InitializeComponent(); } private void RxForm_Load(object sender, EventArgs e) { var enum_bright = Enumerable.Range(0, 25 + 1).Concat(Enumerable.Range(0, 25).Reverse()).Select(_i => _i * 10).ToArray(); Observable.FromEvent(timer1, "Tick").Zip(enum_bright, (_e, _bright) => _bright).Repeat().Subscribe(_bright => { label1.BackColor = Color.FromArgb(_bright, _bright, _bright); }); } }

代码风格与前面的部分有很大的不同,如果有使用Rx的经验,将很容易理解代码的行为。

在这一部分,使用了YieldAwait库。这个库在需要按顺序等待多个事件的情况下非常有用。

public partial class YieldAwaitForm : Form { public YieldAwaitForm() { InitializeComponent(); } IEnumerable TestFunc(EventWaiter waiter) { while (true) { for (var bright = 0; bright < 255; bright += 10) { label1.BackColor = Color.FromArgb(bright, bright, bright); yield return waiter.Wait(timer1, "Tick"); } for (var bright = 255; bright > 0; bright -= 10) { label1.BackColor = Color.FromArgb(bright, bright, bright); yield return waiter.Wait(timer1, "Tick"); } } } private void YieldAwaitForm_Load(object sender, EventArgs e) { new EventWaiter(TestFunc); } }

这是非常直接的。可以在代码中直接表达想要做的事情。即使不知道如何有效地使用Rx,也可以做到这一点。

在查看了第3部分(Rx)和第4部分(YieldAwait)的代码风格后,可能会认为也可以使用常规事件处理器与yield函数以如下方式编写代码:

public partial class EventHandlerAndYieldForm : Form { public EventHandlerAndYieldForm() { InitializeComponent(); } private IEnumerator _BrightEnumerator; private void timer1_Tick(object sender, EventArgs e) { _BrightEnumerator.MoveNext(); var bright = _BrightEnumerator.Current; label1.BackColor = Color.FromArgb(bright, bright, bright); } IEnumerator _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语句。它变得更易于阅读,因为代码将完全按照编写的顺序执行,一行一行地执行。
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485