В .NET доступен общий словарь только для чтения? - PullRequest
185 голосов
/ 24 марта 2009

Я возвращаю ссылку на словарь в своем свойстве только для чтения. Как я могу запретить потребителям изменять мои данные? Если бы это был IList, я мог бы просто вернуть его AsReadOnly. Есть ли что-то подобное, что я могу сделать со словарем?

Private _mydictionary As Dictionary(Of String, String)
Public ReadOnly Property MyDictionary() As Dictionary(Of String, String)
    Get
        Return _mydictionary
    End Get
End Property

Ответы [ 14 ]

230 голосов
/ 24 марта 2009

.NET 4.5

.NET Framework 4.5 BCL представляет ReadOnlyDictionary<TKey, TValue> ( источник ).

Поскольку в .NET Framework 4.5 BCL отсутствует словарь AsReadOnly для словарей, вам необходимо написать собственный (если вы этого хотите). Это будет что-то вроде следующего, простота которого, возможно, подчеркивает, почему это не было приоритетом для .NET 4.5.

public static ReadOnlyDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(
    this IDictionary<TKey, TValue> dictionary)
{
    return new ReadOnlyDictionary<TKey, TValue>(dictionary);
}

.NET 4.0 и ниже

До .NET 4.5 не было класса платформы .NET, который переносит Dictionary<TKey, TValue>, как ReadOnlyCollection , упаковывает Список . Однако его не сложно создать.

Вот пример - есть много других, если вы Google для ReadOnlyDictionary .

156 голосов
/ 13 августа 2009

Вот простая реализация, которая переносит словарь:

public class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    private readonly IDictionary<TKey, TValue> _dictionary;

    public ReadOnlyDictionary()
    {
        _dictionary = new Dictionary<TKey, TValue>();
    }

    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
    {
        _dictionary = dictionary;
    }

    #region IDictionary<TKey,TValue> Members

    void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
    {
        throw ReadOnlyException();
    }

    public bool ContainsKey(TKey key)
    {
        return _dictionary.ContainsKey(key);
    }

    public ICollection<TKey> Keys
    {
        get { return _dictionary.Keys; }
    }

    bool IDictionary<TKey, TValue>.Remove(TKey key)
    {
        throw ReadOnlyException();
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        return _dictionary.TryGetValue(key, out value);
    }

    public ICollection<TValue> Values
    {
        get { return _dictionary.Values; }
    }

    public TValue this[TKey key]
    {
        get
        {
            return _dictionary[key];
        }
    }

    TValue IDictionary<TKey, TValue>.this[TKey key]
    {
        get
        {
            return this[key];
        }
        set
        {
            throw ReadOnlyException();
        }
    }

    #endregion

    #region ICollection<KeyValuePair<TKey,TValue>> Members

    void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
    {
        throw ReadOnlyException();
    }

    void ICollection<KeyValuePair<TKey, TValue>>.Clear()
    {
        throw ReadOnlyException();
    }

    public bool Contains(KeyValuePair<TKey, TValue> item)
    {
        return _dictionary.Contains(item);
    }

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

    public int Count
    {
        get { return _dictionary.Count; }
    }

    public bool IsReadOnly
    {
        get { return true; }
    }

    bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
    {
        throw ReadOnlyException();
    }

    #endregion

    #region IEnumerable<KeyValuePair<TKey,TValue>> Members

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        return _dictionary.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion

    private static Exception ReadOnlyException()
    {
        return new NotSupportedException("This dictionary is read-only");
    }
}
19 голосов
/ 22 сентября 2011

На недавней конференции BUILD было объявлено, что начиная с .NET 4.5 включен интерфейс System.Collections.Generic.IReadOnlyDictionary<TKey,TValue>. Доказательство: здесь (моно) и здесь (Microsoft);)

Не уверен, включен ли ReadOnlyDictionary, но, по крайней мере, с интерфейсом не должно быть затруднений создать реализацию, которая предоставляет официальный общий интерфейс .NET:)

18 голосов
/ 02 августа 2011

Не стесняйтесь использовать мою простую обертку. Он НЕ реализует IDictionary, поэтому он не должен генерировать исключения во время выполнения для методов словаря, которые могли бы изменить словарь. Методы изменения просто отсутствуют. Я сделал свой собственный интерфейс для этого, названный IReadOnlyDictionary.

public interface IReadOnlyDictionary<TKey, TValue> : IEnumerable
{
    bool ContainsKey(TKey key);
    ICollection<TKey> Keys { get; }
    ICollection<TValue> Values { get; }
    int Count { get; }
    bool TryGetValue(TKey key, out TValue value);
    TValue this[TKey key] { get; }
    bool Contains(KeyValuePair<TKey, TValue> item);
    void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex);
    IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator();
}

public class ReadOnlyDictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
{
    readonly IDictionary<TKey, TValue> _dictionary;
    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
    {
        _dictionary = dictionary;
    }
    public bool ContainsKey(TKey key) { return _dictionary.ContainsKey(key); }
    public ICollection<TKey> Keys { get { return _dictionary.Keys; } }
    public bool TryGetValue(TKey key, out TValue value) { return _dictionary.TryGetValue(key, out value); }
    public ICollection<TValue> Values { get { return _dictionary.Values; } }
    public TValue this[TKey key] { get { return _dictionary[key]; } }
    public bool Contains(KeyValuePair<TKey, TValue> item) { return _dictionary.Contains(item); }
    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { _dictionary.CopyTo(array, arrayIndex); }
    public int Count { get { return _dictionary.Count; } }
    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return _dictionary.GetEnumerator(); }
    IEnumerator IEnumerable.GetEnumerator() { return _dictionary.GetEnumerator(); }
}
11 голосов
/ 13 августа 2009

IsReadOnly для IDictionary<TKey,TValue> наследуется от ICollection<T> (IDictionary<TKey,TValue> расширяет ICollection<T> как ICollection<KeyValuePair<TKey,TValue>>). Он не используется и не реализуется каким-либо образом (и фактически «скрыт» за счет явной реализации связанных ICollection<T> членов).

Существует как минимум 3 способа решения проблемы:

  1. Реализация пользовательского чтения только IDictionary<TKey, TValue> и завернуть / делегировать во внутренний словарь как было предложено
  2. вернуть ICollection<KeyValuePair<TKey, TValue>> установить только для чтения или IEnumerable<KeyValuePair<TKey, TValue>> в зависимости от использования значение
  3. Клонировать словарь, используя копию конструктор .ctor(IDictionary<TKey, TValue>) и вернуть копию - это способ, которым пользователь свободен делать с ним как им угодно и это не влияние на состояние объекта хостинг исходного словаря. Заметка что если словарь вы клонирование содержит ссылочные типы ( не строки, как показано в примере ) вам нужно будет сделать копирование "вручную" и клонировать ссылку а также типы.

в сторону; при представлении коллекций стремитесь предоставить наименьший возможный интерфейс - в данном примере это должен быть IDictionary, поскольку это позволяет варьировать базовую реализацию, не нарушая публичный контракт, который предоставляет тип.

7 голосов
/ 24 мая 2011

Доступный только для чтения словарь может быть в некоторой степени заменен на Func<TKey, TValue> - я обычно использую это в API, если я только хочу, чтобы люди выполняли поиск; это просто и, в частности, просто заменить бэкэнд, если вы когда-нибудь захотите. Однако он не предоставляет список ключей; имеет ли это значение, зависит от того, что вы делаете.

4 голосов
/ 24 марта 2009

Нет, но было бы легко свернуть свое собственное. IDictionary определяет свойство IsReadOnly. Просто оберните словарь и сгенерируйте NotSupportedException из соответствующих методов.

3 голосов
/ 24 марта 2009

Нет доступных в BCL. Однако я опубликовал ReadOnlyDictionary (названный ImmutableMap) в моем проекте BCL Extras

Помимо того, что он является полностью неизменным словарем, он поддерживает создание прокси-объекта, который реализует IDictionary и может использоваться в любом месте, где берется IDictionary. Он будет выдавать исключение всякий раз, когда один из API-интерфейсов с мутацией называется

void Example() { 
  var map = ImmutableMap.Create<int,string>();
  map = map.Add(42,"foobar");
  IDictionary<int,string> dictionary = CollectionUtility.ToIDictionary(map);
}
1 голос
/ 09 января 2014

Теперь есть Неизменяемые коллекции Microsoft (System.Collections.Immutable). Получить их через NuGet .

1 голос
/ 16 апреля 2012

+ 1 Отличная работа, Томас. Я сделал ReadOnlyDictionary еще на шаг вперед.

Как и решение Дейла, я хотел удалить Add(), Clear(), Remove() и т. Д. Из IntelliSense. Но я хотел, чтобы мои производные объекты реализовали IDictionary<TKey, TValue>.

Кроме того, я хотел бы, чтобы следующий код сломался: (Опять же, решение Дейла делает то же самое)

ReadOnlyDictionary<int, int> test = new ReadOnlyDictionary<int,int>(new Dictionary<int, int> { { 1, 1} });
test.Add(2, 1);  //CS1061

Строка Add () приводит к:

error CS1061: 'System.Collections.Generic.ReadOnlyDictionary<int,int>' does not contain a definition for 'Add' and no extension method 'Add' accepting a first argument 

Вызывающий абонент может все еще привести его к IDictionary<TKey, TValue>, но NotSupportedException будет поднят, если вы попытаетесь использовать не доступные для чтения члены (из решения Томаса).

В любом случае, вот мое решение для тех, кто тоже этого хотел:

namespace System.Collections.Generic
{
    public class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    {
        const string READ_ONLY_ERROR_MESSAGE = "This dictionary is read-only";

        protected IDictionary<TKey, TValue> _Dictionary;

        public ReadOnlyDictionary()
        {
            _Dictionary = new Dictionary<TKey, TValue>();
        }

        public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
        {
            _Dictionary = dictionary;
        }

        public bool ContainsKey(TKey key)
        {
            return _Dictionary.ContainsKey(key);
        }

        public ICollection<TKey> Keys
        {
            get { return _Dictionary.Keys; }
        }

        public bool TryGetValue(TKey key, out TValue value)
        {
            return _Dictionary.TryGetValue(key, out value);
        }

        public ICollection<TValue> Values
        {
            get { return _Dictionary.Values; }
        }

        public TValue this[TKey key]
        {
            get { return _Dictionary[key]; }
            set { throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE); }
        }

        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            return _Dictionary.Contains(item);
        }

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

        public int Count
        {
            get { return _Dictionary.Count; }
        }

        public bool IsReadOnly
        {
            get { return true; }
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return _Dictionary.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return (_Dictionary as IEnumerable).GetEnumerator();
        }

        void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
        {
            throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE);
        }

        bool IDictionary<TKey, TValue>.Remove(TKey key)
        {
            throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE);
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
        {
            throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE);
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Clear()
        {
            throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE);
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
        {
            throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE);
        }
    }
}
...