Чи є реалізація IDictionary, яка за відсутнім ключем повертає значення за замовчуванням замість того, щоб кидати?


129

Індексатор у словник видає виняток, якщо ключ відсутній. Чи є реалізація IDictionary, яка замість цього поверне дефолт (T)?

Я знаю про метод "TryGetValue", але це неможливо використовувати з linq.

Чи може це ефективно робити те, що мені потрібно ?:

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

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


10
Дивіться також це пізніше питання, позначене як дублікат цього, але з різними відповідями: Словник, що повертає значення за замовчуванням, якщо ключ не існує
Rory O'Kane

2
@dylan Це викличе виняток для відсутнього ключа, а не null. Крім того, потрібно буде визначити, який за замовчуванням має бути скрізь, де використовується словник. Також врахуйте вік цього питання. У нас не було ?? оператор 8 років тому як би то не було
TheSoftwareJedi

Відповіді:


147

Дійсно, це зовсім не буде ефективно.

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

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)
  • Вихідна змінна (C # 7.0)
  • Літерал за замовчуванням (C # 7.1)

2
Або більш компактно:return (dictionary.ContainsKey(key)) ? dictionary[key] : default(TValue);
Пітер Глюк

33
@PeterGluck: Більш компактно, але менш ефективно ... навіщо виконувати два огляди у випадку, коли ключ присутній?
Джон Скіт

5
@JonSkeet: Дякую за виправлення Пітера; Я використовую той "менш ефективний" метод, не усвідомлюючи це деякий час.
Дж. Брайан Прайс

5
Дуже хороша! Я збираюся безсоромно плагіатувати - ер, розщедритися. ;)
Джон Кумбс

3
Мабуть, MS вирішила, що це досить добре, щоб додати System.Collections.Generic.CollectionExtensions, як я тільки що спробував, і це є.
theMayer

19

Проведення цих методів розширення може допомогти ..

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
Остання перевантаження цікава. Так як немає нічого , щоб вибрати з , це просто форма ледачою оцінки?
ПАМ'ЯТ

1
@MEMark yes селектор значень запускається лише за потреби, тому певним чином це можна вважати ледачим оцінкою, але його не відкладеним виконанням, як у контексті Linq. Але лінива оцінка тут не залежить від того, щоб вибрати щось із фактора, можна, звичайно, зробити вибір Func<K, V>.
nawfal

1
Чи є цей третій метод загальнодоступним, оскільки він корисний сам по собі? Тобто, чи думаєте ви, що хтось може пройти в лямбда, який використовує поточний час, або розрахунок на основі ... хм. Я б очікував, що я буду мати лише другий метод значення V; return dict.TryGetValue (ключ, значення)? значення: defVal;
Джон Кумбс

1
@JCoombs справа, третя перевантаження там з тією ж метою. І ви, звичайно, можете це зробити при другому перевантаженні, але я це робив трохи сухим (щоб я не повторював логіку).
nawfal

4
@nawfal Хороший момент. Залишатися сухим важливіше, ніж уникати однієї кривдної методики.
Джон Кумбс

19

Якщо хтось використовує .net core 2 і вище (C # 7.X), вводиться клас CollectionExtensions і може використовувати метод GetValueOrDefault, щоб отримати значення за замовчуванням, якщо ключа немає в словнику.

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

4

Collections.Specialized.StringDictionaryнадає результат, що не є винятком, під час пошуку значення відсутнього ключа. Він також нечутливий до регістру за замовчуванням.

Коваджі

Він дійсний лише для спеціалізованого використання, і, будучи розробленим до дженериків, не має дуже хорошого переліку, якщо вам потрібно переглянути всю колекцію.


4

Якщо ви використовуєте .Net Core, ви можете використовувати метод CollectionExtensions.GetValueOrDefault . Це те саме, що реалізація передбачена у прийнятій відповіді.

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

3
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

Можна було б визначити інтерфейс для функції пошуку ключів словника. Я, мабуть, визначив це як щось подібне:

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об’єкт у класі, який реалізує інтерфейс. Однак, залежно від того, чим ви займаєтесь, такий інтерфейс та відхилення, яке він дозволяє, можуть зробити вартими зусиль.


1

Якщо ви використовуєте 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

Це питання допомогло підтвердити, що TryGetValueп'єсиFirstOrDefault роль.

Однією з цікавих особливостей C # 7, яку я хотів би зазначити, є функція " out of змінних ", і якщо ви додасте до рівняння нульовий умовний оператор з C # 6, ваш код може бути набагато простішим, не потребуючи додаткових методів розширення.

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

Мінус цього полягає в тому, що ви не можете робити все так, як це;

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

Якщо нам знадобиться / хочемо це зробити, ми повинні зашифрувати такий метод розширення, як Джон.


1

Ось версія @ 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

Я використовував інкапсуляцію, щоб створити IDictionary з поведінкою, дуже схожою на карту STL , для тих із вас, хто знайомий з c ++. Для тих, хто не:

  • індексатор 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();
    }
}

1

Оскільки .NET core 2.0 ви можете використовувати:

myDict.GetValueOrDefault(someKeyKalue)

0

Що з цим однолінійним лайнером, який перевіряє наявність ключа, який використовує, ContainsKeyа потім повертає або нормально отримане значення, або значення за замовчуванням за допомогою умовного оператора?

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

Не потрібно впроваджувати новий клас словника, який підтримує значення за замовчуванням, просто замініть свої заяви пошуку на короткий рядок вище.


5
У ньому є два пошукові місця замість одного, і він вводить умову гонки, якщо ви використовуєте ConcurrentDictionary.
пієдар

0

Це може бути однолінійним інструментом для перевірки 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

Спробуйте в Інтернеті


-1

Ні, тому що в іншому випадку, як би ви знали різницю, коли ключ існує, але зберігається нульове значення? Це може бути суттєвим.


11
Це може бути - але в деяких ситуаціях ви цілком можете знати, що це не так. Я можу бачити, що це іноді корисно.
Джон Скіт

8
Якщо ви знаєте, що нуля там немає - це надзвичайно приємно. Приєднання цих речей до Linq (що я все роблю останнім часом) робить набагато простіше
TheSoftwareJedi

1
Використовуючи метод ContainsKey?
MattDavey

4
Так, це насправді дуже корисно. У Python це є, і я використовую його набагато більше, ніж звичайний метод, коли мені трапляється в Python. Економить написання багато зайвих, якщо заяви, які просто перевіряють на нульовий результат. (Ви, звичайно, маєте рацію, що нульова ситуація буває корисною, але зазвичай я бажаю "" або 0 замість цього.)
Джон Кумбс,

Я відмовився від цього. Але якби це було сьогодні, я б не мав. Скасуйте зараз :). Я підтримав коментарі, щоб висловити свою думку :)
nawfal
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.