Last active
April 22, 2023 17:30
-
-
Save asmichi/114d153b76c6a1819abe5cc9e3f0e705 to your computer and use it in GitHub Desktop.
Dictionary<string, List<int>> as IReadOnlyDictionary<string, IReadOnlyList<int>>
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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