Есть ли реализация IDictionary, которая при отсутствии ключа возвращает значение по умолчанию вместо метания?

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

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

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

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

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

Ответ 1

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

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

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;
}

Или с С# 7.1:

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

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

  • Метод с выражением тела (С# 6)
  • Переменная out (С# 7.0)
  • Литерал по умолчанию (С# 7.1)

Ответ 2

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

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();
}

Ответ 3

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

Dictionary<string, string> colorData = new  Dictionary<string, string>();
string color = colorData.GetValueOrDefault("colorId", string.Empty);

Ответ 4

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

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

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

Ответ 5

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();
    }
}

Ответ 6

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

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"). Но нет ничего плохого в использовании mutable Dictionary(Of Cat, String) как неизменяемого Dictionary(Of Animal, String), так как SomeDictionaryOfCat.Contains(FionaTheFish) следует рассматривать как прекрасно сформированное выражение (он должен возвращать false, не выполняя поиск словаря, ни для чего, что isn 't типа Cat).

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

Ответ 7

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

public object this[string key]
{
  get
  {
    object obj;
    this.TryGetValue(key, out obj);
    return obj;
  }
  set
  {
    this._dictionary[key] = value;
  }
}

Ответ 8

Вот версия @JonSkeet для мира С# 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;
}

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

Ответ 9

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

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

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

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

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

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

Ответ 10

Я использовал инкапсуляцию для создания IDictionary с поведением, очень похожим на карту STL, для тех из вас, кто знаком с 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();
    }
}

Ответ 11

Как насчет этого однострочного интерфейса, который проверяет наличие ключа с помощью ContainsKey, а затем возвращает либо нормально восстановленное значение, либо значение по умолчанию с помощью условного оператора?

var myValue = myDictionary.ContainsKey(myKey) ? myDictionary[myKey] : myDefaultValue;

Не нужно внедрять новый класс Dictionary, поддерживающий значения по умолчанию, просто замените ваши операторы поиска на короткую строку выше.

Ответ 12

public static class CollectionExtensions
{
    public static V GetOrDefault<K, V>(this Dictionary<K,V> dictionary,K key,V defaultValue) => (dictionary.TryGetValue(key, out V result)) ? result : defaultValue;         
}

Ответ 13

Это может быть однострочник, чтобы проверить TryGetValue и вернуть значение по умолчанию, если оно false.

 Dictionary<string, int> myDic = new Dictionary<string, int>() { { "One", 1 }, { "Four", 4} };
 string myKey = "One"
 int value = myDic.TryGetValue(myKey, out value) ? value : 100;

myKey = "One" => value = 1

myKey = "two" => value = 100

myKey = "Four" => value = 4

Попробуйте онлайн

Ответ 14

Если вы используете .Net Core, вы можете использовать метод CollectionExtensions.GetValueOrDefault. Это то же самое, что и реализация, представленная в принятом ответе.

public static TValue GetValueOrDefault<TKey,TValue> (
   this System.Collections.Generic.IReadOnlyDictionary<TKey,TValue> dictionary,
   TKey key);

Ответ 15

Нет, потому что иначе как вы узнаете разницу, когда ключ существует, но сохранил нулевое значение? Это может быть значительным.