UPDATE: @SimonCropp just tweeted a link to his FluentDateTime library which handles some of the extensions below. It's even available via NuGet. Nice one!
One of the ways I like to keep my code readable and expressive is around anything to do with the DateTime and TimeSpan classes. Using DateTime or TimeSpan directly can be a little hard to comprehend. Here's an example. Say we have an NHibernate Linq query retrieving all widgets that are less than a day old.
var widgets = session.Query<Widget>()
.Where(w => w.CreatedOn > DateTime.Now.AddDays(-1));
Forget the Linq in this example, the bit I find hard to read is the date we're comparing with.
w.CreatedOn > DateTime.Now.AddDays(-1)
The DateTime isn't immediately communicating its intent. This is the sort of code that really begs for a quick comment.
var widgets = session.Query<Widget>()
// Get widgets that are less than a day old.
.Where(w => w.CreatedOn > DateTime.Now.AddDays(-1));
The problem with adding a comment however is that we've repeated our code in English. Changing one now requires us to change the other. If these ever get out of sync, chaos will abound. Cats and dogs will rain from the sky! Or worse, your technical lead will get a headache. Keep it DRY (yes, I think most comments violate DRY) and readable. Enter extension methods and a neat little trick from Ruby on Rails.
w.CreatedOn > 1.Day().Ago()
How do we achieve such awesomeness? It's surprisingly easy. First we create an extension method that gives us the time span we care about.
public static class TimeSpanHelper
{
public static TimeSpan Days(this int days)
{
return TimeSpan.FromDays(days);
}
}
We only care about one day in this particular example and it's not hard to cater for the singlular form.
public static TimeSpan Day(this int day)
{
return Days(day);
}
Next, we create something that can cast our time span into the past.
public static class DateTimeHelper
{
public static DateTime Ago(this TimeSpan timeSpan)
{
return DateTime.Now.Subtract(timeSpan);
}
}
And the future (while we're here).
public static DateTime FromNow(this TimeSpan timeSpan)
{
return DateTime.Now.Add(timeSpan);
}
Now we can write the Linq query like so.
var widgets = session.Query<Widget>()
.Where(w => w.CreatedOn > 1.Day().Ago());
Much clearer. I don't think that code needs a comment. Nothing to get out of sync either. Your technical lead may still get a headache from the extension methods, but we can't have it all. ;)
Of course TimeSpanHelper can be extended to cater for seconds, minutes, hours, etc. Here's one more example. Say we wanted to find the time half a week from now. There's a few ways to do that.
84.Hours().FromNow();
(3.Days() + 12.Hours()).FromNow();
3.5.Days().FromNow();
0.5.Weeks().FromNow();
The last two require extending double instead of int, like this.
public static TimeSpan Weeks(this double weeks)
{
return TimeSpan.FromDays(weeks * 7);
}
The double extensions can be handy at times, but I prefer to use int whenever I can. I think it's a little more readable.
Before I go, what do you think of this statement?
w.CreatedOn.IsNewerThan(1.Day().Ago())
I'll leave it up to you to implement.
There are 0 comments.
browse with Pivot
Readable Code with Extension Methods
Visual Studio 2010 and Vim
Zenburn PowerShell
Mercurial Prompt
PowerShell Prompt
Architecture (13)
ASP.NET (2)
ASP.NET MVC (13)
Brisbane Flood (1)
Building Neno (38)
Communicator (1)
Concurrency Control (2)
Configuration (1)
CSS (5)
DataAnnotations (2)
Database (1)
DotNetOpenAuth (2)
Entity Framework (1)
FluentNHibernate (2)
Inversion of Control (5)
JavaScript (1)
jQuery (4)
Linq (5)
Markdown (4)
Mercurial (5)
NHibernate (20)
Ninject (2)
OpenID (3)
Pivot (6)
PowerShell (8)
Prettify (2)
RSS (1)
Spring (3)
SQL Server (5)
T-SQL (2)
Validation (2)
Vim (1)
Visual Studio (2)
Windows Forms (3)
Windows Service (1)