I've been writing up some demos for the Readify Pro .NET 3.5 course over the past couple of days and on a couple of occasions I've run into the situation of needing to time a set of operations. The way I usually approach this is to get the time from DateTime.Now before and after the operations and print the result to the screen. This was starting to become tedious:

long startTime = DateTime.Now.Ticks;
System.Threading.Thread.Sleep(1500);
long endTime = DateTime.Now.Ticks;
Console.WriteLine("Time Elapsed: {0}", new TimeSpan(endTime - startTime).TotalMilliseconds.ToString());

So, I decided to take a different approach. After using RhinoMocks I stumbled upon the MockRepository.Record method. This method can be used to wrap a set of mock statements in a using block. "Eureka!" I shout...

How do I accomplish this, now...? I want to have a StopWatch that has a Time method. I want to call the Time method with a using statement and time the statements that execute within the block. This means my Time method needs to return an object that implements IDisposable so that the Dispose method is hit at the end of the using block.

What shall the Time method return...? How about a Timer object? I know, I'm a creative genious! :)

 
public class Timer : IDisposable
{
    public DateTime StartTime { get; private set; }
    public DateTime EndTime { get; private set; }
    internal event EventHandler<TimerStoppedEventArgs> TimerStopped;

    public void BeginTiming()
    {
        StartTime = DateTime.Now; // Start the timer by recording the current time.
    }

    public void StopTiming()
    {
        EndTime = DateTime.Now; // Record the end time.
        OnTimingStopped(); // Call to raise the TimerStopped event.
    }

    protected virtual void OnTimingStopped()
    {
        TimeSpan span = EndTime - StartTime; // Create the TimeSpan that defines the elapsed time.
        if (TimerStopped != null) // Raise the TimerStopped event.
            TimerStopped(this, new TimerStoppedEventArgs(span));
    }

    public void Dispose()
    {
        if (EndTime == DateTime.MinValue) // We've hit the end of the using block! Stop the timer!
            StopTiming();
    }
}

/// <summary>
/// Describes a TimerStopped event by providing the elapsed time as a TimeSpan.
/// </summary>
internal class TimerStoppedEventArgs : EventArgs
{
    public TimeSpan ElapsedTime { get; set; }
    public TimerStoppedEventArgs(TimeSpan elapsedTime)
    {
        ElapsedTime = elapsedTime;
    }
}

Ok, now to utilise this in the StopWatch...

public class StopWatch
{
    Timer currentTimer; // The Timer currently in use.

    /// <summary>
    /// The total time that the StopWatch timed.
    /// </summary>
    public TimeSpan TotalElapsedTime { get; private set; }

    public Timer Time()
    {
        // Check to see the StopWatch isn't already in use.
        if (currentTimer != null) throw new InvalidOperationException("You are already using this StopWatch!");
        // Create a Timer, create an event handler for it's TimerStopped event, start it and return it.
        currentTimer = new Timer();
        currentTimer.TimerStopped += new EventHandler<TimerStoppedEventArgs>(currentTimer_TimerStopped);
        currentTimer.BeginTiming();
        return currentTimer;
    }

    public TimeSpan GetCurrentElapsedTime()
    {
        // Check to see the StopWatch is in use.
        if (currentTimer == null) throw new InvalidOperationException("You aren't timing anything...");
        return DateTime.Now - currentTimer.StartTime; // Return the current time - the timer's start time.
    }

    void currentTimer_TimerStopped(object sender, TimerStoppedEventArgs e)
    {
        if (e != null) // Set the total elapsed time of the StopWatch.
            TotalElapsedTime = e.ElapsedTime;
        if (currentTimer != null) // Kill the current Timer.
            currentTimer.TimerStopped -= new EventHandler<TimerStoppedEventArgs>(currentTimer_TimerStopped);
        currentTimer = null;
    }
}

That's it! 90 lines of code (including comments) and five minutes later I have a StopWatch. I even threw a GetCurrentElapsedTime method in there for fun too.

Now, to test it out...

StopWatch watch = new StopWatch();
using (watch.Time())
{
    System.Threading.Thread.Sleep(1500);
}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

This was actually giving me around 1520ms. Hmm...

So I went back to my old method to test this out, and found that the following code ran with the same results... Excellent!

Ok, so using the DateTime.Now compare method isn't exactly going to be good for real time computing but, for demos and the like I think it will do the job. And it definitely makes the code look that little bit neater.

I've got a little sample available in my downloads for those interested.

Technorati Tags: ,