web-dev-qa-db-ja.com

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

キーが欠落している場合、ディクショナリへのインデクサーは例外をスローします。代わりにdefault(T)を返すIDictionaryの実装はありますか?

「TryGetValue」メソッドについては知っていますが、linqでは使用できません。

これは必要なことを効率的に行うでしょうか?:

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

ハッシュルックアップを使用する代わりに、キーを反復処理すると思うので、そうなるとは思いません。

111
TheSoftwareJedi

実際、それはまったく効率的ではありません。

常に拡張メソッドを書くことができます:

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)
130
Jon Skeet

これらの拡張メソッドを実行すると役立ちます。

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

.net core 2以上(C#7.X)を使用している場合、 CollectionExtensions クラスが導入され、キーが存在しない場合に GetValueOrDefault メソッドを使用してデフォルト値を取得できます辞書。

7
cdev

Collections.Specialized.StringDictionaryは、欠落しているキーの値を検索するときに、例外以外の結果を提供します。また、デフォルトでは大文字と小文字が区別されません。

注意事項

特殊な用途にのみ有効であり、ジェネリックよりも前に設計されているため、コレクション全体を確認する必要がある場合、非常に優れた列挙子はありません。

3
Mark Hurd

ASP.NET MVCを使用している場合、ジョブを実行するRouteValueDictionaryクラスを活用できます。

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

辞書のキー検索機能のインターフェースを定義できます。私はおそらく次のようなものとして定義します:

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
supercat

この質問は、ここでTryGetValueFirstOrDefaultの役割を果たすことを確認するのに役立ちました。

言及したい興味深いC#7機能の1つは、 out variables 機能です。C#6の null-conditional operator を式に追加すると、コードはかなり大きくなります。追加の拡張メソッドを必要とせず、よりシンプルに。

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

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

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

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

1
hardkoded
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
Brian

これは、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;
_

default(TV)を返したい場合を最適化するために、2つの関数を用意する方が効率的です。

_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
NetMage

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

オンラインで試す

0
Aryan Firouzian

カプセル化を使用して、C++に精通しているユーザー向けにSTL mapと非常によく似た動作を持つIDictionaryを作成しました。そうでない人のために:

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

ContainsKeyを使用してキーが存在するかどうかを確認し、通常取得された値または条件演算子を使用してデフォルト値を返すこの1行はどうでしょうか。

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

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

0
Byte Commander