Best Practice for Using Timers in .NET C#
top of page

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

  • Writer's pictureAhmed Tarek

Best Practice for Using Timers in .NET C#

Updated: Apr 17

How to have full control over the Timer and be able to reach 100% coverage with unit tests.


Best Practice for Using Timers in DotNet (.NET) CSharp (C#). Applying Dependency Injection (DI), Inversion of Control (IoC), Test Driven Development (TDD), Unit Testing, Coverage 100% Software Development Engineering Architecture Code Coding Programming
Photo by Agê Barros on Unsplash, adjusted by Ahmed Tarek

When using System.Timers.Timer in your .NET C# application, you might face problems with abstracting it and being able to cover your modules with Unit Tests.


In this article, we would be discussing the Best Practices on how to conquer these challenges and by the end you would be able to achieve 100% coverage of your modules.



The Approach


This is how we are going to approach our solution:

  1. Come up with a very simple example to work on.

  2. Start with the simple bad solution.

  3. Keep trying to enhance it till we reach the final format.

  4. Summarizing the lessons learned through our journey.



The Example


In our example, we will be building a simple Console Application which would do only one simple thing, use a System.Timers.Timer to write to the console the date and time every one second.


At the end, you should end up with this:


Best Practice for Using Timers in DotNet (.NET) CSharp (C#). Applying Dependency Injection (DI), Inversion of Control (IoC), Test Driven Development (TDD), Unit Testing, Coverage 100% Software Development Engineering Architecture Code Coding Programming

As you can see, it is simple in terms of requirements, nothing fancy.


 

Best Practice for Using Timers in DotNet (.NET) CSharp (C#). Applying Dependency Injection (DI), Inversion of Control (IoC), Test Driven Development (TDD), Unit Testing, Coverage 100% Software Development Engineering Architecture Code Coding Programming
Photo by Mikael Seegen on Unsplash, adjusted by Ahmed Tarek

Disclaimer


  1. Some best practices would be ignored/dropped in order to drive the main focus to the other best practices targeted on this article.

  2. On this article we would focus on covering the module using System.Timers.Timer with unit tests. However, the rest of the solution would not be covered with unit tests. If you would like to know more about this, you can check the article How to Fully Cover .NET C# Console Application With Unit Tests.

  3. There are some third party libraries which could be used to achieve nearly similar results. However, whenever possible, I would rather follow a native simple design than depending on a whole big third party library.


 


 

Best Practice for Using Timers in DotNet (.NET) CSharp (C#). Applying Dependency Injection (DI), Inversion of Control (IoC), Test Driven Development (TDD), Unit Testing, Coverage 100% Software Development Engineering Architecture Code Coding Programming
Photo by Maria Teneva on Unsplash, adjusted by Ahmed Tarek

Bad Solution


In this solution, we would directly use System.Timers.Timer without providing a layer of abstraction.


The structure of the solution should look like this:


Best Practice for Using Timers in DotNet (.NET) CSharp (C#). Applying Dependency Injection (DI), Inversion of Control (IoC), Test Driven Development (TDD), Unit Testing, Coverage 100% Software Development Engineering Architecture Code Coding Programming

It is a UsingTimer solution with only one Console TimerApp project.


I intentionally invested some time and effort in abstracting System.Console into IConsole to prove that this would not solve our problem with the Timer.



We would only need to use System.Console.WriteLine in our example, that’s why this is the only abstracted method.



We have only two methods on the IPublisher interface; StartPublishing and StopPublishing.


Now, for the implementations.



Console is just a thin wrapper for System.Console.



Publisher is a simple implementation of IPublisher. It is using a System.Timers.Timer and just configuring it.


It has the IConsole defined as a dependency. This is not a best practice from my point of view. If you want to understand what I mean, you can check the article When Not To Use DI, IoC, and IoC Containers in .NET C#. However, only for the sake of simplicity, we would just inject it as a dependency in the constructor.


We are also setting the Timer interval to 1000 Milliseconds (1 Second), and setting the handler to write the Timer SignalTime to the Console.



Here in the Program class we are not doing much. We are just creating an instance of the Publisher class and starting publishing.


Running this should end up with something like this:


Best Practice for Using Timers in DotNet (.NET) CSharp (C#). Applying Dependency Injection (DI), Inversion of Control (IoC), Test Driven Development (TDD), Unit Testing, Coverage 100% Software Development Engineering Architecture Code Coding Programming

Now, the question is, if you are going to write a unit test for the Publisher class, what can you do?


Unfortunately, the answer would be: not too much.


First, you are not injecting the Timer itself as a dependency. This means that you are hiding the dependency inside the Publisher class. Therefore, we can’t mock or stub the Timer.


Second, let’s say that we modified the code so that the Timer is now injected in the constructor, still the question would be, how to write a unit test and replace the Timer with a mock or stub?


I am hearing someone shouting let’s wrap the Timer into an abstraction and inject it instead of the Timer.


Yes, that’s right, however, it is not that simple. There are some tricks which I am going to explain in the next section.


 


 

Best Practice for Using Timers in DotNet (.NET) CSharp (C#). Applying Dependency Injection (DI), Inversion of Control (IoC), Test Driven Development (TDD), Unit Testing, Coverage 100% Software Development Engineering Architecture Code Coding Programming
Photo by Carson Masterson on Unsplash, adjusted by Ahmed Tarek

Good Solution


This is the time for the good solution. Let’s see what we can do about it.


The structure of the solution should look like this:


Best Practice for Using Timers in DotNet (.NET) CSharp (C#). Applying Dependency Injection (DI), Inversion of Control (IoC), Test Driven Development (TDD), Unit Testing, Coverage 100% Software Development Engineering Architecture Code Coding Programming

It is the same UsingTimer solution with a new Console BetterTimerApp project.


IConsole, IPublisher, and Console would be the same.



ITimer



What we can notice here:

  1. We defined the new delegate TimerIntervalElapsedEventHandler. This delegate represents the event to be raised by our ITimer.

  2. You might argue that we don’t need this new delegate as we already have the native ElapsedEventHandler which is already used by System.Timers.Timer.

  3. Yes, this is true. However, you would notice that the ElapsedEventHandler event is providing ElapsedEventArgs as the event arguments. This ElapsedEventArgs has a private constructor and you would not be able to create your own instance. Additionally, the SignalTime property defined in the ElapsedEventArgs class is read-only. Therefore, you would not be able to override it in a child class.

  4. There is a change request ticket opened for Microsoft to update this class but up till the moment of writing this article no changes were applied.

  5. Also, note that ITimer extends the IDisposable.



Publisher



It is almost the same as the old Publisher except for small changes. Now we have the ITimer defined as a dependency which is injected through the constructor. The rest of the code would be the same.



Timer



This is where almost all the magic happens.


What we can notice here:

  1. Internally we are using System.Timers.Timer.

  2. We applied the IDisposable design pattern. That’s why you can see the private bool m_IsDisposed, public void Dispose(), protected virtual void Dispose(bool disposing), and ~Timer().

  3. In the constructor we are initializing a new instance of System.Timers.Timer. We would refer to this as the Internal Timer in the rest of the steps.

  4. For public bool Enabled, public double Interval, public void Start(), and public void Stop(), we are just delegating the implementation to the Internal Timer.

  5. For public event TimerIntervalElapsedEventHandler TimerIntervalElapsed, this is the most important part so let’s analyze it step by step.

  6. What we need to do with this event, is to handle when someone subscribes/unsubscribes to it from outside. In this case, we want to mirror this to the Internal Timer.

  7. In other words, if someone from outside is having an instance of our ITimer, he should be able to do something like this t.TimerIntervalElapsed += (sender, dateTime) => { //do something }.

  8. At this moment, what we should do is internally do something like m_Timer.Elapsed += (sender, elapsedEventArgs) => { //do something }.

  9. However, we need to keep in mind that the two handlers are not the same as they are actually of different types; TimerIntervalElapsedEventHandler and ElapsedEventHandler.

  10. Therefore, what we need to do is to wrap the coming in TimerIntervalElapsedEventHandler into a new internal ElapsedEventHandler. This is something we can do.

  11. However, we also need to keep in mind that at some point someone might need to unsubscribe a handler from the TimerIntervalElapsedEventHandler event.

  12. This means that at this moment, we need to be able to know which ElapsedEventHandler handler corresponds to that TimerIntervalElapsedEventHandler handler so that we can unsubscribe it from the Internal Timer.

  13. The only way to achieve this is through keeping track of each TimerIntervalElapsedEventHandler handler and the newly created ElapsedEventHandler handler in a dictionary. This way, by knowing the passed in TimerIntervalElapsedEventHandler handler, we can know the corresponding ElapsedEventHandler handler.

  14. However, we also need to keep in mind that from outside, someone might subscribe the same TimerIntervalElapsedEventHandler handler more than once.

  15. Yes, this is not logical, but still it is doable. Therefore, for the sake of completeness, for each TimerIntervalElapsedEventHandler handler we would keep a list of ElapsedEventHandler handlers.

  16. In most of the cases, this list would have only one entry unless in case of a duplicate subscription.

  17. And that’s why you can see this private Dictionary<TimerIntervalElapsedEventHandler, List<ElapsedEventHandler>> m_Handlers = new();



In the add, we are creating a new ElapsedEventHandler, adding a record in m_Handlers the dictionary mapping this to TimerIntervalElapsedEventHandler and finally subscribing to the Internal Timer.


In the remove, we are getting the corresponding list of ElapsedEventHandler handlers, selecting the last handler, unsubscribing it from the Internal Timer, removing it from the list, and removing the whole entry if the list is empty.


Also, worth to mention, the Dispose implementation.



We are unsubscribing all the remaining handlers from the Internal Timer, disposing the Internal Timer, and clearing the m_Handlers dictionary.



Program



Here we are still not doing much. It is almost the same as the old solution.


Running this should end up with something like this:


Best Practice for Using Timers in DotNet (.NET) CSharp (C#). Applying Dependency Injection (DI), Inversion of Control (IoC), Test Driven Development (TDD), Unit Testing, Coverage 100% Software Development Engineering Architecture Code Coding Programming

 


 

Best Practice for Using Timers in DotNet (.NET) CSharp (C#). Applying Dependency Injection (DI), Inversion of Control (IoC), Test Driven Development (TDD), Unit Testing, Coverage 100% Software Development Engineering Architecture Code Coding Programming
Photo by Testalize.me on Unsplash, adjusted by Ahmed Tarek

Time For Testing, The Moment of Truth


Now, we have our final design. However, we need to see if this design really can help us cover our Publisher module with unit tests.


The structure of the solution should look like this:


Best Practice for Using Timers in DotNet (.NET) CSharp (C#). Applying Dependency Injection (DI), Inversion of Control (IoC), Test Driven Development (TDD), Unit Testing, Coverage 100% Software Development Engineering Architecture Code Coding Programming

I am using NUnit and Moq for testing. You can for sure work with your preferred libraries.



TimerStub



What we can notice here:

  1. We defined the Action enum to be used while logging the actions performed through our Timer stub. This would be used later to assert the internal actions performed.

  2. Also, we defined the ActionLog class to be used for logging.

  3. We defined the TimerStub class as a stub of ITimer. We would use this stub later when testing the Publisher module.

  4. The implementation is simple. Worth to mention that we added an extra public void TriggerTimerIntervalElapsed(DateTime dateTime) method so that we can trigger the the stub manually within a unit test.

  5. We can also pass in the expected value of the dateTime so that we have a known value to assert against.



PublisherTests



Now as you can see, we have full control and we can easily cover our Publisher module with unit tests.


If we calculate the coverage, we should get this:


Best Practice for Using Timers in DotNet (.NET) CSharp (C#). Applying Dependency Injection (DI), Inversion of Control (IoC), Test Driven Development (TDD), Unit Testing, Coverage 100% Software Development Engineering Architecture Code Coding Programming

As you can see, the Publisher module is 100% covered. For the rest, this is out of scope of this article but you can simply cover it if you follow the approach on the article How to Fully Cover .NET C# Console Application With Unit Tests.


 

Best Practice for Using Timers in DotNet (.NET) CSharp (C#). Applying Dependency Injection (DI), Inversion of Control (IoC), Test Driven Development (TDD), Unit Testing, Coverage 100% Software Development Engineering Architecture Code Coding Programming
Photo by Jingda Chen on Unsplash, adjusted by Ahmed Tarek

Final Words


You can do it. It is just a matter of splitting large modules into smaller ones, defining your abstractions, getting creative with tricky parts and then you are done.


If you want to train yourself more, you can check my other articles about some Best Practices.



Recent Posts

See All

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

bottom of page
Mastodon Mastodon