2016-04-28 2 views
1

У меня есть коллекция IDictionary<string, MyEnum?>, которую необходимо передать в класс, чтобы обернуть ее в IReadOnlyDictionary<string, MyEnum> (примечание MyEnum, но не MyEnum?).Конструкция класса для свойств только для чтения

Я пришел с двумя конструкциями:

  1. Задержка обертка для IReadOnlyDictionary<string, MyEnum> до доступа к свойству:

    public class MyClass 
    { 
        private readonly IEnumerable<KeyValuePair<string, MyEnum?>> _kvps; 
    
        public MyClass(IEnumerable<KeyValuePair<string, MyEnum?>> kvps) 
        { 
         _kvps = kvps; 
        } 
    
        public IReadOnlyDictionary<string, MyEnum> Kvps 
        { 
         get 
         { 
          var filtered = from kvp in _kvps 
              where kvp.Value.HasValue 
              select kvp; 
          return new ReadOnlyDictionary<string, MyEnum>(
           filtered.ToDictionary(kvp => kvp.Key, kvp => (MyEnum)kvp.Value); 
         } 
        } 
    } 
    
  2. Жадно оценить коллекцию в конструкторе

    public class MyClass 
    { 
        public MyClass(IEnumerable<KeyValuePair<string, MyEnum?>> kvps) 
        { 
         Kvps = ToReadOnly(kvps); 
        } 
    
        public IReadOnlyDictionary<string, MyEnum> Kvps { get; } 
    
        private static IReadOnlyDictionary<string, MyEnum> ToReadOnly(
         IEnumerable<KeyValuePair<string, MyEnum?>> kvps) 
        { 
         var filtered = from kvp in kvps 
             where kvp.Value.HasValue 
             select kvp; 
         return new ReadOnlyDictionary<string, MyEnum>(
          filtered.ToDictionary(kvp => kvp.Key, kvp => (MyEnum)kvp.Value); 
        } 
    } 
    

В разделе «Руководства по проектированию рамок» constructor design предполагается, что минимальная работа должна быть выполнена в конструкторах, поэтому я выбираю первый подход. Тем не менее, это означает, что каждый звонок до MyClass.Kvps вызовет копию _kvps, которая не является идеальной.

Я хотел бы знать, что является лучшим подходом (или есть другие способы) с точки зрения:

  • эффективность памяти (в идеале только один экземпляр коллекции хранится в MyClass)
  • Производительность (доступ к собственности должно быть быстрым и не должен вызывать копию KeyValuePair с)
+0

Ваши два проекта имеют разные типы поведения, если исходный словарь изменен между конструкцией и доступом к свойствам или между последовательными доступами свойств. Какой ты хочешь? – Blorgbeard

+0

@Blorgbeard Я бы хотел, чтобы свойство всегда отображало последние значения из источника 'IDictionary'. Означает ли это, что я ограничу только своим первым подходом? – rexcfnghk

+0

Тогда да, вам нужно будет сделать фильтрацию в аксессуре свойств. – Blorgbeard

ответ

1

Из двух требований - не копировать ключевые пары значений и не хранить две копии - вы должны разорвать один.

Что заставляет нас смотреть на это и думать, что должно быть решением является то, что мы видим TValue и TValue? и наши умы хотят видеть их как одного и того же типа. Но они не одного типа.

Это становится ясным, если представить себе, что вместо TValue и TValue?, что это два разных типа, как в int и string, и мы хотим, чтобы проецировать коллекцию одного в коллекцию другого при фильтрации. Например,

List<string> GetStringsFromNonNegativeInts(List<int> ints) 
{ 
    return ints.Where(i=>i>-1).Select(i=>i.ToString()).ToList(); 
} 

Это точно такой же сценарий, как пытаться фильтровать набор TValue? к набору TValue, даже без словаря. Это просто сложнее увидеть. TValue и TValue? с кодовым замком.

Существует только два способа сделать это. Один из них - копировать каждый раз, а другой - сохранять два списка в синхронизации.

+0

Отличный пример. Благодарю. – rexcfnghk

1

EDIT: Если вы хотите, чтобы последние исходные значения, лучший способ заключается в реализации вашего собственный класс, который реализует IReadOnlyDictionary. Инициализируйте это с закрытым полем ReadOnlyDictionary<string, MyEnum?>. Каждый вызов будет выполнять поиск, и если ключ существует AND HasValue, верните значение.

Обратите внимание, что эта реализация зависит от ссылки на исходные значения, передаваемые в качестве IReadOnlyDictionary, чтобы избежать необходимости копировать значения.


public class MyReadOnlyDictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue> where TValue : struct 
{ 
    // other methods to implement here... 

    public MyReadOnlyDictionary(IReadOnlyDictionary<TKey, TValue?> kvps) 
    { 
     _kvps = kvps; 
    } 

    private IReadOnlyDictionary<TKey, TValue?> _kvps; 

    new public TValue this[TKey key] 
    { 
     get 
     { 
      TValue? val = _kvps[key]; 
      if (val.HasValue) 
       return val.Value; 
      throw new KeyNotFoundException(); 
     } 
    } 
} 
+0

OP заявила в своих комментариях, что ему нужны самые последние исходные значения для каждого вызова свойства, поэтому кэширование не будет работать. – RJM

+0

Хорошая точка. Я обновил свой ответ, чтобы отразить это. Я думаю, что комментарий выше о внедрении нового класса реализации IReadOnlyDictionary является наиболее эффективным способом для этого. – gnalck

+0

Я тоже посмотрел на этот подход. Работа с счетчиками казалась болью, потому что вам пришлось бы «притворяться», что некоторые элементы не находятся в словаре. –

Смежные вопросы