2010-09-14 2 views
4

Я устал от этого словаря идиом:Combined «Проверить Добавить или Fetch» ​​из словаря

 Dictionary<Guid,Contact> Contacts; 
     //... 
     if (!Contacts.ContainsKey(id)) 
     { 
      contact = new Contact(); 
      Contacts[id] = contact; 
     } 
     else 
     { 
      contact = Contacts[id]; 
     } 

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

ответ

11

Реализация:

public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, 
              TKey key, Func<TValue> valueCreator) 
{ 
    TValue value; 
    if (!dictionary.TryGetValue(key, out value)) 
    { 
     value = valueCreator(); 
     dictionary.Add(key, value); 
    } 
    return value; 
} 

public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, 
              TKey key) where TValue : new() 
{ 
    return dictionary.GetOrAdd(key,() => new TValue()); 
} 

Использование:

var contacts = new Dictionary<Guid, Contact>(); 
Guid id = ... 

contacts.GetOrAdd(id).Name = "Abc"; // ok since Contact has public parameterless ctor 
contacts.GetOrAdd(id,() => new Contact { Name = "John Doe" }).Age = 40; 
3

То же ответ Ани, но в более неразборчивой однострочника :)

/// <param name="valueCreator">The expensive value creator function.</param> 
public static T GetOrAdd<S, T>(this IDictionary<S, T> dict, S key, 
           Func<T> valueCreator) 
{ 
    T value; 
    return dict.TryGetValue(key, out value) ? value : dict[key] = valueCreator(); 
} 

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

Словарь, к сожалению, doesn't have this feature out of the box to do all this in a single lookup.

+1

Просто заметка о вашем упоминании используя значение создателя. Это имеет большее значение, когда либо создание значения дорого, либо обычный случай НЕ является созданием новых экземпляров. Если вы используете только типы значений или путем профилирования, вы обнаружите, что общий случай создает новые экземпляры, это может быть больше накладных расходов, чем необходимо, поэтому лучше использовать назначение прямых значений и общие делегаты. Существуют также случаи, когда вы хотите назначить уже созданное значение. – Jeremy

0

Вы всегда можете свернуть свой словарь.

Решение 1: Наследуйте и используйте «новые» методы переопределения, сначала проверяющие наличие ключа. Если да, верните это значение с помощью ключа или создайте на

Func<K, T> 

делегат. Тем не менее, это решение будет перерыв при использовании этого словаря через интерфейс

IDictionary<K,T> 

Так что вы должны быть более тщательно с помощью раствора 2.

Решение 2: Словарь обертка, которая использует внутренний словарь - остальное то же самое, что и решение 1.

Решение 3: ConcurrentDictionary предлагает GetOrAdd, который также является потокобезопасным.

Решение 4: ConcurrentDictionary Упаковочный аналогично решению 2.

Вот словарь обертка:

public class WrappedDictionary<K, T> : IDictionary<K, T> 
{ 
    public IDictionary<K, T> WrappedInstance { get; set; } 

    public virtual T this[K key] 
    { 
     get 
     { 
      // CUSTOM RESOLUTION CODE GOES HERE 
      return this.WrappedInstance[key]; 
     } 
     set 
     { 
      this.WrappedInstance[key] = value; 
     } 
    } 

    public int Count 
    { 
     get 
     { 
      return this.WrappedInstance.Count; 
     } 
    } 

    public bool IsReadOnly 
    { 
     get 
     { 
      return this.WrappedInstance.IsReadOnly; 
     } 
    } 

    public ICollection<K> Keys 
    { 
     get 
     { 
      return this.WrappedInstance.Keys; 
     } 
    } 

    public ICollection<T> Values 
    { 
     get 
     { 
      return this.WrappedInstance.Values; 
     } 
    } 

    public void Add(KeyValuePair<K, T> item) 
    { 
     this.WrappedInstance.Add(item); 
    } 

    public void Add(K key, T value) 
    { 
     this.WrappedInstance.Add(key, value); 
    } 

    public void Clear() 
    { 
     this.WrappedInstance.Clear(); 
    } 

    public bool Contains(KeyValuePair<K, T> item) 
    { 
     return this.WrappedInstance.Contains(item); 
    } 

    public bool ContainsKey(K key) 
    { 
     return this.WrappedInstance.ContainsKey(key); 
    } 

    public void CopyTo(KeyValuePair<K, T>[] array, int arrayIndex) 
    { 
     this.WrappedInstance.CopyTo(array, arrayIndex); 
    } 

    public IEnumerator<KeyValuePair<K, T>> GetEnumerator() 
    { 
     return this.WrappedInstance.GetEnumerator(); 
    } 

    public bool Remove(KeyValuePair<K, T> item) 
    { 
     return this.WrappedInstance.Remove(item); 
    } 

    public bool Remove(K key) 
    { 
     return this.WrappedInstance.Remove(key); 
    } 

    public bool TryGetValue(K key, out T value) 
    { 
     // CUSTOM RESOLUTION CODE GOES HERE 
     return this.WrappedInstance.TryGetValue(key, out value); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return this.WrappedInstance.GetEnumerator(); 
    } 
} 
Смежные вопросы