Skip to content

Instantly share code, notes, and snippets.

@asmichi
Last active April 22, 2023 17:30
Show Gist options
  • Save asmichi/114d153b76c6a1819abe5cc9e3f0e705 to your computer and use it in GitHub Desktop.
Save asmichi/114d153b76c6a1819abe5cc9e3f0e705 to your computer and use it in GitHub Desktop.
Dictionary<string, List<int>> as IReadOnlyDictionary<string, IReadOnlyList<int>>
// https://blog.kyubuns.dev/entry/2023/04/22/201156
//
// IReadOnlyDictionary<TKey, TValue> は、IEnumerable<KeyValuePair<TKey, TValue>> を
// 実装できるためには invariant でなければならない (いつもの)。 KeyValuePair<TKey, TValue> はもちろん invariant だから。
//
// IReadOnlyDictionary で返さなくてよければもっといろいろできるのだが。
// でもやっぱり標準の型である IReadOnlyDictionary をインターフェースにしたいこともありがちで。
using System.Collections;
using System.Diagnostics.CodeAnalysis;
public static class Program
{
public static void Main()
{
var h = new Hoge();
// 1,2
Console.WriteLine(Join(h.PublicDictionary["a"]));
// a: 1,2
// b: 3,4
foreach (var (k, v) in h.PublicDictionary)
{
Console.WriteLine($"{k}: {Join(v)}");
}
// a: 1,2
// b: 3,4
foreach (var k in h.PublicDictionary.Keys)
{
var v = h.PublicDictionary.TryGetValue(k, out var value) ? value : throw new KeyNotFoundException(k);
Console.WriteLine($"{k}: {Join(v)}");
}
// 1,2
// 3,4
foreach (var v in h.PublicDictionary.Values)
{
Console.WriteLine(Join(v));
}
// a
// b
// a
{
var e = h.PublicDictionary.GetEnumerator();
e.MoveNext();
Console.WriteLine(e.Current.Key);
e.MoveNext();
Console.WriteLine(e.Current.Key);
e.Reset();
e.MoveNext();
Console.WriteLine(e.Current.Key);
}
static string Join(IEnumerable<int> v) => string.Join(",", v);
}
}
public sealed class Hoge
{
public IReadOnlyDictionary<string, IReadOnlyList<int>> PublicDictionary { get; }
private readonly Dictionary<string, List<int>> _privateDictionary = new()
{
{ "a", new List<int> { 1, 2 } },
{ "b", new List<int> { 3, 4 } },
};
public Hoge()
{
PublicDictionary = new MyReadOnlyDictionaryWithBaseClassValue<string, List<int>, IReadOnlyList<int>>(_privateDictionary);
}
}
internal sealed class MyReadOnlyDictionaryWithBaseClassValue<TKey, TValue, TValueBase>
: IReadOnlyDictionary<TKey, TValueBase>
where TKey : notnull
where TValue : class, TValueBase
{
private readonly Dictionary<TKey, TValue> _inner;
public MyReadOnlyDictionaryWithBaseClassValue(Dictionary<TKey, TValue> inner) => _inner = inner;
bool IReadOnlyDictionary<TKey, TValueBase>.TryGetValue(
TKey key,
[MaybeNullWhen(false)] out TValueBase value)
{
bool success = _inner.TryGetValue(key, out var innerValue);
value = innerValue;
return success;
}
// ぜんぶこいつ (KeyValuePair) のせい
IEnumerator<KeyValuePair<TKey, TValueBase>> IEnumerable<KeyValuePair<TKey, TValueBase>>.GetEnumerator() =>
new MyEnumerator(_inner.GetEnumerator());
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<KeyValuePair<TKey, TValueBase>>)this).GetEnumerator();
// 移譲
TValueBase IReadOnlyDictionary<TKey, TValueBase>.this[TKey key] => _inner[key];
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValueBase>.Keys => _inner.Keys;
IEnumerable<TValueBase> IReadOnlyDictionary<TKey, TValueBase>.Values => _inner.Values;
int IReadOnlyCollection<KeyValuePair<TKey, TValueBase>>.Count => _inner.Count;
bool IReadOnlyDictionary<TKey, TValueBase>.ContainsKey(TKey key) => _inner.ContainsKey(key);
private sealed class MyEnumerator : IEnumerator<KeyValuePair<TKey, TValueBase>>
{
private Dictionary<TKey, TValue>.Enumerator _inner;
public MyEnumerator(Dictionary<TKey, TValue>.Enumerator enumerator) => _inner = enumerator;
public KeyValuePair<TKey, TValueBase> Current
{
get
{
var innerValue = _inner.Current;
return KeyValuePair.Create<TKey, TValueBase>(innerValue.Key, innerValue.Value);
}
}
object IEnumerator.Current => Current;
public void Dispose() => _inner.Dispose();
public bool MoveNext() => _inner.MoveNext();
public void Reset() => CallReset(ref _inner);
private static void CallReset<T>(ref T e) where T : struct, IEnumerator => e.Reset();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment