Есть ли реализация IDictionary, которая при отсутствии ключа возвращает значение по умолчанию вместо броска? - PullRequest
108 голосов
/ 11 февраля 2009

Индексатор в словаре выдает исключение, если ключ отсутствует. Есть ли реализация IDictionary, которая вместо этого будет возвращать default (T)?

Я знаю о методе TryGetValue, но это невозможно использовать с linq.

Будет ли это эффективно делать то, что мне нужно?:

myDict.FirstOrDefault(a => a.Key == someKeyKalue);

Я не думаю, что это произойдет, поскольку я думаю, что он будет перебирать ключи вместо использования поиска по хэшу.

Ответы [ 12 ]

128 голосов
/ 11 февраля 2009

Действительно, это не будет эффективно вообще.

Вы всегда можете написать метод расширения:

public static TValue GetValueOrDefault<TKey,TValue>
    (this IDictionary<TKey, TValue> dictionary, TKey key)
{
    TValue ret;
    // Ignore return value
    dictionary.TryGetValue(key, out ret);
    return ret;
}

Или с C # 7.1:

public static TValue GetValueOrDefault<TKey,TValue>
    (this IDictionary<TKey, TValue> dictionary, TKey key) =>
    dictionary.TryGetValue(key, out var ret) ? ret : default;

Что использует:

  • Метод с выражением тела (C # 6)
  • Переменная out (C # 7.0)
  • Литерал по умолчанию (C # 7.1)
17 голосов
/ 20 сентября 2013

Перенос этих методов расширения может помочь ..

public static V GetValueOrDefault<K, V>(this IDictionary<K, V> dict, K key)
{
    return dict.GetValueOrDefault(key, default(V));
}

public static V GetValueOrDefault<K, V>(this IDictionary<K, V> dict, K key, V defVal)
{
    return dict.GetValueOrDefault(key, () => defVal);
}

public static V GetValueOrDefault<K, V>(this IDictionary<K, V> dict, K key, Func<V> defValSelector)
{
    V value;
    return dict.TryGetValue(key, out value) ? value : defValSelector();
}
5 голосов
/ 10 января 2019

Если кто-то использует ядро ​​.net 2 и выше (C # 7.X), введен класс CollectionExtensions , который может использовать метод GetValueOrDefault для получения значения по умолчанию, если ключ отсутствует словарь.

3 голосов
/ 30 июня 2014

Collections.Specialized.StringDictionary обеспечивает исключительный результат при поиске значения отсутствующего ключа. Он также не учитывает регистр по умолчанию.

Предостережения

Он действителен только для своего специализированного использования и & mdash; разрабатывается перед дженериками & mdash; у него нет очень хорошего перечислителя, если вам нужно просмотреть всю коллекцию.

1 голос
/ 12 января 2018

Этот вопрос помог подтвердить, что TryGetValue играет здесь роль FirstOrDefault.

Одна интересная особенность C # 7, которую я хотел бы упомянуть, - это переменные , и если вы добавите нулевой условный оператор из C # 6 в уравнение, ваш код может быть намного проще без дополнительных методов расширения.

var dic = new Dictionary<string, MyClass>();
dic.TryGetValue("Test", out var item);
item?.DoSomething();

Недостатком этого является то, что вы не можете делать все встроенное, как это;

dic.TryGetValue("Test", out var item)?.DoSomething();

Если нам нужно / нужно сделать это, мы должны написать один метод расширения, такой как у Джона.

1 голос
/ 16 ноября 2017

Вот версия @ JonSkeet для мира C # 7.1, которая также позволяет передавать необязательный параметр по умолчанию:

public static TV GetValueOrDefault<TK, TV>(this IDictionary<TK, TV> dict, TK key, TV defaultValue = default) => dict.TryGetValue(key, out TV value) ? value : defaultValue;

Может быть более эффективно иметь две функции для оптимизации случая, когда вы хотите вернуть default(TV):

public static TV GetValueOrDefault<TK, TV>(this IDictionary<TK, TV> dict, TK key, TV defaultValue) => dict.TryGetValue(key, out TV value) ? value : defaultValue;
public static TV GetValueOrDefault2<TK, TV>(this IDictionary<TK, TV> dict, TK key) {
    dict.TryGetValue(key, out TV value);
    return value;
}

К сожалению, в C # (пока?) Нет оператора запятой (или предложенного C # 6 оператора точки с запятой), поэтому для одной из перегрузок необходимо иметь фактическое тело функции (задыхаясь!)

1 голос
/ 05 октября 2017
public class DefaultIndexerDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    private IDictionary<TKey, TValue> _dict = new Dictionary<TKey, TValue>();

    public TValue this[TKey key]
    {
        get
        {
            TValue val;
            if (!TryGetValue(key, out val))
                return default(TValue);
            return val;
        }

        set { _dict[key] = value; }
    }

    public ICollection<TKey> Keys => _dict.Keys;

    public ICollection<TValue> Values => _dict.Values;

    public int Count => _dict.Count;

    public bool IsReadOnly => _dict.IsReadOnly;

    public void Add(TKey key, TValue value)
    {
        _dict.Add(key, value);
    }

    public void Add(KeyValuePair<TKey, TValue> item)
    {
        _dict.Add(item);
    }

    public void Clear()
    {
        _dict.Clear();
    }

    public bool Contains(KeyValuePair<TKey, TValue> item)
    {
        return _dict.Contains(item);
    }

    public bool ContainsKey(TKey key)
    {
        return _dict.ContainsKey(key);
    }

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        _dict.CopyTo(array, arrayIndex);
    }

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        return _dict.GetEnumerator();
    }

    public bool Remove(TKey key)
    {
        return _dict.Remove(key);
    }

    public bool Remove(KeyValuePair<TKey, TValue> item)
    {
        return _dict.Remove(item);
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        return _dict.TryGetValue(key, out value);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _dict.GetEnumerator();
    }
}
1 голос
/ 07 апреля 2014

Если вы используете ASP.NET MVC, вы можете использовать класс RouteValueDictionary, который выполняет эту работу.

public object this[string key]
{
  get
  {
    object obj;
    this.TryGetValue(key, out obj);
    return obj;
  }
  set
  {
    this._dictionary[key] = value;
  }
}
1 голос
/ 15 сентября 2011

Можно определить интерфейс для функции поиска по словарю в словаре. Я бы определил это как что-то вроде:

Interface IKeyLookup(Of Out TValue)
  Function Contains(Key As Object)
  Function GetValueIfExists(Key As Object) As TValue
  Function GetValueIfExists(Key As Object, ByRef Succeeded As Boolean) As TValue
End Interface

Interface IKeyLookup(Of In TKey, Out TValue)
  Inherits IKeyLookup(Of Out TValue)
  Function Contains(Key As TKey)
  Function GetValue(Key As TKey) As TValue
  Function GetValueIfExists(Key As TKey) As TValue
  Function GetValueIfExists(Key As TKey, ByRef Succeeded As Boolean) As TValue
End Interface

Версия с неуниверсальными ключами позволила бы коду, использующему код, использующий неструктурные типы ключей, чтобы учесть произвольную дисперсию ключа, что было бы невозможно с параметром универсального типа. Нельзя допустить использование изменяемого Dictionary(Of Cat, String) в качестве изменяемого Dictionary(Of Animal, String), так как последний допускает SomeDictionaryOfCat.Add(FionaTheFish, "Fiona"). Но нет ничего плохого в том, чтобы использовать непостоянный Dictionary(Of Cat, String) в качестве неизменяемого Dictionary(Of Animal, String), поскольку SomeDictionaryOfCat.Contains(FionaTheFish) следует считать совершенно правильно сформированным выражением (оно должно возвращать false без необходимости поиска в словаре для всего, что не является т типа Cat).

К сожалению, единственный способ реально использовать такой интерфейс - это обернуть объект Dictionary в класс, который реализует интерфейс. Однако в зависимости от того, что вы делаете, такой интерфейс и различия, которые он допускает, могут стоить усилий.

0 голосов
/ 10 мая 2018

Я использовал инкапсуляцию для создания IDictionary с поведением, очень похожим на STL map , для тех из вас, кто знаком с c ++. Для тех, кто не:

  • indexer get {} в SafeDictionary ниже возвращает значение по умолчанию, если ключ отсутствует, и добавляет этот ключ в словарь со значением по умолчанию. Это часто желаемое поведение, так как вы ищите предметы, которые в конечном итоге появятся или имеют хорошие шансы появиться.
  • метод Add (ключ TK, TV val) ведет себя как метод AddOrUpdate, заменяя текущее значение, если оно существует, а не бросание. Я не понимаю, почему m $ не имеет метода AddOrUpdate и считает, что выбрасывать ошибки в очень распространенных сценариях - хорошая идея.

TL / DR - SafeDictionary написан так, чтобы никогда не генерировать исключения ни при каких обстоятельствах, кроме порочных сценариев , например, из-за того, что на компьютере не хватает памяти (или произошел пожар) , Это достигается путем замены Add на поведение AddOrUpdate и возврата по умолчанию вместо того, чтобы выдавать исключение NotFoundException из индексатора.

Вот код:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

public class SafeDictionary<TK, TD>: IDictionary<TK, TD> {
    Dictionary<TK, TD> _underlying = new Dictionary<TK, TD>();
    public ICollection<TK> Keys => _underlying.Keys;
    public ICollection<TD> Values => _underlying.Values;
    public int Count => _underlying.Count;
    public bool IsReadOnly => false;

    public TD this[TK index] {
        get {
            TD data;
            if (_underlying.TryGetValue(index, out data)) {
                return data;
            }
            _underlying[index] = default(TD);
            return default(TD);
        }
        set {
            _underlying[index] = value;
        }
    }

    public void CopyTo(KeyValuePair<TK, TD>[] array, int arrayIndex) {
        Array.Copy(_underlying.ToArray(), 0, array, arrayIndex,
            Math.Min(array.Length - arrayIndex, _underlying.Count));
    }


    public void Add(TK key, TD value) {
        _underlying[key] = value;
    }

    public void Add(KeyValuePair<TK, TD> item) {
        _underlying[item.Key] = item.Value;
    }

    public void Clear() {
        _underlying.Clear();
    }

    public bool Contains(KeyValuePair<TK, TD> item) {
        return _underlying.Contains(item);
    }

    public bool ContainsKey(TK key) {
        return _underlying.ContainsKey(key);
    }

    public IEnumerator<KeyValuePair<TK, TD>> GetEnumerator() {
        return _underlying.GetEnumerator();
    }

    public bool Remove(TK key) {
        return _underlying.Remove(key);
    }

    public bool Remove(KeyValuePair<TK, TD> item) {
        return _underlying.Remove(item.Key);
    }

    public bool TryGetValue(TK key, out TD value) {
        return _underlying.TryGetValue(key, out value);
    }

    IEnumerator IEnumerable.GetEnumerator() {
        return _underlying.GetEnumerator();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...