Есть ли у C # способ дать мне неизменный словарь? - PullRequest
68 голосов
/ 29 августа 2008

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

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

Collections.unmodifiableMap(myMap);

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

Ответы [ 13 ]

50 голосов
/ 30 августа 2008

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

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 [], если хотите разрешить изменение значений.

26 голосов
/ 29 августа 2008

Насколько я знаю, нет. Но, может быть, вы можете скопировать некоторый код (и узнать много) из этих статей:

Неизменяемость в C # Часть первая: Виды неизменности
Неизменяемость в C # Часть вторая: Простой неизменяемый стек
Неизменяемость в C # Часть третья: ковариантный неизменяемый стек
Неизменяемость в C # Часть четвертая: неизменяемая очередь
Неизменяемость в C # Часть шестая: простое двоичное дерево
Неизменяемость в C # Часть седьмая: подробнее о двоичных деревьях
Неизменяемость в C # Часть восьмая: еще больше о двоичных деревьях
Неизменяемость в C # Часть девятая: Академическая? Плюс моя реализация дерева AVL
Неизменяемость в C # Часть 10. Двусторонняя очередь
Неизменяемость в C # Часть одиннадцатая: рабочая двусторонняя очередь

14 голосов
/ 17 сентября 2012

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

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

3 голосов
/ 24 июня 2011

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

3 голосов
/ 09 июля 2009

Добавляя на ответ 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 голосов
/ 29 августа 2008

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

2 голосов
/ 29 августа 2008

Один из обходных путей может быть, выкинуть новый список 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 голос
/ 15 октября 2013

Как правило, гораздо лучше не передавать словари, если вы этого НЕ ДОЛЖНЫ.

Вместо этого - создайте объект домена с интерфейсом, который не предлагает никаких методов , изменяющим словарь (который он переносит). Вместо этого предлагается обязательный 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 голос
/ 21 декабря 2011

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

private readonly Dictionary<string, string> _someDictionary;

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

Это позволило бы устранить проблему изменчивости, так как вызывающему абоненту пришлось бы либо преобразовать его в собственный словарь:

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

... или используйте операцию сравнения на ключе, а не поиск по индексу, например ::100100

foo.SomeDictionary.First(kvp => kvp.Key == "SomeKey");
1 голос
/ 15 сентября 2009

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

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

using System.Linq;
// (...)
var dictionary = new Dictionary<string, object>();
// (...)
var read_only = dictionary.ToLookup(kv => kv.Key, kv => kv.Value);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...