将嵌套字典转换为IReadOnlyDictionary

问题描述 投票:1回答:3

我试图给出IReadOnly引用内部Collection对象。这在大多数情况下效果很好,但如果我想将包含集合的字典转换为包含IReadOnlyDictionaryIReadOnlyCollection则不行。

这是一个代码示例:

    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
3个回答
4
投票

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. 字典的值,即只读列表,指的是原始列表,并且在字典中的只读版本中也更新了更改。

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

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


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

以及这些例子的变化。更糟糕的是,您将此实现细节暴露给您的类用户。如果此细节发生变化,那么您将破坏所有代码。例如,如果你想用List<int>替换HashSet<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);
}

只需在IReadOnlyMultiMap<TKey, TValue>中实现MultiMap<TKey, TValue>并返回一个你无所事事的只读集合(虚构的例子):

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

要返回只读视图,您需要执行以下操作:

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

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


0
投票

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

所以你总是需要某种解决方法(MultiMap方法在我看来也是一个......)。如果nestedDictionary是私有的,那么你可以从你的班级完全控制它,你可能会逃避这个:

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

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

编辑:只是一个想法,没有经过测试,但它可能有效:有你自己的字典添加丢失的接口可分配给只读字典:

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

我可能还错过了要实现的接口,您可能还需要实现一些成员。如果它有效,可能是最干净的解决方案......

© www.soinside.com 2019 - 2024. All rights reserved.