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


71

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

类似于Java的东西:

Collections.unmodifiableMap(myMap);

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


6
这似乎是ReadOnlyDictionary<TKey,TValue>将在.NET 4.5被添加为平行于ReadOnlyCollection<T>自NET 2.0已经被本msdn.microsoft.com/en-us/magazine/jj133817.aspx
戈登古斯塔夫森

Answers:


51

不,但是包装器很简单:

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 []设置器。


3
您没有实现相等性检查,这对于不可变数据结构是非常重要的功能。
Elazar Leibovich

2
看起来您所做的只是拿一个标准的Dictionary并在各处抛出异常...?“不变”不一定意味着“没用”。恰恰相反。
理查德·伯格

6
当海报清楚地表示“只读”而不是“不可变”时,这里充满了怨恨。我认为这个包装器可能符合他的需求,因此得分很高。我很高兴看到人们出来,并有了一些理论知识,但让我们不要忽视OP的实际需求。
Egor

13
@其他人:如果您想像我一样想知道,似乎有些人在“只读”和“不可变”之间有所不同。“不可变”字典可以具有Add方法,该方法返回添加了元素的新字典,而“只读”字典则没有,这是获取“只读”有用实例的唯一方法dictionary是要构建普通的Dictionary,然后为其构建“只读”包装。
Stefan Monov

5
@Stefan:我所看到的区别不在于是否有任何方法可以从现有集合中构造一个新集合。类似于此答案中的集合的集合具有只读接口:传递给它的实例的任何人都不能对其进行修改,但是不能保证没有人可以对其进行修改。(原始的人backingDict可能会修改集合。)另一方面,保证不可变的集合不会被任何人修改。
Joren

26

16

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

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


1
此外,还有一个来自Microsoft的名为“不可变集合”
sinelaw 2013年

ImmutableDictionary(来自Immutable Collections)与此答案中提到的ReadOnlyDictionary之间的区别在于,您可以使用要应用的更改获得一个新的ImmutableDictionary(而不是对现有对象进行突变)。
sinelaw

警告语!!ReadOnlyDictionary仅在创建基础字典时捕获其属性值。有关更多信息,请参见codeproject.com/Tips/1103307/…
Michael Erickson

3

除了dbkk的答案外,我希望能够在首次创建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"}
        };

3

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


2

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


正如Dylan下面回答中所提到的那样,由于.NET 4.5内置了一个解决方案。
sinelaw

2

一种解决方法是,从Dictionary中抛出一个新的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;

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


1

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



1

您可以尝试这样的事情:

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

1

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

相反,请创建一个域对象,该对象的接口不提供任何修改字典(包装)的方法。取而代之的是提供所需的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...};
}

1

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

https://docs.microsoft.com/zh-cn/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();
    }
}

0

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

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

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

4
这是不一样的,因为查找将键映射到值列表。您只可以输入一个值,但是如果您在没有编译时支持的情况下也不能写入字典。
实例猎人

-1

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

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

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


3
您的实现将输入的Dictionary展平为一个列表,然后执行查询以在展平的列表中搜索Key。该查询现在不是在平化列表上以线性时间(慢)运行,而不是以原始字典的对数时间(快速)运行吗?
dthorpe

@dthrope,是的,从技术上来说是正确的,但是除非字典中包含成千上万的项目,否则内存中的字典无论如何都不适合作为数据结构,在当今世界上运行多个机器的计算机确实会有所作为吗?四核处理器和千兆字节的内存?是的,例如,搜索可能需要1.01毫秒而不是1.0毫秒,但这是否需要更复杂的设计?在大多数情况下,答案是否定的。
SoftwareRockstar 2010年

1
虽然我反对过早的优化,但是如果开发人员从工具箱中选择字典,则期望内部实现使用哈希表。
TrueWill 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.