Есть ли общедоступный словарь для чтения, доступный в .NET?

Я возвращаю ссылку на словарь в свойстве только для чтения. Как запретить пользователям изменять мои данные? Если бы это был 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

Ответ 1

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

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

Ответ 2

.NET 4.5

.NET Framework 4.5 BCL представляет ReadOnlyDictionary<TKey, TValue> (source).

Так как .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 framework, который обертывает Dictionary<TKey, TValue>, как ReadOnlyCollection обертывает List. Однако создать его нетрудно.

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

Ответ 3

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

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

Ответ 4

Не стесняйтесь использовать мою простую оболочку. Он НЕ реализует 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(); }
}

Ответ 5

IsReadOnly on IDictionary<TKey,TValue> наследуется от ICollection<T> (IDictionary<TKey,TValue> extends ICollection<T> как ICollection<KeyValuePair<TKey,TValue>>). Он не используется и не реализуется каким-либо образом (и фактически "скрыт" посредством использования явно реализующих связанных членов ICollection<T>).

Есть как минимум три способа решения проблемы:

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

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

Ответ 6

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

public class MyClass
{
  private Dictionary<string, string> _myDictionary;

  public string this[string index]
  {
    get { return _myDictionary[index]; }
  }
}

Ответ 10

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

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

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

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

Ответ 12

Вы можете создать класс, который реализует только частичную реализацию словаря и скрывает все функции add/remove/set.

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

Однако, поскольку ваш словарь, вероятно, содержит ссылочные типы, вы не можете запретить пользователю устанавливать значения в классах, хранящихся в словаре (если только эти классы не читаются только)

Ответ 13

public IEnumerable<KeyValuePair<string, string>> MyDictionary()
{
    foreach(KeyValuePair<string, string> item in _mydictionary)
        yield return item;
}

Ответ 14

Это плохое решение, см. ниже.

Для тех, кто по-прежнему использует .NET 4.0 или ранее, у меня есть класс, который работает так же, как и в принятом ответе, но он намного короче. Он расширяет существующий объект Dictionary, переопределяя (фактически скрывая) определенные члены, чтобы заставить их вызывать исключение при вызове.

Если вызывающий пытается вызвать Add, Remove или какую-либо другую операцию мутирования, которую использует встроенный словарь, компилятор выдает ошибку. Я использую устаревшие атрибуты для повышения этих ошибок компилятора. Таким образом, вы можете заменить словарь этим ReadOnlyDictionary и сразу увидеть, какие проблемы могут возникнуть без необходимости запуска приложения и ожидания исключений во время выполнения.

Посмотрите:

public class ReadOnlyException : Exception
{
}

public class ReadOnlyDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
        : base(dictionary) { }

    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
        : base(dictionary, comparer) { }

    //The following four constructors don't make sense for a read-only dictionary

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary() { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary(IEqualityComparer<TKey> comparer) { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary(int capacity) { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary(int capacity, IEqualityComparer<TKey> comparer) { throw new ReadOnlyException(); }


    //Use hiding to override the behavior of the following four members
    public new TValue this[TKey key]
    {
        get { return base[key]; }
        //The lack of a set accessor hides the Dictionary.this[] setter
    }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public new void Add(TKey key, TValue value) { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public new void Clear() { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public new bool Remove(TKey key) { throw new ReadOnlyException(); }
}

Это решение имеет проблему, обозначенную здесь @supercat:

var dict = new Dictionary<int, string>
{
    { 1, "one" },
    { 2, "two" },
    { 3, "three" },
};

var rodict = new ReadOnlyDictionary<int, string>(dict);
var rwdict = rodict as Dictionary<int, string>;
rwdict.Add(4, "four");

foreach (var item in rodict)
{
    Console.WriteLine("{0}, {1}", item.Key, item.Value);
}

Вместо того, чтобы давать ошибку времени компиляции, как я ожидал, или исключение среды выполнения, как я надеялся, этот код работает без ошибок. Он печатает четыре числа. Это делает мой ReadOnlyDictionary a readWriteDictionary.