キーがない場合に、スローする代わりにデフォルト値を返すIDictionary実装はありますか?


129

辞書へのインデクサーは、キーがない場合に例外をスローします。代わりにdefault(T)を返すIDictionaryの実装はありますか?

「TryGetValue」メソッドについては知っていますが、それをlinqで使用することは不可能です。

これは私が必要とすることを効率的に行いますか?:

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

ハッシュルックアップを使用する代わりにキーを反復するのではないかと思います。


10
この後者の質問も参照してください。この質問の複製としてマークされていますが、答えは異なります。キーが存在しない場合、辞書はデフォルト値を返します
Rory O'Kane

2
@dylanそれはnullではなく、欠落しているキーで例外をスローします。さらに、ディクショナリが使用されるすべての場所でのデフォルトを決定する必要があります。この質問の年齢にも注意してください。私たちは持っていませんでした?? とにかく8年前のオペレーター
TheSoftwareJedi 2017年

回答:


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)
  • out変数(C#7.0)
  • デフォルトのリテラル(C#7.1)

2
またはよりコンパクトに:return (dictionary.ContainsKey(key)) ? dictionary[key] : default(TValue);
Peter Gluck

33
@PeterGluck:よりコンパクトになりますが、効率は低下します...キーが存在する場合に、なぜ2つのルックアップを実行するのですか?
Jon Skeet

5
@JonSkeet:Peterを訂正してくれてありがとう。この「効率の悪い」方法は、しばらく気づかずに使ってきました。
Jブライアン価格

5
非常に素晴らしい!私は恥知らずに盗用するつもりです-ええ、それをフォークします。;)
Jon Coombs 2014年

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
最後のオーバーロードは興味深いものです。選択するためには何もありませんのでからは、これは単に遅延評価の形ですか?
MEMark、2014年

1
@MEMark yes値セレクターは必要な場合にのみ実行されるため、遅延評価と言うことができますが、Linqコンテキストの場合のように実行は遅延されません。ただし、ここでの遅延評価は要素から選択するものに依存していませんFunc<K, V>。もちろん、セレクターを作成できます。
nawfal 2014年

1
それ自体が有用であるため、その3番目のメソッドはパブリックですか?つまり、誰かが現在の時刻を使用するラムダを渡す可能性があると考えていますか、それとも...に基づく計算です。私は2番目のメソッドにV値を実行させることだけを期待していました。return dict.TryGetValue(key、out value)?値:defVal;
Jon Coombs 2014年

1
@JCoombs右、3番目のオーバーロードは同じ目的のためにあります。もちろん、2番目のオーバーロードでも実行できますが、少し論理的にしていました(ロジックを繰り返さないようにするため)。
nawfal 14年

4
@nawfal良い点。乾いた状態を維持することは、1回のメソッド呼び出しを避けるよりも重要です。
Jon Coombs、2014年

19

誰かが.netコア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の興味深い機能の1つに変数出力機能があります。C#6 のnull条件演算子を方程式に追加すると、追加の拡張メソッドを必要とせずに、コードをはるかに簡単にすることができます。

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

これの欠点は、このようにすべてをインラインで実行できないことです。

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

これを行う必要がある場合は、Jonのような1つの拡張メソッドをコーディングする必要があります。


1

以下は、C#7.1の世界向けの@JonSkeetのバージョンで、オプションのデフォルトを渡すこともできます。

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

戻りたい場合を最適化するには、2つの関数を使用する方が効率的です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つに実際の関数本体(gasp!)が必要です。


1

カプセル化を使用して、c ++に精通している方のために、STLマップに非常に似た動作のIDictionaryを作成しました。そうでない人のために:

  • インデクサGET {} SafeDictionaryに戻り以下のキーが存在しない場合、デフォルト値は、デフォルト値を持つ辞書にそのキーを追加します。最終的に表示されるか表示される可能性が高いアイテムを検索しているため、これは多くの場合望ましい動作です。
  • メソッドAdd(TK key、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();
    }
}


0

を使用してキーが存在するかどうかを確認しContainsKey、通常は取得した値または条件演算子を使用してデフォルト値を返すこのワンライナーはどうですか?

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

デフォルト値をサポートする新しいディクショナリクラスを実装する必要はありません。ルックアップステートメントを上記の短い行に置き換えるだけです。


5
これには1つではなく2つのルックアップがあり、ConcurrentDictionaryを使用している場合に競合状態が発生します。
ピエダー

0

であるTryGetValue場合、デフォルト値をチェックして返すのは1行の場合があり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

いいえ。それ以外の場合、キーは存在するがnull値が格納されている場合、どのようにして違いを知ることができるでしょうか。それは重要かもしれません。


11
可能性はありますが、状況によってはそうではないことをよく知っているかもしれません。これは時々役に立ちます。
Jon Skeet、

8
nullがないことがわかっている場合-これは非常に便利です。Linqでこれらのものに参加すること(これが最近私が行っていることのすべてです)を非常に簡単にします
TheSoftwareJedi 09

1
ContainsKeyメソッドを使用して?
MattDavey、2011

4
はい、実際には非常に便利です。Pythonにはこれがあり、たまたまPythonを使用しているときは、プレーンな方法よりもはるかに多く使用しています。結果がnullかどうかをチェックしているだけのifステートメントをたくさん書く必要がなくなります。(もちろん、その通りです。nullは時々役立つことがありますが、通常は代わりに ""または0が必要です。)
Jon Coombs

私はこれに賛成していた。しかし、それが今日だったとしたら、私にはありません。今すぐキャンセルできません:) 私は私の意見を述べるためにコメントを賛成しました:)
nawfal 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.