关于java:C#是否可以给我一个不变的字典?

关于java:C#是否可以给我一个不变的字典?

Does C# have a way of giving me an immutable Dictionary?

C#核心库中是否内置可以为我提供不可变字典的任何内容?

与Java类似的东西:

1
Collections.unmodifiableMap(myMap);

只是为了澄清一下,我并不是要阻止键/值本身被更改,而只是希望字典的结构不会停止更改。 如果任何IDictionary的mutator方法被称为(Add, Remove, Clear),我希望它们能够快速响亮。


不,但是包装器很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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 []设置器。


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

  • C#中的不变性第一部分:不变性的种类
  • C#中的不变性第二部分:一个简单的不变性堆栈
  • C#中的不变性第三部分:协变不变栈
  • C#的不变性第四部分:不可变队列
  • C#第五部分的不变性:LOLZ
  • C#第六部分中的不变性:简单的二叉树
  • C#第七部分的不变性:有关二叉树的更多信息
  • C#第八部分中的不变性:关于二叉树的更多内容
  • C#第九部分的不变性:学术性?加上我的AVL树实现
  • C#第10部分中的不变性:双端队列
  • C#第十一部分中的不变性:可工作的双端队列

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

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


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


除了dbkk的答案外,我希望能够在首次创建ReadOnlyDictionary时使用对象初始化程序。我进行了以下修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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"
        );
    }
}

现在,我可以像这样使用该类:

1
2
3
4
5
6
ReadOnlyDictionary<string, string> Fields =
    new ReadOnlyDictionary<string, string>(2)
        {
            {"hey","now"},
            {"you","there"}
        };

一种解决方法是,从Dictionary中抛出一个新的KeyValuePair列表,以保持原始状态不变。

1
2
3
4
5
6
7
8
9
10
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;

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


我不这么认为。有一种创建只读列表和只读Collection的方法,但我认为没有内置的只读Dictionary。 System.ServiceModel具有ReadOnlyDictinoary实现,但其内部。不过,使用Reflector复制它,或者只是从头开始创建自己的复制,可能并不难。它基本上包装了Dictionary并在调用mutator时引发。


总的来说,最好不要先传递任何字典(如果您不必这样做)。

相反,请创建一个域对象,该对象的接口不提供任何修改字典(包装)的方法。 取而代之的是提供所需的LookUp方法,该方法通过键从字典中检索元素(奖励是,它也比字典更易于使用)。

1
2
3
4
5
6
7
8
9
10
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...};
}

您可以尝试这样的事情:

1
2
3
4
5
6
private readonly Dictionary<string, string> _someDictionary;

public IEnumerable<KeyValuePair<string, string>> SomeDictionary
{
    get { return _someDictionary; }
}

这将消除可变性问题,有利于让您的调用者将其转换为自己的字典:

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

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

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

我在这里找到了C#的AVLTree的Inmutable(不是READONLY)实现的实现。

AVL树在每个操作上的代价都是对数的(不是恒定的),但是仍然很快。

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


没有"开箱即用"的方法。您可以通过派生自己的Dictionary类并实现所需的限制来创建一个。


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

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

用法:

1
2
3
4
5
6
7
8
9
10
using System.Collections.Immutable;

public MyClass {
    private Dictionary<KeyType, ValueType> myDictionary;

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

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

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

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

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

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

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


推荐阅读