Как создать новый словарь <,> из IReadOnlyDictionary <,>?

.NET 4.5 представляет удобный интерфейс IReadOnlyDictionary<TKey, TValue>. A Dictionary<TKey, TValue> is-an IReadOnlyDictionary<TKey, TValue>, поэтому я могу просто передать первое, где требуется последнее.

Я не уверен в обратном пути: как мне создать новый Dictionary<,> на основе существующего IReadOnlyDictionary<,>?

  • Dictionary<,> имеет несколько конструкторов, которые принимают IDictionary<,>, но ни один из них не принимает IReadOnlyDictionary<,>.
  • IReadOnlyDictionary<,> имеет метод расширения ToDictionary<,>(). Однако это унаследовано от IEnumerable<>. Поэтому, чтобы использовать его, я должен передать двух совершенно лишних делегатов, таких как: readOnlyDict.ToDictionary(pair => pair.Key, pair => pair.Value). Гадкий!
  • Конечно, я всегда могу создать новый Dictionary<,>, перебрать исходные пары и скопировать их.

Мне кажется, что должен существовать тривиальный способ создания нового Dictionary<,> на основе существующего IReadOnlyDictionary<,>. Я что-то пропустил?

Изменить: Некоторые пояснения. Я не ищу какой-то волшебный способ лечения IReadOnlyDictionary<,> как Dictionary<,>. Я хочу создать новый Dictionary<,>, который копирует свои начальные значения из IReadOnlyDictionary<,>. Этот вопрос - это длинный вопрос:

Dictionary<,> имеет конструктор удобства для копирования начальных значений из IDictionary<,>. Почему у него нет одного, принимающего IReadOnlyDictionary<,> и какого самого идиоматического способа достижения одного и того же результата?

Ответ 1

Dictionary<TKey, TValue> способен реализовать IReadOnlyDictionary<TKey, TValue>, потому что любой код, который принимает последний, обещает не изменять словарь, который является подмножеством функциональности, предоставляемой первым.

Но рассмотрим другое направление. Вы начинаете с IReadOnlyDictionary<TKey, TValue>, и пытаетесь превратить его в обычный Dictionary<TKey, TValue>. Теперь вы взяли то, что ранее обещано не изменять, и превратили его во что-то, что может быть изменено.

Ясно, что этого просто не будет.

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

Чтобы быть в безопасности, у вас есть два варианта:

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

Обратите внимание, что последнее на самом деле не дает вам то, о чем вы конкретно просите. Он закрывается, но это не фактический пример Dictionary<TKey, TValue>.

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

public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(
    this IReadOnlyDictionary<TKey, TValue> dict)
{
    return dict.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}


Приложение:
Мне кажется, что я должен, вероятно, уточнить: прежде всего предполагается, что вы пытаетесь преобразовать произвольный экземпляр IReadOnlyDictionary<TKey, TValue>. Очевидно, что если вы знаете или, по крайней мере, подозреваете, что ваш объект IReadOnlyDictionary<TKey, TValue> на самом деле является экземпляром Dictionary<TKey, TValue>, вы можете сделать или попытаться применить (соответственно) к Dictionary<TKey, TValue>.

Ответ 2

Похоже, что IReadOnlyDictionary<> был добавлен позже, а существующие классы (например, Dictionary<>) не были модифицированы для поддержки его новыми методами, для соображений обратной совместимости. [править], однако.

Для чего это стоит Справочная реализация Microsoft конструктора Dictionary по существу

Dictionary(IDictionary dictionary)
: this(dictionary.Count) {
    foreach (KeyValuePair<TKey,TValue> pair in dictionary) {
            Add(pair.Key, pair.Value);
    }
}

И Enumerable по существу

Dictionary<> ToDictionary<>(this IEnumerable<> source, Func<> keySelector, Func<> elementSelector) 
{
    Dictionary<> d = new Dictionary<>();
    foreach (TSource element in source) 
    d.Add(keySelector(element), elementSelector(element));
    return d;
}

Таким образом, разница, по-видимому, в основном вызывает вызовы делегатов селектора.

Таким образом, метод расширения, копирующий код конструктора, но принимающий IReadOnlyDictionary<>, более или менее, чем @Steve, выглядит немного проще и немного более эффективным...

Ответ 3

Часто цитируемый ответ для любого "почему С# не имеет этой функции" заключается в том, что для разработки, реализации, документирования, тестирования и т.д. требуется время, особенно когда исходная и двоичная совместимость с существующим кодом должны быть приняты счет.

Интуитивно, любой IDictionary<,> также был бы IReadOnlyDictionary<,> (последний является подмножеством первого интерфейса), и, возможно, если бы IReadOnlyDictionary<,> существовало с самого начала, оно было бы настроено таким образом (с соответствующими конструктор, принимающий IReadOnlyDictionary<,> в качестве аргумента и автоматически принимающий IDictionary<,> из-за наследования), но поскольку вещи стоят IDictionary<,> и IReadOnlyDictionary<,>, это несвязанные интерфейсы, которые включают в себя те же функции. Я могу только предположить, что они не связаны с тем, чтобы не нарушать существующий код пользователя, который выполняет явную реализацию интерфейса IDictionary<,>.

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

Я могу только предположить, что это не считалось более важным, чем другие возможные функции в конвейере...

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

В моем собственном случае у меня был класс, который происходит от Dictionary<MyEnum,double> (для того, чтобы иметь словарь, который также реализует интерфейс, обращаясь к определенным ключам), и хотел написать конструктор, например:

public MyDictionary(IReadOnlyDictionary<MyEnum, double> source) : base(source)
{ }

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

public MyDictionary(IReadOnlyDictionary<MyEnum, double> source)
{
  foreach (var pair in source)
    base.Add(pair.Key, pair.Value);
}

В тех случаях, когда мы непосредственно создаем Dictionary<,>, есть, по крайней мере, две отлично читаемые опции - петля, аналогичная вышеизложенной, или функция расширения ToDictionary, которую исходный вопрос отклонил исключительно за то, что он был "уродливым", (Я отклонил это, потому что это означало бы копирование словаря дважды - один раз в вызове ToDictionary и один раз в конструкторе базового класса).

Я бы ожидал, что "отсутствующий" конструктор на Dictionary<,> будет в равной степени тривиальным в реализации, но им также нужно будет иметь дело со всем другим связанным с ним palaver, который сопровождает любую новую функцию. Dictionary<,> и IDictionary<,> ОЧЕНЬ широко используются во множестве интересных способов, создавая потенциал для разбивки материала даже при более простых и самых безобидных изменениях.

Изменить: Думая дальше... если бы "отсутствующий" конструктор должен был быть реализован, это сделало бы его неоднозначным, какой конструктор следует вызывать для класса, который реализует оба интерфейсы IDictionary<,> и IReadOnlyDictionary<,>. Первым примером является сам класс Dictionary.