#c#
#c#
Вопрос:
В моем коде я объявляю внутренние классы и общедоступные интерфейсы, и у меня есть ситуация, когда я хочу предоставить интерфейс с IReadOnlyDictionary, содержащий значения типа интерфейса, но я хочу реализовать его с классом, имеющим словарь со значениями конкретного типа.
Мне это нужно, потому что я десериализую некоторый JSON в конкретный тип, и я хочу предоставить десериализованные данные только для чтения.
Я реализовал это, как показано ниже, лениво создав новый словарь, в котором конкретные значения преобразуются в значения интерфейса. Есть ли способ сделать это без необходимости создавать новый словарь?
// In this code I want to expose an IReadonlyDictionary<string,ISomeValue>.
// The data is deserialized from JSON by using concrete type with a dictionary
// with values of type SomeValue which has to have public getter/setters to be deserialized.
// I would like to do this without copying and casting into a new dictionary as shown here.
// Is that possible?
public interface ISomeValue { }
internal class SomeValue : ISomeValue { }
public interface IConfiguration {
IReadOnlyDictionary<string, ISomeValue> Values{ get; }
}
internal class Configuration : IConfiguration {
public Configuration() {
_values = new Lazy<IReadOnlyDictionary<string, ISomeValue>>(()
=> Values.ToDictionary(x=>x.Key,x=>(ISomeValue)x.Value));
}
public Dictionary<string, SomeValue> Values { get; } = null!;
private Lazy<IReadOnlyDictionary<string, ISomeValue>> _values;
IReadOnlyDictionary<string, ISomeValue> IConfiguration.Values=> _values.Value;
}
Ответ №1:
Вам нужно создать новый объект, но вам не нужно создавать копию исходного словаря. Вместо этого вы можете создать представление поверх исходного словаря с новой формой. Вот несколько совершенно непроверенных, но простых кодов, которые, я считаю, должны работать:
public class ReadOnlyDictionaryWrapper<TKey, TValue, TOriginalValue> : IReadOnlyDictionary<TKey, TValue>
where TOriginalValue : TValue
{
private readonly IReadOnlyDictionary<TKey, TOriginalValue> original;
public ReadOnlyDictionaryWrapper(IReadOnlyDictionary<TKey, TOriginalValue> original) =>
this.original = original;
public TValue this[TKey key] => original[key];
public IEnumerable<TKey> Keys => original.Keys;
public IEnumerable<TValue> Values => original.Values.Cast<TValue>();
public int Count => original.Count;
public bool ContainsKey(TKey key) => original.ContainsKey(key);
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() =>
original.Select(pair => new KeyValuePair<TKey, TValue>(pair.Key, pair.Value))
.GetEnumerator();
public bool TryGetValue(TKey key, out TValue value)
{
bool ret = original.TryGetValue(key, out var originalValue);
value = originalValue;
return ret;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Я бы предложил создать единственный экземпляр этого в вашем Configuration
конструкторе для переноса _values
.
Ответ №2:
Я хотел бы сделать это без копирования и приведения к новому словарю, как показано здесь.
Ну, вы не можете просто напрямую возвращать Value
в явной реализации интерфейса, потому что оба общих параметра IReadOnlyDictionary
в общем случае инвариантны. Если бы только TValue
были ковариантными, вы могли бы сделать это напрямую:
public Dictionary<string, SomeValue> Values { get; } = new Dictionary<string, SomeValue>();
IReadOnlyDictionary<string, ISomeValue> IConfiguration.Values => Values;
Но TryGetValue
используется TValue
в качестве параметра, IReadOnlyDictionary
вынужден быть инвариантным к TValue
.
Я предлагаю вам сделать ваш код более красивым в некоторых других аспектах. Например, вы могли бы извлечь ту очень длинную строку кода, которая создает новый словарь, в метод расширения.
Комментарии:
1. На самом деле нет необходимости копировать словарь . Вполне возможно создать «оболочку», которая просто соответствующим образом делегирует операции. Когда вы говорите: «Это невозможно. Вы должны создать новый словарь» мне кажется, что вы считаете, что должна быть сделана настоящая копия.
2. @JonSkeet Ах! Обертки. Код делегирования просто кажется мне слишком «шаблонным», поэтому я даже не рассматривал его как «лучший способ» 🙂 Но я согласен, что это прозвучало слишком экстремально. Теперь исправил формулировку.