【问题标题】:Converting Dictionary<TKey, List<TValue>> to ReadOnlyDictionary<TKey, ReadOnlyCollection<TValue>>将 Dictionary<TKey, List<TValue>> 转换为 ReadOnlyDictionary<TKey, ReadOnlyCollection<TValue>>
【发布时间】:2016-06-14 12:48:57
【问题描述】:

我有一本字典如下:

public enum Role { Role1, Role2, Role3, }
public enum Action { Action1, Action2, Action3, }

var dictionary = new Dictionary<Role, List<Action>>();

dictionary.Add(RoleType.Role1, new Action [] { Action.Action1, Action.Action2 }.ToList());

现在我希望能够构造一个只读字典,其值类型也是只读的,如下所示:

var readOnlyDictionary = new ReadOnlyDictionary<Role, ReadOnlyCollection<Action>>(dictionary);

由于TValue 类型的差异,最后一行显然会导致编译时错误。

使用List&lt;TValue&gt; 也是必要的,因为原始字典是以编程方式从外部源填充的。

有没有简单的方法来执行这种转换?

【问题讨论】:

  • 你知道像列表这样的集合也可以使用集合初始化器来初始化吗? ;P
  • @MatíasFidemraizer:是的,我做到了。但我需要在运行时构建只读集合。
  • 不,我建议这样做是因为您从数组构建列表...
  • @MatíasFidemraizer:我的错。这确实具有误导性。
  • 为什么? new List&lt;int&gt; { 1, 2, 3 } 怎么了?

标签: c# .net dictionary readonly-collection


【解决方案1】:

将字典转换为 ReadOnlyDictionary 就像将常规字典作为参数传递给 ReadOnlyDictionary 的构造函数一样简单:

var myDict = new Dictionary<K, V>();

var myReadOnlyDict = new ReadOnlyDictionary<K, V>(myDictionary);

【讨论】:

    【解决方案2】:

    一种可能性(对于大型集合可能不是最理想的)是构造所需类型的新Dictionary 对象(使用Enumerable.ToDictionary overload)并像这样使用List.AsReadOnly() extension

    var readOnlyDictionary = 
        new ReadOnlyDictionary<Role, ReadOnlyCollection<Action>>
            (dictionary.ToDictionary(k => k.Key, v => v.Value.AsReadOnly()));
    

    【讨论】:

    • 啊!谢谢。 ToDictionary 转换效果很好。
    • 很高兴这个答案有帮助。
    【解决方案3】:

    近 5 年后,这里有一个简单的解决方案,它适应字典,正确处理值类型的协方差。

    /// <summary>
    /// Provides a <see cref="AsReadOnly{TKey, TValue, TReadOnlyValue}(IDictionary{TKey, TValue})"/>
    /// method on any generic dictionary.
    /// </summary>
    public static class DictionaryExtension
    {
        class ReadOnlyDictionaryWrapper<TKey, TValue, TReadOnlyValue> : IReadOnlyDictionary<TKey, TReadOnlyValue>
                where TValue : TReadOnlyValue
                where TKey : notnull
        {
            private IDictionary<TKey, TValue> _dictionary;
    
            public ReadOnlyDictionaryWrapper( IDictionary<TKey, TValue> dictionary )
            {
                if( dictionary == null ) throw new ArgumentNullException( nameof(dictionary) );
                _dictionary = dictionary;
            }
            public bool ContainsKey( TKey key ) => _dictionary.ContainsKey( key );
    
            public IEnumerable<TKey> Keys => _dictionary.Keys;
    
            public bool TryGetValue( TKey key, [MaybeNullWhen( false )]out TReadOnlyValue value )
            {
                var r = _dictionary.TryGetValue( key, out var v );
                value = v!;
                return r;
            }
    
            public IEnumerable<TReadOnlyValue> Values => _dictionary.Values.Cast<TReadOnlyValue>();
    
            public TReadOnlyValue this[TKey key] => _dictionary[key];
    
            public int Count => _dictionary.Count;
    
            public IEnumerator<KeyValuePair<TKey, TReadOnlyValue>> GetEnumerator() => _dictionary.Select( x => new KeyValuePair<TKey, TReadOnlyValue>( x.Key, x.Value ) ).GetEnumerator();
    
            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
        }
    
        /// <summary>
        /// Creates a wrapper on a dictionary that adapts the type of the values.
        /// </summary>
        /// <typeparam name="TKey">The dictionary key.</typeparam>
        /// <typeparam name="TValue">The dictionary value.</typeparam>
        /// <typeparam name="TReadOnlyValue">The base type of the <typeparamref name="TValue"/>.</typeparam>
        /// <param name="this">This dictionary.</param>
        /// <returns>A dictionary where values are a base type of this dictionary.</returns>
        public static IReadOnlyDictionary<TKey, TReadOnlyValue> AsReadOnly<TKey, TValue, TReadOnlyValue>( this IDictionary<TKey,TValue> @this )
            where TValue : TReadOnlyValue
            where TKey : notnull
        {
            return new ReadOnlyDictionaryWrapper<TKey, TValue, TReadOnlyValue>( @this );
        }
    }
    

    【讨论】: