There are a number of times when you want to access properties of an instance in code and it can get a bit clunky and repetative. I was recently at a presentation at DotNetOxford. As part of the presentation by Jon Skeet I noticed he was deconstructing DateTime. Now in itself this isn’t interesting however having not investigated this language feature much I thought I would investigate and see how easy it is.
At the beginning
When you want to access properties of objects, such as DateTime
or DateTimeOffset
you reference the property of the instance and assign the value to a variable.
[Fact]
public void Original()
{
var date = new DateTimeOffset(2018, 10, 23, 0, 0, 0, 0, TimeSpan.Zero);
var year = date.Year;
Assert.Equal(2018, year);
}
This is a simple unit test as an example. This in itself is fine.
Multi property referencing
What happens when you want to start referencing multiple properties? The code starts to duplicate, gets repetative and gets a bit clunky.
[Fact]
public void OriginalMoreParts()
{
var date = new DateTimeOffset(2018, 10, 23, 0, 0, 0, 0, TimeSpan.Zero);
var year = date.Year;
var month = date.Month;
var day = date.Day;
Assert.Equal(2018, year);
}
This can potentially hide the meaning of the functionality you are trying to write with boiler plate code.
Cleaner way?
Then you remember the new language feature which allows for deconstucting the instance, using the new powers of Tuples, and you write the following:
[Fact]
public void Deconstruct()
{
var date = new DateTimeOffset(2018, 10, 23, 0, 0, 0, 0, TimeSpan.Zero);
(int day, int month, int year) = date;
Assert.Equal(2018, year);
}
It looks nice and clean however you start getting a red squiggly under the date
variable when you are deconstructing the values into day
, month
and year
.
CSharp allows for custom Deconstruct
methods to be added to built in types through the power of extension methods - https://docs.microsoft.com/en-us/dotnet/csharp/deconstruct#deconstructing-a-user-defined-type-with-an-extension-method
New extension method
Using the knowledge we have gathered about extension methods we write the following:
public static class DateTimeOffsetExtensions
{
/// <summary>
/// Version #1
/// </summary>
public static void Deconstruct(this DateTimeOffset date, out int day, out int month, out int year)
{
day = date.Day;
month = date.Month;
year = date.Year;
}
}
This is clean, to the point and does exactly what it says on the tin. However we’re back to assigning variables from the property values and can get to being a bit clunky again.
Harnessing the power of Tuples
Using the new Tuples language feature and the power this gives us, and also throwing in a method expression body into the mix, we can tidy up the extension method to be very clean and concise.
public static class DateTimeOffsetExtensions
{
/// <summary>
/// Version #2
/// </summary>
public static void Deconstruct(this DateTimeOffset date, out int day, out int month, out int year) =>
(day, month, year) = (date.Day, date.Month, date.Year);
}
We are doing exactly the same as the previous version but using the Tuple feature to assign the object values into the out variables in one go.
Calling the extension method, the magic!
As with standard extenstion methods this can be called in the following way:
[Fact]
public void Deconstruct()
{
var date = new DateTimeOffset(2018, 10, 23, 0, 0, 0, 0, TimeSpan.Zero);
date.Deconstruct(out int day, out int month, out int year);
Assert.Equal(2018, year);
}
By definition you are extending the DateTimeOffset
type to allow for assigning the values. However due to the fact that the method is called Deconstruct
the compiler knows that it’s special. This allows for our nice deconstruct code to function as required.
[Fact]
public void Deconstruct()
{
var date = new DateTimeOffset(2018, 10, 23, 0, 0, 0, 0, TimeSpan.Zero);
(int day, int month, int year) = date;
Assert.Equal(2018, year);
}
Conclusion
We’ve looked at using the Deconstruct
functionaltity to allow for clean value extraction of the DateTimeOffset
struct. This technique can be applied to any type, whether in the framework, a libarary or of your own design.
I’ve not touched on using discards when using deconstruction however I will leave that up to you to investigate.
Any questions/comments then please contact me on Twitter @WestDiscGolf