C# 有办法给我一个不可变的字典吗?

Posted

技术标签:

【中文标题】C# 有办法给我一个不可变的字典吗?【英文标题】:Does C# have a way of giving me an immutable Dictionary? 【发布时间】:2010-09-07 07:01:58 【问题描述】:

核心 C# 库中是否有任何东西可以给我一个不可变的字典?

类似于 Java 的

Collections.unmodifiableMap(myMap);

澄清一下,我不想阻止键/值本身被改变,只是字典的结构。如果调用 IDictionary 的任何 mutator 方法 (Add, Remove, Clear),我想要一些快速而响亮的失败。

【问题讨论】:

似乎 ReadOnlyDictionary<TKey,TValue> 将添加到 .Net 4.5 中,与自 .Net 2.0 msdn.microsoft.com/en-us/magazine/jj133817.aspx 以来一直存在的 ReadOnlyCollection<T> 平行 【参考方案1】:

不,但是包装器相当简单:

public class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>

    IDictionary<TKey, TValue> _dict;

    public ReadOnlyDictionary(IDictionary<TKey, TValue> backingDict)
    
        _dict = backingDict;
    

    public void Add(TKey key, TValue value)
    
        throw new InvalidOperationException();
    

    public bool ContainsKey(TKey key)
    
        return _dict.ContainsKey(key);
    

    public ICollection<TKey> Keys
    
        get  return _dict.Keys; 
    

    public bool Remove(TKey key)
    
        throw new InvalidOperationException();
    

    public bool TryGetValue(TKey key, out TValue value)
    
        return _dict.TryGetValue(key, out value);
    

    public ICollection<TValue> Values
    
        get  return _dict.Values; 
    

    public TValue this[TKey key]
    
        get  return _dict[key]; 
        set  throw new InvalidOperationException(); 
    

    public void Add(KeyValuePair<TKey, TValue> item)
    
        throw new InvalidOperationException();
    

    public void Clear()
    
        throw new InvalidOperationException();
    

    public bool Contains(KeyValuePair<TKey, TValue> item)
    
        return _dict.Contains(item);
    

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    
        _dict.CopyTo(array, arrayIndex);
    

    public int Count
    
        get  return _dict.Count; 
    

    public bool IsReadOnly
    
        get  return true; 
    

    public bool Remove(KeyValuePair<TKey, TValue> item)
    
        throw new InvalidOperationException();
    

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    
        return _dict.GetEnumerator();
    

    System.Collections.IEnumerator 
           System.Collections.IEnumerable.GetEnumerator()
    
        return ((System.Collections.IEnumerable)_dict).GetEnumerator();
    

显然,如果您想允许修改值,您可以更改上面的 this[] 设置器。

【讨论】:

你没有实现相等性检查,这是不可变数据结构的一个非常重要的特性。 看起来你所做的只是使用标准字典并到处抛出异常......? “不可变”不一定意味着“无用”。恰恰相反。 这里有这么多的怨恨,当海报明确表示“只读”而不是“不可变”时。我认为这个包装可能符合他的需要,因此得分很高。我很高兴人们出来并获得了更多理论知识,但我们不要忽视 OP 实际需要什么。 @Everyone else:如果您像我一样想知道,似乎有些人会区分“只读”和“不可变”。 “不可变”字典可以有一个 Add 方法,该方法返回一个新字典,其中添加了元素,而“只读”字典没有,这是获得有用实例的唯一方法一个“只读”字典就是先构建一个普通的字典,然后为它构造一个“只读”的包装器。 @Stefan:我看到的区别不在于是否有任何方法可以从现有集合中构建新集合。像此答案中的集合具有只读接口:传递此实例的任何人都无法修改它,但不能保证 noone 可以修改它。 (拥有原始backingDict 的人可以修改集合。)另一方面,不可变集合保证不会被任何人修改。【参考方案2】:

据我所知,没有。但也许你可以从这些文章中复制一些代码(并学到很多东西):

Immutability in C# Part One: Kinds of Immutability Immutability in C# Part Two: A Simple Immutable Stack Immutability in C# Part Three: A Covariant Immutable Stack Immutability in C# Part Four: An Immutable Queue Immutability in C# Part Five: LOLZ Immutability in C# Part Six: A Simple Binary Tree Immutability in C# Part Seven: More on Binary Trees Immutability in C# Part Eight: Even More On Binary Trees Immutability in C# Part Nine: Academic? Plus my AVL tree implementation Immutability in C# Part 10: A double-ended queue Immutability in C# Part Eleven: A working double-ended queue

【讨论】:

其中大部分现在是 Microsoft 的官方库,可通过 nuget - Immutable Collections【参考方案3】:

随着 .NET 4.5 的发布,有一个新的 ReadOnlyDictionary 类。您只需将 IDictionary 传递给构造函数即可创建不可变字典。

Here 是一种有用的扩展方法,可用于简化只读字典的创建。

【讨论】:

此外,还有一个来自 Microsoft 的新附加 (nuGet) 库,名为 Immutable collections ImmutableDictionary(来自 Immutable Collections)与此答案中提到的 ReadOnlyDictionary 之间的区别在于,您可以获得一个新的 ImmutableDictionary 以及您想要应用的更改(而不是改变现有对象)。 警告! ReadOnlyDictionary 仅在创建时捕获底层字典的属性值。请参阅codeproject.com/Tips/1103307/… 了解更多信息。【参考方案4】:

添加到dbkk's answer,我希望能够在第一次创建我的 ReadOnlyDictionary 时使用对象初始化器。我做了以下修改:

private readonly int _finalCount;

/// <summary>
/// Takes a count of how many key-value pairs should be allowed.
/// Dictionary can be modified to add up to that many pairs, but no
/// pair can be modified or removed after it is added.  Intended to be
/// used with an object initializer.
/// </summary>
/// <param name="count"></param>
public ReadOnlyDictionary(int count)

    _dict = new SortedDictionary<TKey, TValue>();
    _finalCount = count;


/// <summary>
/// To allow object initializers, this will allow the dictionary to be
/// added onto up to a certain number, specifically the count set in
/// one of the constructors.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void Add(TKey key, TValue value)

    if (_dict.Keys.Count < _finalCount)
    
        _dict.Add(key, value);
    
    else
    
        throw new InvalidOperationException(
            "Cannot add pair <" + key + ", " + value + "> because " +
            "maximum final count " + _finalCount + " has been reached"
        );
    

现在我可以像这样使用这个类了:

ReadOnlyDictionary<string, string> Fields =
    new ReadOnlyDictionary<string, string>(2)
        
            "hey", "now",
            "you", "there"
        ;

【讨论】:

【参考方案5】:

开源PowerCollections 库包括一个只读字典包装器(以及几乎所有其他东西的只读包装器),可通过Algorithms 类上的静态ReadOnly() 方法访问。

【讨论】:

【参考方案6】:

我知道这是一个非常古老的问题,但我不知何故在 2020 年发现了它,所以我想现在有一种创建不可变字典的方法可能值得注意:

https://docs.microsoft.com/en-us/dotnet/api/system.collections.immutable.immutabledictionary.toimmutabledictionary?view=netcore-3.1

用法:

using System.Collections.Immutable;

public MyClass 
    private Dictionary<KeyType, ValueType> myDictionary;

    public ImmutableDictionary<KeyType, ValueType> GetImmutable()
    
        return myDictionary.ToImmutableDictionary();
    

【讨论】:

【参考方案7】:

我不这么认为。有一种方法可以创建只读列表和只读集合,但我认为没有内置的只读字典。 System.ServiceModel 有一个 ReadOnlyDictinoary 实现,但它是内部的。不过,使用 Reflector 复制它或简单地从头开始创建自己的可能不会太难。它基本上包装了一个 Dictionary 并在调用 mutator 时抛出。

【讨论】:

正如answer below by Dylan 中提到的,从.NET 4.5 开始就有一个内置的解决方案。【参考方案8】:

一种解决方法可能是,从字典中抛出一个新的 KeyValuePair 列表以保持原始不变。

var dict = new Dictionary<string, string>();

dict.Add("Hello", "World");
dict.Add("The", "Quick");
dict.Add("Brown", "Fox");

var dictCopy = dict.Select(
    item => new KeyValuePair<string, string>(item.Key, item.Value));

// returns dictCopy;

这样原始字典就不会被修改。

【讨论】:

【参考方案9】:

“开箱即用”没有办法做到这一点。您可以通过派生自己的 Dictionary 类并实现所需的限制来创建一个。

【讨论】:

【参考方案10】:

我在这里找到了用于 C# 的 AVLTree 的不可变(非只读)实现的实现。

AVL 树在每个操作上都有对数(非恒定)成本,但仍然很快。

http://csharpfeeds.com/post/7512/Immutability_in_Csharp_Part_Nine_Academic_Plus_my_AVL_tree_implementation.aspx

【讨论】:

【参考方案11】:

你可以试试这样的:

private readonly Dictionary<string, string> _someDictionary;

public IEnumerable<KeyValuePair<string, string>> SomeDictionary

    get  return _someDictionary; 

这将消除可变性问题,让您的调用者必须将其转换为他们自己的字典:

foo.SomeDictionary.ToDictionary(kvp => kvp.Key);

...或者对键使用比较操作而不是索引查找,例如:

foo.SomeDictionary.First(kvp => kvp.Key == "SomeKey");

【讨论】:

【参考方案12】:

一般来说,最好不要一开始就传递任何字典(如果你不必这样做的话)。

相反 - 创建一个具有不提供任何方法的接口的域对象修改字典(它包装)。而是提供所需的 LookUp 方法,通过键从字典中检索元素(好处是它也比字典更容易使用)。

public interface IMyDomainObjectDictionary 

    IMyDomainObject GetMyDomainObject(string key);


internal class MyDomainObjectDictionary : IMyDomainObjectDictionary 

    public IDictionary<string, IMyDomainObject> _myDictionary  get; set; 
    public IMyDomainObject GetMyDomainObject(string key)         .._myDictionary .TryGetValue..etc...;

【讨论】:

【参考方案13】:

从 Linq 开始,就有了一个通用接口 ILookup。 在MSDN 中阅读更多内容。

因此,要简单地获取不可变字典,您可以调用:

using System.Linq;
// (...)
var dictionary = new Dictionary<string, object>();
// (...)
var read_only = dictionary.ToLookup(kv => kv.Key, kv => kv.Value);

【讨论】:

这不一样,因为查找会将键映射到值的列表。你可以只输入一个值,但如果你要在没有编译时支持的情况下做,你也可以不写字典。【参考方案14】:

还有另一种选择,正如我所描述的那样:

http://www.softwarerockstar.com/2010/10/readonlydictionary-tkey-tvalue/

本质上它是 ReadOnlyCollection> 的子类,它以更优雅的方式完成工作。优雅的意义在于它具有编译时支持,可以将 Dictionary 设为只读,而不是从修改其中项目的方法中抛出异常。

【讨论】:

您的实现将输入 Dictionary 展平为 List,然后执行查询以在展平列表中搜索 Key。该查询现在不是在扁平列表上以线性时间(慢)运行,而不是在原始字典的对数时间(快)中运行吗? @dthrope,从技术上讲,你是对的,但除非你的字典有成千上万的项目,内存中的字典可能不是一个合适的数据结构,它真的有区别吗在当今世界,机器运行多个四核处理器和千兆字节内存?是的,例如,搜索可能需要 1.01 毫秒而不是 1.0 毫秒,但这是否需要更复杂的设计?在大多数情况下,答案是否定的。 虽然我反对过早优化,但如果开发人员从工具箱中选择字典,则期望内部实现使用哈希表。

以上是关于C# 有办法给我一个不可变的字典吗?的主要内容,如果未能解决你的问题,请参考以下文章

线程安全集合

Objective - C NSDictionary不可变字典和NSMutableDictionary可变字典

iosOC不可变字典和可变字典

c#多键字典,用自己的可变类替换元组时出现“KeyNotFoundException”[重复]

Python 为啥list不能作为字典的key

是否存在C#的开源不可变字典,具有快速“With / Without”方法?