在C#中,有多种方式来等待事件的发生,例如使用常规的事件处理器、Lambda函数作为事件处理器、Rx(响应式扩展),以及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函数以这种方式编写代码。
首先,可以用多种风格编写闪烁标签程序的代码。不能说哪种好哪种坏。所有风格可能都有优点和缺点。