【问题标题】:Converting a nested dictionary to IReadOnlyDictionary将嵌套字典转换为 IReadOnlyDictionary
【发布时间】:2016-05-20 08:08:56
【问题描述】:

我正在尝试向内部 Collection 对象提供 IReadOnly-references。 这在大多数情况下效果很好,但如果我想将包含集合的字典转换为包含 IReadOnlyCollectionIReadOnlyDictionary,则不会。

这里是一个代码示例:

    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 的值发生变化时都需要手动更新。

有没有简单的方法来转换这种嵌套字典?

【问题讨论】:

    标签: c# dictionary collections .net-4.5 readonly


    【解决方案1】:

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

    虽然我不建议这样做,但您可以使用以下 LINQ 表达式将字典的值转换为只读列表:

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

    这是您的解决方法的一个较短版本,它是惰性评估的,但由于以下原因,我不建议这样做:

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

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

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

    【讨论】:

    • 我编辑了答案以更好地描述为什么使用 LINQ 的解决方案是不好的做法。
    【解决方案2】:

    在我看来,关键是您错过了介绍具有自己尊严的适当新型的机会。如果您使用的是Dictionary&lt;int, List&lt;int&gt;&gt;,那么每次您需要插入一个值时,您都会看到这样的代码:

    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);
    

    以及这些示例的变体。更糟糕的是,您将这个实现细节暴露给您的班级用户。如果这个细节会改变,那么你将破坏所有代码。例如,如果您想将List&lt;int&gt; 替换为HashSet&lt;int&gt;


    应该怎么样?

    _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&lt;TKey, TValue&gt;

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

    只需在MultiMap&lt;TKey, TValue&gt; 中实现IReadOnlyMultiMap&lt;TKey, TValue&gt; 并返回一个您无事可做的只读集合(虚构示例):

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

    请注意,您可能需要引入一个新的ReadOnlyMultiMap&lt;TKey, TValue&gt; 来隧道读取调用底层实时集合(以避免调用者简单地转换为MultiMap&lt;TKey, TValue&gt; 以规避只读限制)。概念证明:

    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;
    }
    

    要返回只读视图,您可以:

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

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

    【讨论】:

    • 虽然我强调您热衷于讨论代码设计,但这显然在这种情况下没有帮助。提供的代码是一个示例,您的答案缺少对潜在问题的解释(请参阅 Philip 的答案)
    • ...然而,它没有提供任何解决方案(因为 LINQ 字典是分离的,而不是您要求的live) .对您的问题的回答(当然是 IMO)是改变您的设计(无论是从哲学 POV 还是作为实际解决方案)。那说E.L.文章总是很好的参考,我不是在这里讨论你的反对意见和你自己的意见......
    • Philip 的解决方案不是他的解决方法,而是有关重要 C# 限制的信息
    • 这是毫无意义的辩论,但是:如果您的问题是 "为什么我不能这样做?" 那么我会同意(而且我' d 投票关闭此帖子作为另一个帖子的副本,顺便说一句)。但是,您正在寻求解决方案(这不仅仅是“因为 C# 而无法解决”)。 IMO,当然,我必须尊重您的意见。
    【解决方案3】:

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

    因此,您总是需要某种解决方法(MultiMap 方法在我看来也是一种方法......)。 如果 nestedDictionary 是私有的,所以你可以从你的课堂上完全控制它,你可能会侥幸逃脱:

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

    每当修改字典中的列表时,都会对List&lt;int&gt; 进行强制转换。另一个丑陋的解决方法,我承认,但为您节省了额外的内存和冗余管理,并保留了IReadOnlyDictionary&lt;int, IReadOnlyList&lt;int&gt;&gt; 的(假设...)公共接口。

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

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

    我可能还错过了要实现的接口,而您可能还必须实现一些成员。如果可行,可能是最干净的解决方案...

    【讨论】:

      猜你喜欢
      • 2020-09-03
      • 2013-12-07
      • 2022-12-05
      • 1970-01-01
      • 1970-01-01
      • 2013-11-16
      • 2019-10-25
      • 2021-12-10
      相关资源
      最近更新 更多