What is Described in This Article?

We have several ways to await events in C#, for example: 

    1. using a normal event handler 
    2. using a lambda function as an event handler 
    3. 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.

    4. 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: 

blinking.gif 

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: 

design_form.png 

doc_outline.png 

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; // [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]
    }
} 

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; // [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]
        };
    }
} 

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 Extention) 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 // [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]
            });
    }
} 

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

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);
    }
} 

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. 

5. Using a Normal Event Handler with Yield Function

After looking at the both code styles of 3. (Rx) and 4. (YieldAwait) sections, 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) { // [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();
    }
} 

Points of Interest 

What do you think the points are? (I'm writing this section right now) 

History  

  • 2010/01/10
    • Wrote an article of the first edition 

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架