将嵌套字典转换为 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>);
这是您的解决方法的较短版本,并且是惰性评估,但由于以下原因我不推荐这样做:
- 此解决方案仍会创建字典的副本,并且不会更新原始字典中的任何 new/deleted 条目。
- 字典的值,即只读列表,是指原始列表,并且在字典的只读版本中更新了其中的更改。
这是一种不一致的行为,因此是一种不好的做法!
除非无法转换字典的值,否则我不建议这样做。您应该深度复制包括嵌套列表在内的整个字典,或者使用其他支持转换的容器。
在我看来,关键是你错过了引入一个有尊严的新类型的机会。如果您使用 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 您应该引入一个新的(正确命名的)类型来描述 它包含什么,而不是存储是如何实现的。它可能是 MeasureCollection
、SoccerScoreCollection
或您的模型所谈论的任何内容,存储可能会有所不同,但 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>>
{
}
我可能还没有实现某个接口,您可能还必须实现一些成员。如果有效,可能是最干净的解决方案...
我正在尝试给出一个 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>);
这是您的解决方法的较短版本,并且是惰性评估,但由于以下原因我不推荐这样做:
- 此解决方案仍会创建字典的副本,并且不会更新原始字典中的任何 new/deleted 条目。
- 字典的值,即只读列表,是指原始列表,并且在字典的只读版本中更新了其中的更改。
这是一种不一致的行为,因此是一种不好的做法!
除非无法转换字典的值,否则我不建议这样做。您应该深度复制包括嵌套列表在内的整个字典,或者使用其他支持转换的容器。
在我看来,关键是你错过了引入一个有尊严的新类型的机会。如果您使用 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 您应该引入一个新的(正确命名的)类型来描述 它包含什么,而不是存储是如何实现的。它可能是 MeasureCollection
、SoccerScoreCollection
或您的模型所谈论的任何内容,存储可能会有所不同,但 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 class MyDictionary
: Dictionary<int, List<int>>,
ICollection<KeyValuePair<int, IReadOnlyList<int>>,
IEnumerable<KeyValuePair<int, IReadOnlyList<int>>,
IReadOnlyCollection<KeyValuePair<int, IReadOnlyList<int>>
{
}
我可能还没有实现某个接口,您可能还必须实现一些成员。如果有效,可能是最干净的解决方案...