Есть ли у C# способ дать мне неизменяемый словарь?


есть ли что-нибудь встроенное в основные библиотеки C#, которые могут дать мне неизменяемый словарь?

что-то вроде Java:

Collections.unmodifiableMap(myMap);

и просто чтобы уточнить, я не собираюсь останавливать сами ключи / значения от изменения, просто структура словаря. Я хочу что-то, что не работает быстро и громко, если какой-либо из методов мутатора IDictionary вызывается (Add, Remove, Clear).

13 66

13 ответов:

нет, но обертка довольно тривиальна:

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();
    }
}

очевидно, что вы можете изменить этот[] сеттер выше, если вы хотите разрешить изменение значений.

С выпуском .NET 4.5, есть новый ReadOnlyDictionary класса. Вы просто передаете IDictionary конструктору для создания неизменяемого словаря.

здесь является полезным методом расширения, который может быть использован для упрощения создания словаря только для чтения.

Я так не думаю. Есть способ создать список только для чтения и коллекцию только для чтения, но я не думаю, что есть встроенный словарь только для чтения. Система.ServiceModel имеет реализацию ReadOnlyDictinoary,но ее внутреннюю. Вероятно, было бы не слишком сложно скопировать его, используя отражатель, или просто создать свой собственный с нуля. Он в основном обертывает словарь и бросает, когда вызывается мутатор.

добавляем ответ 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"}
        };

С открытым исходным кодом PowerCollections библиотека включает в себя только для чтения словарь обертку (а также только для чтения обертки для почти все остальное), доступный через статический ReadOnly() метод Algorithms класса.

один обходной путь может быть, бросить новый список 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;

таким образом, исходный словарь не будет изменен.

"из коробки" нет способа сделать это. Вы можете создать его, создав свой собственный класс словаря и реализовав необходимые ограничения.

Я нашел реализацию Inmutable (не только для чтения) реализации AVLTree для C# здесь.

дерево AVL имеет логарифмическую (не постоянную) стоимость для каждой операции, но быстро останавливается.

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

начиная с Linq, существует общий интерфейс дополнительные инфекций, таких как. Подробнее читайте в MSDN.

поэтому, чтобы просто получить неизменяемый словарь, вы можете вызвать:

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

вы могли бы попробовать что-то вроде этого:

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

В общем, гораздо лучше не передавать какие-либо словари в первую очередь (если вам это не нужно).

вместо этого - создать домен-объект с интерфейсом, который не предлагает никаких методов модификации словарь (который он обертывает). Вместо этого предлагается обязательный поиск-метод, который извлекает элемент из словаря по ключу (бонус-это делает его проще в использовании, чем словарь).

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...};
}

есть и другая альтернатива, как я описал в:

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

по существу, это подкласс ReadOnlyCollection>, который выполняет работу более элегантно. Элегантный в том смысле, что он имеет поддержку времени компиляции для создания словаря только для чтения, а не бросать исключения из методов, которые изменяют элементы в нем.