вторник, 24 декабря 2013 г.

C#: Realtime event-driven date presenter

Hello everybody. This is another time saving post written to provide right solution for frequently occurred annoying development cases. Recently I bumped into requirement to show relevant date and time to user, like the system tray clock does. Considering that it is far not the first time of such situation, writing complete and optimal solution is appropriate. Let's have a look at my approach of doing it:
using System;
using System.ComponentModel;
using System.Linq;
using System.Threading;

namespace DateTimeServices
{
    public delegate void SecondChangedEventHandler(DateTime currentDateTime);
    /// <summary>
    /// Event - driven system clock wrapper.
    /// Every second notifies listeners with current date changed.
    /// </summary>
    public static class CurrentDateTimeNotifier
    {
        private static event SecondChangedEventHandler _secondChangedEvent; //Event field
        private static DateTime _currentDateTime;
        //The less the interval is, the less delays visible
        private const Int32 SecondCheckIntervalMs = 200; 
        private static BackgroundWorker _backgroundTimeChecker = new BackgroundWorker();
        private static Boolean _areListenersPresent;

        static CurrentDateTimeNotifier()
        {
            //Note: that is only property assignment.
            //Worker stands by until first listener appeared.
            _backgroundTimeChecker.DoWork +=
                (Object data, DoWorkEventArgs eventArguments) =>
            {
                Int32 currentSecond = _currentDateTime.Second;
                while (_areListenersPresent) //Stops tracking if there aren't listeners
                {
                    Thread.Sleep(SecondCheckIntervalMs);
                    _currentDateTime = DateTime.Now;
                    if (currentSecond != _currentDateTime.Second)
                    {
                        currentSecond = _currentDateTime.Second;
                        _secondChangedEvent.Invoke(_currentDateTime);
                    }
                }
            };
            AppDomain.CurrentDomain.ProcessExit += //Stop background worker before exit
                (Object data, EventArgs eventArguments) => _areListenersPresent = false;
        }
        //Event accessor
        public static event SecondChangedEventHandler SecondChangedEvent
        {
            add
            {
                _secondChangedEvent += value;
                _areListenersPresent = true;
                if (!_backgroundTimeChecker.IsBusy)
                {
                    //Occurs when first listener (at init or after removal) added
                    _backgroundTimeChecker.RunWorkerAsync();
                }
            }
            remove
            {
                if (_secondChangedEvent.GetInvocationList().Contains(value))
                {
                    _secondChangedEvent -= value;
                    //If the last listener is removed, background worker stops
                    _areListenersPresent =
                        _secondChangedEvent.GetInvocationList().Length > 0;
                }
            }
        }
    }
}
You can start using it just by copying this code in a separate file. Advantages of this solution:
  • Because of static implementation it can be used project-wide simultaneously
  • Events mechanism makes it well combined with common object-oriented design patterns
  • It works only when needed - means not loading CPU for nothing
  • It is made thread-, process- and memory-safe
  • And the main ones - clear and reusable
Check this sample out in console:
static void Main(string[] args)
{
    CurrentDateTimeNotifier.SecondChangedEvent +=
    (DateTime currentDateTime) =>
    {
        Console.WriteLine("First listener: " + currentDateTime.Second);
    };

    CurrentDateTimeNotifier.SecondChangedEvent +=
    (DateTime currentDateTime) =>
    {
        Console.WriteLine("Second listener: " + currentDateTime.Second);
    };
            
    Console.ReadKey();
}

Комментариев нет:

Отправить комментарий