C #: Как реализовать интерфейс со словарем IReadOnly, содержащим значения интерфейса из конкретного словаря, содержащего конкретные значения

#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 Ах! Обертки. Код делегирования просто кажется мне слишком «шаблонным», поэтому я даже не рассматривал его как «лучший способ» 🙂 Но я согласен, что это прозвучало слишком экстремально. Теперь исправил формулировку.