IanG on Tap

Ian Griffiths in Weblog Form (RSS 2.0)

Blog Navigation

April (2018)

(1 item)

August (2014)

(1 item)

July (2014)

(5 items)

April (2014)

(1 item)

March (2014)

(1 item)

January (2014)

(2 items)

November (2013)

(2 items)

July (2013)

(4 items)

April (2013)

(1 item)

February (2013)

(6 items)

September (2011)

(2 items)

November (2010)

(4 items)

September (2010)

(1 item)

August (2010)

(4 items)

July (2010)

(2 items)

September (2009)

(1 item)

June (2009)

(1 item)

April (2009)

(1 item)

November (2008)

(1 item)

October (2008)

(1 item)

September (2008)

(1 item)

July (2008)

(1 item)

June (2008)

(1 item)

May (2008)

(2 items)

April (2008)

(2 items)

March (2008)

(5 items)

January (2008)

(3 items)

December (2007)

(1 item)

November (2007)

(1 item)

October (2007)

(1 item)

September (2007)

(3 items)

August (2007)

(1 item)

July (2007)

(1 item)

June (2007)

(2 items)

May (2007)

(8 items)

April (2007)

(2 items)

March (2007)

(7 items)

February (2007)

(2 items)

January (2007)

(2 items)

November (2006)

(1 item)

October (2006)

(2 items)

September (2006)

(1 item)

June (2006)

(2 items)

May (2006)

(4 items)

April (2006)

(1 item)

March (2006)

(5 items)

January (2006)

(1 item)

December (2005)

(3 items)

November (2005)

(2 items)

October (2005)

(2 items)

September (2005)

(8 items)

August (2005)

(7 items)

June (2005)

(3 items)

May (2005)

(7 items)

April (2005)

(6 items)

March (2005)

(1 item)

February (2005)

(2 items)

January (2005)

(5 items)

December (2004)

(5 items)

November (2004)

(7 items)

October (2004)

(3 items)

September (2004)

(7 items)

August (2004)

(16 items)

July (2004)

(10 items)

June (2004)

(27 items)

May (2004)

(15 items)

April (2004)

(15 items)

March (2004)

(13 items)

February (2004)

(16 items)

January (2004)

(15 items)

Blog Home

RSS 2.0

Writing

Programming C# 5.0

Programming WPF

Other Sites

Interact Software

Deconstructing KeyValuePair

Thursday 12 April, 2018, 09:03 AM

.NET’s IDictionary<TKey, TValue> implements IEnumerable<KeyValuePair<TKey, TValue>>, so in C# you may find yourself writing something like this:

foreach (KeyValuePair<int, string> kvp in dictionary)
{
    int key = kvp.Key;
    string value = kvp.Value;

    ... do something with the key and value ...
}

This isn’t horrible, but those two lines of boilerplate at the start of the loop are bit annoying, as is the alternative of writing kvp.Key or kvp.Value every time. Having used languages that don’t make you do this, I now find this surprisingly irritating when returning to C#.

Fortunately, with version 7.0, C# became one of those languages that don’t need this boilerplate. You can now write this:

foreach ((int key, string value) in dictionary)
{
    ... do something with the key and value ...
}

Except this doesn’t always work.

It works if you create a new .NET Core 2.0 application in Visual Studio 2017 update 6 (the latest versions available as I write this). However, if you attempt this with a .NET 4.7.1 application (the latest version of the full .NET Framework as I write this) you will get the following errors:

error CS1061: 'KeyValuePair<int, string>' does not contain a definition for 'Deconstruct' and no extension method 'Deconstruct' accepting a first argument of type 'KeyValuePair<int, string>' could be found (are you missing a using directive or an assembly reference?)

error CS8129: No suitable Deconstruct instance or extension method was found for type 'KeyValuePair<int, string>', with 2 out parameters and a void return type.

In fact, you’ll see this problem with any of these targets:

Deconstruction is certainly available on these platforms—it is a language feature and as such it doesn’t require special runtime support. (Most recent C# features don’t, although some depend on library features. For example, if you use C#’s newish tuple features, you will need access to the ValueTuple types. These are built into the latest framework class libraries, but if you’re running on older versions you can pick them up with an additional NuGet reference.) Deconstruction won’t work for KeyValuePair<,> by default with any of the frameworks listed because right now, the necessary Deconstruct method has only been shipped with .NET Core 2.0.

We can demonstrate that the deconstruction language feature works in some cases on .NET 4.7.1 by mapping the dictionary into a tuple first, and you can deconstruct that just fine:

// Don't actually do this
foreach ((int key, string value) in dictionary.Select(kvp => (kvp.Key, kvp.Value)))
{
    ... do something with the key and value ...
}

Don’t do this in real code, though, because it’s unnecessarily inefficient—you’re projecting the dictionary’s enumeration of its contents through a LINQ Select, just to be able to take advantage of a language feature.

The right way to fix this is to solve the problem that the C# compiler has helpfully described in the error messages. It needs a suitable Deconstruct method. In the framework class library available to .NET Core 2.0 applications, this is built into the KeyValuePair<,> type, but for older versions, we can write an extension method:

static class KvpExtensions
{
    public static void Deconstruct<TKey, TValue>(
        this KeyValuePair<TKey, TValue> kvp,
        out TKey key,
        out TValue value)
    {
        key = kvp.Key;
        value = kvp.Value;
    }
}

With that in place, the first code example now works perfectly well for any version of .NET Core or .NET Standard, and for versions of the full .NET Framework all the way back to 3.0 (and you can even get it working on .NET 2.0 if you jump through the hoop described at https://stackoverflow.com/a/1522611/497397 to enable the use of extension methods on that version).

If you define the extension method’s class in the System.Collections.Generic namespace, then any source file that has a using directive for that namespace will be source code compatible across .NET Core 2.0 and other flavours of .NET. It’s not possible to achieve full compatibility, because there are situations where you can obtain and use a KeyValuePair<,> without explicitly using that namespace. (A method could return one directly, or indirectly in the form of an IDictionary<,>. If you’re in the habit of using var whenever possible, you might well not have such a using directive.) So we can’t perfectly replicate what’s available in .NET Core 2.0, but we can get reasonably close.

So, problem solved, right?

Slightly Unsatisfactory

The solution just shown works, but there are a couple of things I don’t like about it.

First, where exactly do I define this extension method? I could copy it into every project I write, but that feels irksome. So I might be tempted to move it into a private library so I can define this once and then use it across multiple projects. Or I might get more ambitious and create a NuGet package. But what if someone else does the same? There’s a risk of ending up with multiple conflicting implementations of this extension method as a result of indirect NuGet dependencies. Ideally Microsoft would define The One True NuGet package that does this, but apparently they haven’t.

Second, it somehow feels wrong that if I write a .NET Standard library, there’s no way to use the built-in Deconstruct method when it is available. I want to be able to use deconstruction, and have it bind to the ‘real’ version when running on .NET Core 2.0 or later, falling back to a substitute otherwise. I’d also like to believe that one day the proper implementation in .NET Core 2.0 will be ported back to the full .NET Framework (although it doesn’t appear to be coming in .NET 4.7.2) and I’d like my hypothetical .NET Standard library to pick that up too if that eventually happens. But I don’t believe it’s technically possible to write a .NET Standard library that automatically uses the preferred method when available but the fall-back otherwise. You’d need more than type forwarding: you’d need some way for an assembly to be able to define a static method in a way that forwards to a method on some completely different type. (It can do that by simply calling the method of course, but that’s not quite the same as type forwarding, where the runtime knows that a specific thing the code refers to should be replaced with a different thing at JIT compilation time.)

The first issue means I wouldn’t strive for almost-source-compatibility as described in the preceding section. In practice I’d put the class in a distinctive namespace to avoid conflicts in the case where someone else has had the same idea. And as for the second issue, well, it’s really not that big a deal. The cost of not using the ‘proper’ method is that you might cause the runtime to load one extra type that it didn’t strictly need to—not ideal but not exactly a big deal either. I don’t like it, but I’m going to have to live with it (as the German chancellor didn’t say).

Copyright © 2002-2024, Interact Software Ltd. Content by Ian Griffiths. Please direct all Web site inquiries to webmaster@interact-sw.co.uk