ITimer 1.1.0

Suggested Alternatives

Microsoft.Bcl.TimeProvider

Additional Details

Use System.TimeProvider instead (https://learn.microsoft.com/en-us/dotnet/api/system.timeprovider)

dotnet add package ITimer --version 1.1.0                
NuGet\Install-Package ITimer -Version 1.1.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ITimer" Version="1.1.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ITimer --version 1.1.0                
#r "nuget: ITimer, 1.1.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install ITimer as a Cake Addin
#addin nuget:?package=ITimer&version=1.1.0

// Install ITimer as a Cake Tool
#tool nuget:?package=ITimer&version=1.1.0                

<img src="https://raw.githubusercontent.com/RobThree/ITimer/main/logo.png" alt="Logo" width="32" height="32"> ITimer

Provides a testable abstraction and alternative to System.Threading.Timer, System.Timers.Timer and System.Threading.PeriodicTimer. Targets netstandard2.0 and higher.

Build status <a href="https://www.nuget.org/packages/ITimer/"><img src="http://img.shields.io/nuget/v/ITimer.svg?style=flat-square" alt="NuGet version" height="18"></a>

First: Good news! 🎉

As of the release of .Net 8 (nov. 14th 2023) Microsoft provides the TimeProvider class and ITimer interface. A good primer on this topic is over at Andrew Lock's site (archived version). You may want to check that out before continuing. What that means for ITimer? It'll most likely be the end of this library, but that's a good thing. I'll keep supporting it for a while but switching to the Microsoft provided solution shouldn't be too hard.

Why and how

Timer related code is, or should I say used to be, hard to unittest. When you have timer related code, you (probably) don't want to wait until the timer elapses in your unittest which would in turn make your unittests slower than strictly necessary.

The basis for this library is the ISignaler interface which defines an interface for timers to implement that allow you to replace those timers with the TestTimer in your unittests so you have total control over when the timer fires the Elapsed event.

This library provides the most common timers: System.Threading.Timer, System.Timers.Timer and System.Threading.PeriodicTimer wrapped in the ThreadingTimer and SystemTimer classes respectively. Other, custom, timers should be simple to implement by simply implementing the ISignaler interface.

ISignaler? Why not ITimer?

Agreed, ISignaler is not the best name. ITimer would have been a much better choice, but that conflicts with the namespace. That would require you to write ITimer.ITimer everywhere this interface is used. And since we wanted a simple package-ID and simple (root) namespace we opted for ITimer as namespace and ISignaler as interface name. If you have any better suggestions, please let us know and we'll consider it for the next major version.

Quickstart

In your code:

public class MyClass
{
    private readonly ISignaler _timer;

    public MyClass(ISignaler timer)
    {
        _timer = timer ?? throw new ArgumentNullException(nameof(timer));
        _timer.Elapsed += (s, e) => { 
            // Do work here...
            Console.WriteLine($"Tick tock! {e.SignalTime}"); 
        };

    }

    public void Start() {
      _timer.Start();
    }

    public void Stop() {
      _timer.Stop();
    }
}

using (var myTimer = new SystemTimer()) {
  var myclass = new MyClass(myTimer);
  myClass.Start();
  //...
}

Or, even better, using Dependency Injection:

public void ConfigureServices(IServiceCollection services)
{
    // Register SystemTimer as ISignaler
    services.AddScoped<ISignaler, SystemTimer>();
    // ...
}

For usage in unittests, see the TestTimer below.

ISignaler

The ISignaler interface defines the Start() and Stop() methods to start and stop the timer raise the Elapsed event. The Interval property gets the timer's interval and the AutoReset property returns whether or not the timer should fire the event once and then stop, or keep going in a fire event / wait cycle.

ThreadingTimer, SystemTimer and PeriodicTimer

As mentioned before, these timers encapsulate (or "wrap") the System.Threading.Timer and System.Timers.Timer timers and provide a unified interface because they both implement the ISignaler interface. The difference between the first two is perhaps best explained by Jon Skeet (archived version here or here). The System.Windows.Forms.Timer is not provided by this library but should be simple to implement.

All timers provided by this library are simplified versions of the underlying timers unified to a single, simple, interface. If you need a more specific implementation then you may want to implement them again in your own class, also implementing the ISignaler interface.

TestTimer

With the TestTimer you are in complete control over when, and how often, the timer fires. You even control the SignalTime so you can specify at what (pretend) time the timer fired. Ofcourse, this is very useful in unittests.

The TestTimer offers some extra properties like the TickCount, StartCount and StopCount that keep track of how often the Elapsed event has been raised and the timer has been started and stopped respectively (all of which can be reset with the Reset() method). The Elapsed event for the TestTimer provides the TestTimerElapsedEventArgs which also contains a TickCount property.

Most important, however, for the TestTimer are the Tick(DateTimeOffset?), Tick(IEnumerable<DateTimeOffset>) and Tick(Int32, Func<Int32, DateTimeOffset>) methods. These methods will raise the Elapsed event on the TestTimer and allow you to specify the SignalTime.

Given the above example, we can now replace the SystemTimer with a TestTimer for our tests:

using (var myTestTimer = new TestTimer()) {
  var myclass = new MyClass(myTestTimer);

  myTestTimer.Tick(); // Raise elapsed event
  Assert.IsTrue(...);
}

The different overloads of the Tick() method allow you to raise the event multiple times. The TestTimer has a constructor argument requireStart that allows you to specify wether you require the timer to be started before it will start raising events or not; this defaults to the latter, making your unittests more concise not having to start the TestTimer each time. When this value equals true the event won't be raised by any of the Tick() methods unless Start() is called.

License

Licensed under MIT license. See LICENSE for details.

Attribution

Icon made by Freepik from www.flaticon.com.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.
  • net6.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.1.0 610 11/20/2023 1.1.0 is deprecated because it is no longer maintained.
1.0.4 2,562 6/22/2022
1.0.3 2,778 5/30/2022
1.0.2 7,955 4/28/2021
1.0.1 405 10/27/2020
1.0.0 444 10/26/2020
1.0.0-alpha 281 10/5/2020

Obsoleted package