将嵌套字典转换为 IReadOnlyDictionary

Converting a nested dictionary to IReadOnlyDictionary

我正在尝试给出一个 IReadOnly-对内部 Collection 对象的引用。 这在大多数情况下效果很好,但如果我想将包含集合的字典转换为包含 IReadOnlyCollection.

IReadOnlyDictionary

这是一个代码示例:

    var list = new List<int>();
    IReadOnlyList<int> listReference = list; //works;

    var dictionary = new Dictionary<int, int>();
    IReadOnlyDictionary<int, int> dictionaryReference = dictionary; //works

    var nestedList = new List<List<int>>();
    IReadOnlyList<IReadOnlyList<int>> nestedReadOnlyListReference = nestedList; //works

    var nestedDictionary = new Dictionary<int, List<int>>();
    //IReadOnlyDictionary<int, IReadOnlyList<int>> nestedReadOnlyDictionaryReference = nestedDictionary; //does not work, can not implicitly convert

    //current workaround
    var nestedDictionaryReferenceHelper = new Dictionary<int, IReadOnlyList<int>>();
    foreach (var kvpNestedDictionary in nestedDictionary)
    {
        nestedDictionaryReferenceHelper.Add(kvpNestedDictionary.Key, (IReadOnlyList<int>)kvpNestedDictionary.Value);
    }
    IReadOnlyDictionary<int, IReadOnlyList<int>> nestedReadOnlyDictionaryReference = nestedDictionaryReferenceHelper; //works, but is only a reference to the internal List, not to the dictionary itself

解决方法非常丑陋,因为它需要额外的内存,并且每次 nestedDictionary 的值更改时都需要手动更新。

有什么简单的方法可以转换这种嵌套字典吗?

In this SO question 你可以找到一个很好的解释为什么不支持转换字典值。请参阅 Eric Lippert 接受的答案。

尽管我不推荐这个,你可以使用下面的 LINQ 表达式将字典的值转换为只读名单:

IReadOnlyDictionary<int, IReadOnlyList<int>> nestedReadOnlyDictionaryReference = nestedDictionary.ToDictionary(kv => kv.Key, kv => kv.Value as IReadOnlyList<int>);

这是您的解决方法的较短版本,并且是惰性评估,但由于以下原因我不推荐这样做:

  1. 此解决方案仍会创建字典的副本,并且不会更新原始字典中的任何 new/deleted 条目。
  2. 字典的值,即只读列表,是指原始列表,并且在字典的只读版本中更新了其中的更改。

这是一种不一致的行为,因此是一种不好的做法!

除非无法转换字典的值,否则我不建议这样做。您应该深度复制包括嵌套列表在内的整个字典,或者使用其他支持转换的容器。

在我看来,关键是你错过了引入一个有尊严的新类型的机会。如果您使用 Dictionary<int, List<int>>,那么每次您需要插入一个值时,您都会看到这样的代码:

if (!_dictionary.ContainsKey(key)) {
    var list = new List<int>();
    list.Add(value);
    _dictionary.Add(key, list);
} else {
    _dictionary[key].Add(value);
}

当您想搜索一个值时,使用这样的代码更糟糕:

_dictionary.ContainsKey(key) && _dictionary[key].Contains(value);

以及这些示例的变体。更糟糕的是,您将 实施细节 暴露给 class 用户。如果此细节发生变化,那么您将破坏所有代码。例如,如果您想用 HashSet<int> 替换 List<int> 怎么办?


应该如何?

_multimap.Add(key, value);

有合适的界面(这里我只展示几个方法):

public interface IMultiMap<TKey, TValue> {
    void Add(TKey key, TValue value);
    bool ContainsKey(TKey key);
}

及其实现:

public sealed class MultiMap<TKey, TValue> : IMultiMap<TKey, TValue> {
    // ...

    private Dictionary<int, List<int>> _items;
}

你可以介绍一下IReadOnlyMultiMap<TKey, TValue>:

public interface IReadOnlyMultiMap<TKey, TValue> {
    bool ContainsKey(TKey key);
}

只需在 MultiMap<TKey, TValue> 中实现 IReadOnlyMultiMap<TKey, TValue> 和 return 只读集合,您无需执行任何操作(虚构示例):

IReadOnlyMultiMap<int, int> MakeReadOnly(MultiMap<int, int> map) {
    return map; // Nothing to do!
}

请注意,您可能需要引入一个新的 ReadOnlyMultiMap<TKey, TValue> 来将读取调用传送到底层实时集合(以避免调用者简单地转换为 MultiMap<TKey, TValue> 来规避只读限制)。概念验证:

public sealed class ReadOnlyMultiMap<TKey, TValue> : IReadOnlyMultiMap<TKey, TValue> {
    public ReadOnlyMultiMap(IMultiMap<TKey, TValue> collection) {
        _collection = collection;
    }

    public bool ContainsKey(TKey key) {
        return _collection.ContainsKey(key);
    }

    private readonly IMultiMap<TKey, TValue> _collection;
}

要return一个你做的只读视图:

IReadOnlyMultiMap<int, int> MakeReadOnly(MultiMap<int, int> map) {
    return new ReadOnlyMultiMap<int, int>(map);
}

请注意,我谈到了实施细节。您仍在公开实现细节(您正在使用多图)然后如果此类代码用于 public API 您应该引入一个新的(正确命名的)类型来描述 它包含什么,而不是存储是如何实现的。它可能是 MeasureCollectionSoccerScoreCollection 或您的模型所谈论的任何内容,存储可能会有所不同,但 content 不会。

转换失败的问题是 KeyValuePair: 虽然 class Derived 继承了 class Base,但 KeyValuePair 不是 KeyValuePair 的子 class;见定义(Dictionary, IReadOnlyDictionary)。

所以您总是需要某种解决方法(MultiMap 方法在我看来也是一种方法...)。 如果 nestedDictionary 是私有的,那么您可以通过 class 完全控制它:

var nestedDictionary = new Dictionary<int, IReadOnlyList<int>>();
IReadOnlyDictionary<int, IReadOnlyList<int>> nestedReadOnlyDictionaryReference = nestedDictionary;

并且每当修改字典中的列表时,都会对 List<int> 应用强制转换。另一个丑陋的解决方法,我承认,但可以节省额外的内存和冗余管理,并保留 IReadOnlyDictionary<int, IReadOnlyList<int>>.

的(假设...)public 接口

编辑:只是一个想法,尚未测试,但它可能 工作:让您自己的字典添加缺少的接口以分配给只读字典:

public class MyDictionary
    : Dictionary<int, List<int>>,
      ICollection<KeyValuePair<int, IReadOnlyList<int>>,
      IEnumerable<KeyValuePair<int, IReadOnlyList<int>>, 
      IReadOnlyCollection<KeyValuePair<int, IReadOnlyList<int>>
{
}

我可能还没有实现某个接口,您可能还必须实现一些成员。如果有效,可能是最干净的解决方案...