Как обойти недостаток ковариации с IReadOnlyDictionary? - программирование
Подтвердить что ты не робот

Как обойти недостаток ковариации с IReadOnlyDictionary?

Я пытаюсь открыть словарь только для чтения, содержащий объекты с интерфейсом только для чтения. Внутренне словарь может писать, а также объекты внутри (см. Ниже пример кода). Моя проблема в том, что IReadOnlyDictionary не поддерживает ковариантные преобразования из-за причины, изложенной в вопросе здесь. Это означает, что я не могу просто выставить свой внутренний словарь как только для чтения.

Итак, мой вопрос: есть ли эффективный способ преобразования моего внутреннего словаря в IReadOnlyDictionary или какой-либо другой способ справиться с этим? Возможные варианты:

  • Удерживайте два внутренних словаря и держите их в синхронизации.
  • Создайте новый словарь при доступе к объекту и введите все объекты внутри.
  • Отправляйте IReadOnly обратно NotReadOnly при его внутреннем использовании.

1 кажется боли, 2 кажется крайне неэффективным. 3 звучит как самый многообещающий на данный момент, но все еще уродлив. У меня есть другие варианты?

public class ExposesReadOnly
{
    private Dictionary<int, NotReadOnly> InternalDict { get; set; }
    public IReadOnlyDictionary<int, IReadOnly> PublicList
    {
        get
        {
            // This doesn't work...
            return this.InternalDict;
        }
    }

    // This class can be modified internally, but I don't want
    // to expose this functionality.
    private class NotReadOnly : IReadOnly
    {
        public string Name { get; set; }
    }
}

public interface IReadOnly
{
    string Name { get; }
}
4b9b3361

Ответ 1

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

public class ReadOnlyDictionaryWrapper<TKey, TValue, TReadOnlyValue> : IReadOnlyDictionary<TKey, TReadOnlyValue> where TValue : TReadOnlyValue
{
    private IDictionary<TKey, TValue> _dictionary;

    public ReadOnlyDictionaryWrapper(IDictionary<TKey, TValue> dictionary)
    {
        if (dictionary == null) throw new ArgumentNullException("dictionary");
        _dictionary = dictionary;
    }
    public bool ContainsKey(TKey key) { return _dictionary.ContainsKey(key); }

    public IEnumerable<TKey> Keys { get { return _dictionary.Keys; } }

    public bool TryGetValue(TKey key, out TReadOnlyValue value)
    {
        TValue v;
        var result = _dictionary.TryGetValue(key, out v);
        value = v;
        return result;
    }

    public IEnumerable<TReadOnlyValue> Values { get { return _dictionary.Values.Cast<TReadOnlyValue>(); } }

    public TReadOnlyValue this[TKey key] { get { return _dictionary[key]; } }

    public int Count { get { return _dictionary.Count; } }

    public IEnumerator<KeyValuePair<TKey, TReadOnlyValue>> GetEnumerator()
    {
        return _dictionary
                    .Select(x => new KeyValuePair<TKey, TReadOnlyValue>(x.Key, x.Value))
                    .GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

Ответ 2

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

В зависимости от того, что вы делаете, может быть полезно определить IFetchByKey<out TValue>, который наследуется IFetchByKey<in TKey, out TValue>, с прежними принимающими запросами для любого типа объекта (с учетом экземпляра объекта, коллекции Cat должен иметь возможность сказать, содержит ли он этот экземпляр, даже если это тип Dog или ToyotaPrius; коллекция не будет содержать экземпляры последних типов и должна быть в состоянии сказать это).

Ответ 3

Возможно, это решение работает для вас:

public class ExposesReadOnly
{
    private IDictionary<int, IReadOnly> InternalDict { get; set; }
    public IReadOnlyDictionary<int, IReadOnly> PublicList
    {
        get
        {
            IReadOnlyDictionary<int, IReadOnly> dictionary = new ReadOnlyDictionary<int, IReadOnly>(InternalDict);

            return dictionary;
        }
    }

    private class NotReadOnly : IReadOnly
    {
        public string Name { get; set; }
    }

    public void AddSomeValue()
    {
        InternalDict = new Dictionary<int, NotReadOnly>();
        InternalDict.Add(1, new NotReadOnly() { Name = "SomeValue" });
    }
}

public interface IReadOnly
{
    string Name { get; }
}

class Program
{
    static void Main(string[] args)
    {
        ExposesReadOnly exposesReadOnly = new ExposesReadOnly();
        exposesReadOnly.AddSomeValue();

        Console.WriteLine(exposesReadOnly.PublicList[1].Name);
        Console.ReadLine();

        exposesReadOnly.PublicList[1].Name = "This is not possible!";
    }
}

Надеюсь, это поможет!

здоровается

Ответ 4

Другой подход к конкретному отсутствию ковариации:

Работает для определенного типа полезной ковариации в idictionary

public static class DictionaryExtensions
    {
        public static IReadOnlyDictionary<TKey, IEnumerable<TValue>> ToReadOnlyDictionary<TKey, TValue>(
            this IDictionary<TKey, List<TValue>> toWrap)
        {
            var intermediate = toWrap.ToDictionary(a => a.Key, a =>a.Value!=null? a.Value.ToArray().AsEnumerable():null);
            var wrapper = new ReadOnlyDictionary<TKey, IEnumerable<TValue>>(intermediate);
            return wrapper;
        }   
    }

Ответ 5

В зависимости от вашего варианта использования вы можете уйти с раскрытием Func<int,IReadOnly>.

public class ExposesReadOnly
{
    private Dictionary<int, NotReadOnly> InternalDict { get; set; }
    public Func<int,IReadOnly> PublicDictionaryAccess
    {
        get
        {
            return (x)=>this.InternalDict[x];
        }
    }

    // This class can be modified internally, but I don't want
    // to expose this functionality.
    private class NotReadOnly : IReadOnly
    {
        public string Name { get; set; }
    }
}

public interface IReadOnly
{
    string Name { get; }
}