Screenshot of the Application Above
What is Described in This Article?
We have several ways to await events in C#, for example:
- using a normal event handler
- using a lambda function as an event handler
- using Rx (Reactive Extensions)
I've created a library named as YieldAwait that allows you to stop running the code wherever you want in order to await events using the functionality of yield
sentence. It means that it provides you with a new way of awaiting events. So we've had yet another way now. - using YieldAwait library
Therefore, in this article, I'm going to compare what the code styles look like when you use those four ways, respectively.
What is YieldAwait Library?
YieldAwait library is being developed in CodePlex. You can download the source codes and more examples there. It includes more advanced examples of awaiting events using this library.
Assuming an Example Situation
As an example here, we think about the situation where we create a code which blinks the backcolor of a Label
UI control by awaiting Timer.Tick
events. The blinking is in the way like black -> gray -> white -> gray -> black...
The final goal of the blinking behavior should be like the following image:
For the comparison, we use the same default form layout which contains one Label
UI control label1
and one Timer
control timer1
as shown below:
1. Using a Normal Event Handler
First, here is the code to blink the backcolor
of a Label
UI control by awaiting Timer.Tick
events by using a normal event handler in the line [C].
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);
}
}
In this case, you have to use two field variables of _Bright
and _Step
to keep the color blinking information among each Timer.Tick
event. This code seems to be very typical and we write this way in a normal event-driven programming.
2. Using a Lambda Function as an Event Handler
In this section, a lambda function is used for the event handler in the line [C], which makes it different from the section 1 (event handler).
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);
};
}
}
In this case, the code style is very similar to the section 1 (event handler), but you don't have to define field variables.
3. Using Rx (Reactive Extensions)
We can also use the power of Rx (Reactive Extension) for this example. Rx is useful in the case of awaiting Timer.Tick
events repeatedly as well.
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<EventArgs>(timer1, "Tick")
.Zip(enum_bright, (_e, _bright) => _bright)
.Repeat()
.Subscribe(_bright => {
label1.BackColor = Color.FromArgb(_bright, _bright, _bright);
});
}
}
The code style is very different from the previous sections as you will see. It would be easy to understand how the code behaves if you have somewhat experience in using Rx.
In the line [G], an array named as enum_bright
containing the list of [0, 10, ... 240, 250, 240, ... 10, 0] is created. Using Observable
of Rx, the timer1.Tick
event is caught in the line [H] and the array and the events are combined in the line [I]. Each time the event occurs, the code in the line [F] will be called and the variable _bright
will change like 0, 10, ... 240, 250, 240, ... 10, 0. Thus, the back color of label1
will blink from black to white to black.
4. Using YieldAwait Library
In this section, YieldAwait
library is used. The library is very useful in such a type of cases where you have to await several events in a sequential way.
public partial class YieldAwaitForm : Form {
public YieldAwaitForm() {
InitializeComponent();
}
IEnumerable<bool> 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);
}
}
It's straight-forward. You can express what you want to do directly in the code. You can write codes exactly in the same way as you thought. You can do that even if you don't know how to use Rx effectively.
The code has two for loops. One in the line [K1] is for the blinking from black to white and the other in the line [K2] is for the blinking from white to black. In the [K1] for loop, the variable bright
will go from 0
up to 250
like 0
, 10
, ... 240
, 250
. The for loop will stop at the line [L1] each time and run again when a timer.Tick
event occurs. Thus, the back color of label1
will change from black to white. The [K2] for loop works in the same way and the [J] while loop makes the blinking last forever.
If you want to know how to use this library, the project page on CodePlex has an easy-to-understand explanation and a very simple example code. So please go to see the page also.
5. Using a Normal Event Handler with Yield Function
After looking at both the code styles of the sections 3. (Rx) and 4. (YieldAwait
), you might think it is also possible to use a normal event handler with a yield
function in the following way:
public partial class EventHandlerAndYieldForm : Form {
public EventHandlerAndYieldForm() {
InitializeComponent();
}
private IEnumerator<int> _BrightEnumerator;
private void timer1_Tick(object sender, EventArgs e) {
_BrightEnumerator.MoveNext();
var bright = _BrightEnumerator.Current;
label1.BackColor = Color.FromArgb(bright, bright, bright);
}
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();
}
}
I think it's less readable comparing to the section 4 (YieldAwait
), but you can also write code this way using a yield
function.
Points of Interest
First, you can write code in many styles for the blinking label program. I can't say which is good and which is bad. All the styles might have good points and bad points.
However, some points that I thought are interesting are:
- When you use Rx like in the section 3,
- many small lambda functions are likely to appear in code,
- and the scope of each variable that is used in the code is likely to be small.
- The small scope means being less complex and easy-to-read.
- When you use the
YieldAwait
library like in the section 4,
- you don't use any lambda functions,
- and the code looks very common because of not using any technical coding (like method chain or lambda functions...) but
yield
sentences. - It gets more readable because the code will run exactly in the order of what is just written, line by line.
- The usage and behavior are very similar to using the new keyword
await
introduced in the next version C# 5.0.
What do you think the points are? Your comments are welcomed.
Links and More Information
Project
Technical Explanations
Previous Forums
History
- 2011/01/10
- Wrote an article of the first edition
- 2011/01/18
- Added a few explanations to the sections 4 and 5
- Added a screenshot for the sample application
- 2011/01/19
- Added a few explanations to Points of Interest
- 2011/01/28