After some experimenting, I remembered/found that C# is willing to resolve ambiguities by preferring subclasses over base classes. Thus, to do this with type safety, you need 3 extension methods rather than one. And if you have any other classes which, like Dictionary<TKey, TValue>
, implement both IDictionary<TKey, TValue>
and IReadOnlyDictionary<TKey, TValue>
, you need to write more and more extension methods…</p>
The following makes the compiler perfectly happy. It does not require casting to use the passed dictionary as a dictionary. My strategy is to implement my actual code for the lowest common denominator, IReadOnlyDictionary<TKey, TValue>
, use a façade to adapt IDictionary<TKey, TValue>
to that (because you could be passed an object which doesn’t implement IReadOnlyDictionary<TKey, TValue>
), and, to avoid the method overload resolution ambiguity error, explicitly extend Dictionary<TKey, TValue>
directly.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
// Proof that this makes the compiler happy.
class Program
{
static void Main(string[] args)
{
// This is written to try to demonstrate an alternative,
// type-clean way of handling http://stackoverflow.com/q/18641693/429091
var x = "blah".ToDictionary(
c => c,
c => (int)c);
// This would be where the ambiguity error would be thrown,
// but since we explicitly extend Dictionary<TKey, TValue> dirctly,
// we can write this with no issue!
x.WriteTable(Console.Out, "a");
IDictionary<char, int> y = x;
y.WriteTable(Console.Out, "b");
IReadOnlyDictionary<char, int> z = x;
z.WriteTable(Console.Out, "lah");
}
}
// But getting compile-time type safety requires so much code duplication!
static class DictionaryExtensions
{
// Actual implementation against lowest common denominator
public static void WriteTable<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dict, TextWriter writer, IEnumerable<TKey> keys)
{
writer.WriteLine("-");
foreach (var key in keys)
// Access values by key lookup to prove that we’re interested
// in the concept of an actual dictionary/map/lookup rather
// than being content with iterating over everything.
writer.WriteLine($"{key}:{dict[key]}");
}
// Use façade/adapter if provided IDictionary<TKey, TValue>
public static void WriteTable<TKey, TValue>(this IDictionary<TKey, TValue> dict, TextWriter writer, IEnumerable<TKey> keys)
=> new ReadOnlyDictionary<TKey, TValue>(dict).StaticCast<IReadOnlyDictionary<TKey, TValue>>().WriteTable(writer, keys);
// Use an interface cast (a static known-safe cast).
public static void WriteTable<TKey, TValue>(this Dictionary<TKey, TValue> dict, TextWriter writer, IEnumerable<TKey> keys)
=> dict.StaticCast<IReadOnlyDictionary<TKey, TValue>>().WriteTable(writer, keys);
// Require known compiletime-enforced static cast http://stackoverflow.com/q/3894378/429091
public static T StaticCast<T>(this T o) => o;
}
The most annoying thing with this approach is that you have to write so much boilerplate—two thin wrapper extension methods and one actual implementation. However, I think this can be justified because code using the extensions can be kept simple and clear. The ugliness is contained in the the extensions class. Also, because the extension implementation is contained in one method, you do not need to worry about updating the overloads when the IReadOnlyDictionary<TKey, TValue>
variant is updated. The compiler will even remind you to update the overloads if you change the parameter types unless you’re adding new parameters with default values.