Есть ли что-нибудь, встроенное в основные библиотеки C#, что может дать мне неизменный словарь?
Что-то вроде Java:
Collections.unmodifiableMap(myMap);
И, чтобы уточнить, я не собираюсь останавливать изменение самих ключей / значений, а только структуру словаря. Я хочу, чтобы что-то выходило из строя быстро и громко при вызове любого из методов мутатора IDictionary (Add, Remove, Clear).





Я так не думаю. Есть способ создать список только для чтения и коллекцию только для чтения, но я не думаю, что есть встроенный словарь только для чтения. System.ServiceModel имеет реализацию ReadOnlyDictinoary, но она внутренняя. Вероятно, было бы не так уж сложно скопировать его, используя Reflector, или просто создать свой собственный с нуля. Он в основном обертывает словарь и выдает его при вызове мутатора.
Как упоминалось в ответ ниже Диланом, начиная с .NET 4.5 ЕСТЬ встроенное решение.
Насколько я знаю, нет. Но, возможно, вы сможете скопировать код (и многому научиться) из этих статей:
Большая часть этого теперь является официальной библиотекой от Microsoft, доступной через nuget - Неизменяемые коллекции
«Из коробки» нет способа сделать это. Вы можете создать его, создав собственный класс 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.
Упс, вместо изменения типа параметра в Contains измените его тело, чтобы использовать Contains вместо ContainsKey: return _dict.Contains(item);
Вы не реализовали проверку на равенство, которая является очень важной функцией для неизменяемых структур данных.
Похоже, все, что вы сделали, это взяли стандартный словарь и повсюду бросали исключения ...? «Неизменяемый» не обязательно означает «бесполезный». Наоборот.
@Ричард. Почему бесполезно обеспечивать неизменность? ReadOnlyCollection делает то же самое. Что бы вы сделали вместо исключения исключений? Предложите альтернативное решение, сказав «наоборот» недостаточно.
Что-то вроде Bit-Partitioned Hash Trie обеспечит неизменность без полного нарушения ADT, как вы это сделали. Ваш класс утверждает, что реализует IDictionary
@Ричард. AFAIK dbkk - это вполне «стандартный» способ предоставления неизменяемых коллекций как в C#, так и в java: они предоставляют один и тот же интерфейс с той лишь разницей, что некоторые методы генерируют исключения. Таким образом, если для метода требуется словарь или список, он может быть снабжен этими неизменяемыми объектами, поскольку интерфейс совпадает.
Нет причин, по которым BPHT не может реализовать IDictionary. Или для чего-то простого и элегантного просто прокрутите вниз до ответа Серхата. Нелепо, что очень поучительная серия блогов от разработчика C# Эрика Липперта набрала в 2,5 раза меньше голосов, чем эта отговорка.
@Richard Честный вопрос: что будет делать неизменяемая коллекция, если вы вызовете к ней Add? Вы не можете избавиться от функции Add, так как у вас больше нет IDictionary. Должен ли он тихо выйти из строя?
Нет, конечно нет. Ты хоть ответ читал? Вот, свяжу еще раз: blogs.msdn.com/b/ericlippert/archive/2008/01/21/…
Так много злобы здесь, когда плакат явно означал «Только для чтения», а не «Неизменяемый». Думаю, эта обертка, вероятно, соответствовала его потребностям, отсюда и высокий балл. Я рад, что люди пришли и стали немного более теоретическими, но давайте не будем упускать из виду то, что на самом деле нужно OP.
@dbkk: Мне не нравится, что ваше решение нарушает принцип подстановки. Я бы не стал выводить из IDictionary. Я бы сохранил закрытый член IDictionary и переадресовал бы ему все не изменяющиеся вызовы, и я бы не стал предоставлять методы пересылки для изменяющихся вызовов. Таким образом, это безопасно во время компиляции. Да, тогда его нельзя использовать как IDictionary или ICollection, но в этом-то и дело.
@ Все остальные: если вам интересно, как и мне, кажется, некоторые люди делают разницу между «Только для чтения» и «Неизменяемый». «Неизменяемый» словарь может иметь метод Add, который возвращается - новый словарь с добавленным к нему элементом, тогда как «только для чтения» - нет, и единственный способ получить полезный экземпляр «только для чтения» Dictionary - создать обычный словарь, а затем создать для него оболочку «только для чтения».
Мой комментарий к dbkk предлагает лучший способ создать словарь только чтение, но не решает проблему «неизменяемого словаря». Об этом говорит Эрик Липперт в последней ссылке Ричарда. Обратите внимание, что Липперт делает все возможное - он не только предоставляет немутантный метод Add, но и делает это эффективно, никогда не копируя данные, которые не нужно копировать.
@Stefan: На мой взгляд, разница не в том, есть ли способ создать новую коллекцию из существующей. Коллекция, подобная той, что в этом ответе, имеет интерфейс только для чтения: любой, кому передан экземпляр этого, не может его изменить, но не гарантируется, что никто может его изменить. (Кто-то с оригинальным backingDict может изменить коллекцию.) Неизменяемые коллекции, с другой стороны, гарантированно никем не будут изменены.
Забавно, насколько язвительны люди в этом ответе, когда этот ответ делает не больше и не меньше, чем собственный ReadOnlyCollection C#, за исключением того, что он поддерживает IDictionary
@ Стефан имеет право. Конструктор должен содержать следующее: _dict = new Dictionary
Почему бы вам, ребята, просто не отредактировать ответ и добавить в него свои «отсутствующие» или «неизменные» требования?
throw под Add, Remove, Clear и т. д. - это нормально, но, пожалуйста, сделайте эти реализации явными, чтобы это не было так очевидно.
Я нашел здесь реализацию Неизменяемой (НЕ ЧИТАТЬ) реализации AVLTree для C#.
Дерево AVL имеет логарифмическую (не постоянную) стоимость каждой операции, но все равно быстро.
Добавляя ответ 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);
Это не то же самое, потому что поиск сопоставляет ключи с список значений. Вы может просто вводите только одно значение, но вы также можете просто не писать в словарь, если вы все равно собираетесь обойтись без поддержки времени компиляции.
Есть еще одна альтернатива, как я описал по адресу:
http://www.softwarerockstar.com/2010/10/readonlydictionary-tkey-tvalue/
По сути, это подкласс ReadOnlyCollection>, который выполняет работу более элегантно. Элегантный в том смысле, что он поддерживает во время компиляции, что делает словарь доступным только для чтения, а не выбрасывает исключения из методов, которые изменяют элементы в нем.
Ваша реализация сводит входной словарь в список, а затем выполняет запросы, ищущие ключ в уплощенном списке. Разве этот запрос теперь не выполняется в линейном времени (медленно) в уплощенном списке, а не в логорифмическом времени (быстро) исходного словаря?
@dthrope, да, технически вы правы, но если в вашем словаре есть тысячи и тысячи элементов, для которых словарь в памяти в любом случае может не быть подходящей структурой данных, действительно ли это имеет значение в сегодняшнем мире с машинами, работающими с несколькими четырехъядерные процессоры и гигабайты памяти? Да, например, поиск может занять 1,01 миллисекунду вместо 1,0 миллисекунды, но требует ли это более сложного дизайна? В большинстве случаев ответ будет отрицательным.
Хотя я против преждевременной оптимизации, если разработчик выбирает словарь из набора инструментов, ожидается, что внутренняя реализация использует хеш-таблицу.
Библиотека 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 под названием Неизменяемые коллекции.
Разница между ImmutableDictionary (из неизменяемых коллекций) и ReadOnlyDictionary, упомянутым в этом ответе, заключается в том, что вы можете получить новый ImmutableDictionary с изменениями, которые хотите применить (вместо изменения существующего объекта).
Слово ВНИМАНИЕ !! ReadOnlyDictionary только захватывает значение свойств базового словаря при его создании. См. codeproject.com/Tips/1103307/… для получения дополнительной информации.
В общем, гораздо лучше вообще не передавать какие-либо словари (если вам это не нужно).
Вместо этого - создайте объект домена с интерфейсом, который не предлагает никаких методов изменение словаря (который он обертывает). Вместо этого предлагается обязательный 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 году, поэтому, думаю, стоит отметить, что сейчас есть способ создать неизменяемый словарь:
Использование:
using System.Collections.Immutable;
public MyClass {
private Dictionary<KeyType, ValueType> myDictionary;
public ImmutableDictionary<KeyType, ValueType> GetImmutable()
{
return myDictionary.ToImmutableDictionary();
}
}
Похоже, что
ReadOnlyDictionary<TKey,TValue>будет добавлен в .Net 4.5 как параллель сReadOnlyCollection<T>, который присутствует с .Net 2.0 msdn.microsoft.com/en-us/magazine/jj133817.aspx