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