Recently I am rewriting an old App using WinRT, I need to display a clock, but I find there is no Timer control.

It looks like I have to implement the Timer myself. I don't use Thread.Sleep because it will block UI thread. I prefer using async await over it. To replace Thread.Sleep, I use Task.Delay: 

while (true)
{
    // 要做的操作
    await Task.Delay(毫秒);
}

为了增加逼格和可重用性,我们需要进一步封装。注意观察本高(diao)手(si)是如何一步步提高逼格的。

首先是循环条件和毫秒数,要能被控制,所以加入属性:

public int Interval { get; set; }
public bool IsEnabled { get; set; }

这样就可以:

while (IsEnabled)
{
    // 要做的操作
    await Task.Delay(Interval);
}

进一步解耦,就是把操作部分留给用户去实现,光荣伟大正确的C#语言提供了Action委托可以把这部分代码抽离出来,所以要定义一个Action委托的类型的属性,让用户自己去决定行为。

public Action<int> DoAction { get; set; }

因为用户有可能想要读取interval的值,所以Action传入参数为int类型的interval。现在就可以这样:

while (IsEnabled)
{
    DoAction(Interval);
    await Task.Delay(Interval);
}

你们以为逼格完整了吗?错,还欠缺很多。

首先是这几年流行的promise和futures编程风格。通过return this,可以让用户写代码的时候一直打.,一路.下去。所以给属性赋值的地方可以这样设计:

public AsyncTimer SetInterval(int ms)
{
    if (ms <= 0)
    {
        this.Interval = ms;
    }
    else
    {
        throw new ArgumentOutOfRangeException("ms", "interval must be larger than zero.");
    }
    return this;
}

public AsyncTimer WhenTick(Action<int> action)
{
    this.DoAction = action;
    return this;
}

注意这些方法名更注重于表达功能,这也是逼格的一部分。

既然用户要使用Timer肯定得有一个interval,所以要用带参构造去勾引用户完成interval的赋值,同时也允许不带参构造给用户强迫症的关爱。这时候菜鸟们就会写出2个构造函数来,其实装逼的办法是用C#的可选参数:

public AsyncTimer(int? ms = null)
{
    if (null != ms)
    {
        if (ms <= 0)
        {
            throw new ArgumentOutOfRangeException("ms", "interval must be larger than zero.");
        }
        Interval = ms.Value;
    }
}

最后增加一对start/stop方法,也可以再奖励一个toggle方法,就可以完成装逼了,最终封装成这样:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DateDiffer.WinRT
{
    public class AsyncTimer
    {
        public int Interval { get; set; }

        public bool IsEnabled { get; set; }

        public Action<int> DoAction { get; set; }

        public AsyncTimer(int? ms = null)
        {
            if (null != ms)
            {
                if (ms <= 0)
                {
                    throw new ArgumentOutOfRangeException("ms", "interval must be larger than zero.");
                }
                Interval = ms.Value;
            }
        }

        public AsyncTimer SetInterval(int ms)
        {
            if (ms <= 0)
            {
                this.Interval = ms;
            }
            else
            {
                throw new ArgumentOutOfRangeException("ms", "interval must be larger than zero.");
            }
            return this;
        }

        public void Stop()
        {
            IsEnabled = false;
        }

        public AsyncTimer WhenTick(Action<int> action)
        {
            this.DoAction = action;
            return this;
        }

        public async Task StartAsync()
        {
            if (Interval <= 0)
            {
                throw new InvalidOperationException("interval must be set.");
            }

            if (DoAction == null)
            {
                throw new InvalidOperationException("action must be set.");
            }

            IsEnabled = true;

            while (IsEnabled)
            {
                DoAction(Interval);
                await Task.Delay(Interval);
            }
        }

        public void Toggle()
        {
            IsEnabled = !IsEnabled;
        }
    }
}

然后调用的地方就是这样:

TimerTask = new AsyncTimer(1000).WhenTick(i => OnOneSecondPassed()).StartAsync();
...
protected virtual void OnOneSecondPassed()
{
    CurrentTime = DateTime.Now;
}