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

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

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

Collections.unmodifiableMap(myMap);

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

Похоже, что ReadOnlyDictionary<TKey,TValue> будет добавлен в .Net 4.5 как параллель с ReadOnlyCollection<T>, который присутствует с .Net 2.0 msdn.microsoft.com/en-us/magazine/jj133817.aspx

Gordon Gustafson 14.06.2012 03:25
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
71
1
23 071
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

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

Как упоминалось в ответ ниже Диланом, начиная с .NET 4.5 ЕСТЬ встроенное решение.

sinelaw 15.10.2013 21:59

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

Большая часть этого теперь является официальной библиотекой от Microsoft, доступной через nuget - Неизменяемые коллекции

sinelaw 15.10.2013 22:01

«Из коробки» нет способа сделать это. Вы можете создать его, создав собственный класс 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;

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

Ответ принят как подходящий

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

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

Несколько ошибок: вам нужен «новый» перед всеми InvalidOperationException(), он должен быть public bool Contains(TKey item), а CopyTo не требует return.

Sarah Vessels 08.07.2009 23:23

Упс, вместо изменения типа параметра в Contains измените его тело, чтобы использовать Contains вместо ContainsKey: return _dict.Contains(item);

Sarah Vessels 08.07.2009 23:24

Вы не реализовали проверку на равенство, которая является очень важной функцией для неизменяемых структур данных.

Elazar Leibovich 15.09.2009 10:01

Похоже, все, что вы сделали, это взяли стандартный словарь и повсюду бросали исключения ...? «Неизменяемый» не обязательно означает «бесполезный». Наоборот.

Richard Berg 10.03.2010 06:42

@Ричард. Почему бесполезно обеспечивать неизменность? ReadOnlyCollection делает то же самое. Что бы вы сделали вместо исключения исключений? Предложите альтернативное решение, сказав «наоборот» недостаточно.

dbkk 15.03.2010 17:06

Что-то вроде Bit-Partitioned Hash Trie обеспечит неизменность без полного нарушения ADT, как вы это сделали. Ваш класс утверждает, что реализует IDictionaryно честно говоря это вранье. Просто потому, что компилятор позволяет вам что-то уйти, еще не значит, что это правильно.

Richard Berg 15.03.2010 19:28

@Ричард. AFAIK dbkk - это вполне «стандартный» способ предоставления неизменяемых коллекций как в C#, так и в java: они предоставляют один и тот же интерфейс с той лишь разницей, что некоторые методы генерируют исключения. Таким образом, если для метода требуется словарь или список, он может быть снабжен этими неизменяемыми объектами, поскольку интерфейс совпадает.

chiccodoro 06.05.2010 12:27

Нет причин, по которым BPHT не может реализовать IDictionary. Или для чего-то простого и элегантного просто прокрутите вниз до ответа Серхата. Нелепо, что очень поучительная серия блогов от разработчика C# Эрика Липперта набрала в 2,5 раза меньше голосов, чем эта отговорка.

Richard Berg 11.05.2010 06:04

@Richard Честный вопрос: что будет делать неизменяемая коллекция, если вы вызовете к ней Add? Вы не можете избавиться от функции Add, так как у вас больше нет IDictionary. Должен ли он тихо выйти из строя?

Michael Stum 12.07.2010 23:47

Нет, конечно нет. Ты хоть ответ читал? Вот, свяжу еще раз: blogs.msdn.com/b/ericlippert/archive/2008/01/21/…

Richard Berg 13.07.2010 07:11

Так много злобы здесь, когда плакат явно означал «Только для чтения», а не «Неизменяемый». Думаю, эта обертка, вероятно, соответствовала его потребностям, отсюда и высокий балл. Я рад, что люди пришли и стали немного более теоретическими, но давайте не будем упускать из виду то, что на самом деле нужно OP.

Egor 09.09.2010 05:09

@dbkk: Мне не нравится, что ваше решение нарушает принцип подстановки. Я бы не стал выводить из IDictionary. Я бы сохранил закрытый член IDictionary и переадресовал бы ему все не изменяющиеся вызовы, и я бы не стал предоставлять методы пересылки для изменяющихся вызовов. Таким образом, это безопасно во время компиляции. Да, тогда его нельзя использовать как IDictionary или ICollection, но в этом-то и дело.

Stefan Monov 23.09.2010 12:49

@ Все остальные: если вам интересно, как и мне, кажется, некоторые люди делают разницу между «Только для чтения» и «Неизменяемый». «Неизменяемый» словарь может иметь метод Add, который возвращается - новый словарь с добавленным к нему элементом, тогда как «только для чтения» - нет, и единственный способ получить полезный экземпляр «только для чтения» Dictionary - создать обычный словарь, а затем создать для него оболочку «только для чтения».

Stefan Monov 23.09.2010 12:51

Мой комментарий к dbkk предлагает лучший способ создать словарь только чтение, но не решает проблему «неизменяемого словаря». Об этом говорит Эрик Липперт в последней ссылке Ричарда. Обратите внимание, что Липперт делает все возможное - он не только предоставляет немутантный метод Add, но и делает это эффективно, никогда не копируя данные, которые не нужно копировать.

Stefan Monov 23.09.2010 12:53

@Stefan: На мой взгляд, разница не в том, есть ли способ создать новую коллекцию из существующей. Коллекция, подобная той, что в этом ответе, имеет интерфейс только для чтения: любой, кому передан экземпляр этого, не может его изменить, но не гарантируется, что никто может его изменить. (Кто-то с оригинальным backingDict может изменить коллекцию.) Неизменяемые коллекции, с другой стороны, гарантированно никем не будут изменены.

Joren 27.03.2011 16:55

Забавно, насколько язвительны люди в этом ответе, когда этот ответ делает не больше и не меньше, чем собственный ReadOnlyCollection C#, за исключением того, что он поддерживает IDictionaryвместо IList.

James Michael Hare 08.07.2011 00:43

@ Стефан имеет право. Конструктор должен содержать следующее: _dict = new Dictionary(backingDict);

szogun1987 18.09.2011 19:56

Почему бы вам, ребята, просто не отредактировать ответ и добавить в него свои «отсутствующие» или «неизменные» требования?

chakrit 06.03.2012 14:17

throw под Add, Remove, Clear и т. д. - это нормально, но, пожалуйста, сделайте эти реализации явными, чтобы это не было так очевидно.

nawfal 09.11.2013 03:53

Я нашел здесь реализацию Неизменяемой (НЕ ЧИТАТЬ) реализации AVLTree для C#.

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

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

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

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

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

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

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

Instance Hunter 04.11.2009 16:23

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

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

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

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

dthorpe 30.10.2010 10:27

@dthrope, да, технически вы правы, но если в вашем словаре есть тысячи и тысячи элементов, для которых словарь в памяти в любом случае может не быть подходящей структурой данных, действительно ли это имеет значение в сегодняшнем мире с машинами, работающими с несколькими четырехъядерные процессоры и гигабайты памяти? Да, например, поиск может занять 1,01 миллисекунду вместо 1,0 миллисекунды, но требует ли это более сложного дизайна? В большинстве случаев ответ будет отрицательным.

SoftwareRockstar 02.11.2010 00:21

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

TrueWill 13.04.2011 20:13

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

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

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

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

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

Также есть новая надстройка (nuGet) от Microsoft под названием Неизменяемые коллекции.

sinelaw 15.10.2013 21:49

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

sinelaw 15.10.2013 21:56

Слово ВНИМАНИЕ !! ReadOnlyDictionary только захватывает значение свойств базового словаря при его создании. См. codeproject.com/Tips/1103307/… для получения дополнительной информации.

Michael Erickson 30.06.2016 01:23

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

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

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

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

Другие вопросы по теме