Once again, in this series of posts I look at the parts of the .NET Framework that may seem trivial, but can help improve your code by making it easier to write and maintain. The index of all my past little wonders posts can be found here.
When I started the “Little Wonders” series, I really wanted to pay homage to parts of the .NET Framework that are often small but can help in big ways. The item I have to discuss today really is a very small item in the .NET BCL, but once again I feel it can help make the intention of code much clearer and thus is worthy of note.
The Problem - Magic numbers aren’t very readable or maintainable
In my first Little Wonders Post (Five Little Wonders That Make Code Better) I mention the TimeSpan factory methods which, I feel, really help the readability of constructed TimeSpan instances.
Just to quickly recap that discussion, ask yourself what the TimeSpan specified in each case below is
1: // Five minutes? Five Seconds?
2: var fiveWhat1 = new TimeSpan(0, 0, 5);
3: var fiveWhat2 = new TimeSpan(0, 0, 5, 0);
4: var fiveWhat3 = new TimeSpan(0, 0, 5, 0, 0);
You’d think they’d all be the same unit of time, right? After all, most overloads tend to tack additional arguments on the end. But this is not the case with TimeSpan, where the constructor forms are:
- TimeSpan(int hours, int minutes, int seconds);
- TimeSpan(int days, int hours, int minutes, int seconds);
- TimeSpan(int days, int hours, int minutes, int seconds, int milliseconds);
Notice how in the 4 and 5 parameter version we suddenly have the parameter days slipping in front of hours? This can make reading constructors like those above much harder. Fortunately, there are TimeSpan factory methods to help make your intention crystal clear:
1: // Ah! Much clearer!
2: var fiveSeconds = TimeSpan.FromSeconds(5);
These are great because they remove all ambiguity from the reader! So in short, magic numbers in constructors and methods can be ambiguous, and anything we can do to clean up the intention of the developer will make the code much easier to read and maintain.
Timeout – Readable identifiers for infinite timeout values
In a similar way to TimeSpan, let’s consider specifying timeouts for some of .NET’s (or our own) many methods that allow you to specify timeout periods.
For example, in the TPL Task class, there is a family of Wait() methods that can take TimeSpan or int for timeouts. Typically, if you want to specify an infinite timeout, you’d just call the version that doesn’t take a timeout parameter at all:
1: myTask.Wait(); // infinite wait
But there are versions that take the int or TimeSpan for timeout as well:
1: // Wait for 100 ms
4: // Wait for 5 seconds
Now, if we want to specify an infinite timeout to wait on the Task, we could pass –1 (or a TimeSpan set to –1 ms), which what the .NET BCL methods with timeouts use to represent an infinite timeout:
1: // Also infinite timeouts, but harder to read/maintain
However, these are not as readable or maintainable. If you were writing this code, you might make the mistake of thinking 0 or int.MaxValue was an infinite timeout, and you’d be incorrect. Also, reading the code above it isn’t as clear that –1 is infinite unless you happen to know that is the specified behavior.
To make the code like this easier to read and maintain, there is a static class called Timeout in the System.Threading namespace which contains definition for infinite timeouts specified as both int and TimeSpan forms:
- An integer constant with a value of –1
- A static readonly TimeSpan which represents –1 ms (only available in .NET 4.5+)
This makes our calls to Task.Wait() (or any other calls with timeouts) much more clear:
1: // intention to wait indefinitely is quite clear now
But wait, you may say, why would we care at all? Why not use the version of Wait() that takes no arguments? Good question! When you’re directly calling the method with an infinite timeout that’s what you’d most likely do, but what if you are just passing along a timeout specified by a caller from higher up? Or perhaps storing a timeout value from a configuration file, and want to default it to infinite?
For example, perhaps you are designing a communications module and want to be able to shutdown gracefully, but if you can’t gracefully finish in a specified amount of time you want to force the connection closed.
You could create a Shutdown() method in your class, and take a TimeSpan or an int for the amount of time to wait for a clean shutdown – perhaps waiting for client to acknowledge – before terminating the connection. So, assume we had a pub/sub system with a class to broadcast messages:
1: // Some class to broadcast messages to connected clients
2: public class Broadcaster
4: // ...
6: // Shutdown connection to clients, wait for ack back from clients
7: // until all acks received or timeout, whichever happens first
8: public void Shutdown(int timeout)
10: // Kick off a task here to send shutdown request to clients and wait
11: // for the task to finish below for the specified time...
13: if (!shutdownTask.Wait(timeout))
15: // If Wait() returns false, we timed out and task
16: // did not join in time.
We could even add an overload to allow us to use TimeSpan instead of int, to give our callers the flexibility to specify timeouts either way:
1: // overload to allow them to specify Timeout in TimeSpan, would
2: // just call the int version passing in the TotalMilliseconds...
3: public void Shutdown(TimeSpan timeout)
Notice in case of this class, we don’t assume the caller wants infinite timeouts, we choose to rely on them to tell us how long to wait. So now, if they choose an infinite timeout, they could use the –1, which is more cryptic, or use Timeout class to make the intention clear:
1: // shutdown the broadcaster, waiting until all clients ack back
2: // without timing out.
We could even add a default argument using the int parameter version so that specifying no arguments to Shutdown() assumes an infinite timeout:
1: // Modified original Shutdown() method to add a default of
2: // Timeout.Infinite, works because Timeout.Infinite is a compile
3: // time constant.
4: public void Shutdown(int timeout = Timeout.Infinite)
6: // same code as before
Note that you can’t default the ShutDown(TimeSpan) overload with Timeout.InfiniteTimeSpan since it is not a compile-time constant. The only acceptable default for a TimeSpan parameter would be default(TimeSpan) which is zero milliseconds, which specified no wait, not infinite wait.
While Timeout.Infinite and Timeout.InfiniteTimeSpan are not earth-shattering classes in terms of functionality, they do give you very handy and readable constant values that you can use in your programs to help increase readability and maintainability when specifying infinite timeouts for various timeouts in the BCL and your own applications.