누락 된 키에서 던지기 대신 기본값을 반환하는 IDictionary 구현이 있습니까?


129

키가 없으면 Dictionary의 인덱서에서 예외가 발생합니다. 대신 default (T)를 반환하는 IDictionary의 구현이 있습니까?

"TryGetValue"메소드에 대해 알고 있지만 linq와 함께 사용할 수 없습니다.

이것이 내가 필요한 것을 효율적으로 수행 할 것인가? :

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

해시 조회를 사용하는 대신 키를 반복한다고 생각하므로 그렇게 생각하지 않습니다.


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);
Peter Gluck

33
@ PeterGluck : 더 작지만 효율적이지 않습니다 ... 키가있는 경우 왜 두 번의 조회를 수행합니까?
Jon Skeet

5
@ JonSkeet : 피터를 수정 주셔서 감사합니다; 나는 잠시 동안 그것을 깨닫지 못한 채 "효율적이지 않은"방법을 사용해왔다.
J Bryan Price

5
아주 좋아요! 나는 뻔뻔스럽게 표절 할 것입니다. ;)
Jon Coombs

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

1
@MEMark yes 값 선택기는 필요한 경우에만 실행되므로 지연 평가라고 할 수 있지만 Linq 컨텍스트에서와 같이 실행이 지연되지는 않습니다. 그러나 여기서 게으른 평가는 요인에서 선택할 항목에 의존하지 않으며 물론 선택기를 만들 수 있습니다 Func<K, V>.
nawfal

1
세 번째 방법은 그 자체로 유용하기 때문에 공개입니까? 즉, 누군가 현재 시간을 사용하는 람다 또는 흠에 근거한 계산을 전달할 수 있다고 생각합니까? 나는 두 번째 방법이 V 값을 갖도록 기대했을 것이다. return dict.TryGetValue (key, out value)? 값 : defVal;
Jon Coombs

1
@JCoombs는 세 번째 과부하가 같은 목적으로 존재합니다. 그리고 물론 두 번째 과부하로 그렇게 할 수는 있지만 조금 건조 하게 만들었습니다 (논리를 반복하지 않도록).
nawfal

4
@nawfal 좋은 지적입니다. 건조하게 유지하는 것이 한 번의 메서드 호출을 피하는 것보다 중요합니다.
Jon Coombs

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)으로 사용할 수 없습니다 . 그러나 mutable 을 immutable로 사용하는 데 아무런 문제가 없습니다 . 완벽하게 잘 구성된 표현으로 간주되어야하기 때문에 ( 사전을 검색하지 않고도 유형이 아닌 모든 것을 반환해야 합니다 ).Dictionary(Of Animal, String)SomeDictionaryOfCat.Add(FionaTheFish, "Fiona")Dictionary(Of Cat, String)Dictionary(Of Animal, String)SomeDictionaryOfCat.Contains(FionaTheFish)falseCat

불행히도 실제로 이러한 인터페이스를 사용할 수있는 유일한 방법은 인터페이스 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 variables 기능이며, 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

다음은 선택적 기본값을 전달할 수있는 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).

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 제안 세미콜론 연산자)가 없으므로 과부하 중 하나에 대한 실제 함수 본문 (gasp!)이 있어야합니다.


1

나는 캡슐화를 사용하여 C ++에 익숙한 사람들을 위해 STL 맵 과 매우 유사한 동작을 가진 IDictionary를 만들었습니다 . 그렇지 않은 사람들을 위해 :

  • 반환 아래 SafeDictionary에서 인덱서의 get {} 키가 존재하지 않는, 경우 디폴트 값 기본 값으로 사전에 해당 키를 추가합니다. 결국 나타나거나 나타날 가능성이 높은 항목을 찾을 때 종종 바람직한 동작입니다.
  • 메소드 Add (TK 키, TV val)는 AddOrUpdate 메소드로 작동하여 던지기 대신 존재하는 경우 존재하는 값을 대체합니다. m $에 AddOrUpdate 메소드가없는 이유를 알 수 없으며 매우 일반적인 시나리오에서 오류를 발생시키는 것이 좋습니다.

TL / DR-SafeDictionary는 컴퓨터의 메모리 부족 (또는 화재)과 같은 악의적 인 시나리오 이외의 상황에서는 예외가 발생하지 않도록 작성되었습니다 . Adder를 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;

기본값을 지원하는 새 Dictionary 클래스를 구현할 필요가 없습니다. 간단히 위의 짧은 줄로 조회 문을 바꾸십시오.


5
여기에는 하나가 아닌 두 개의 조회가 있으며 ConcurrentDictionary를 사용하는 경우 경쟁 조건이 발생합니다.
piedar

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

그렇지 않으면 키가 있지만 null 값을 저장할 때의 차이점을 어떻게 알 수 있습니까? 중요 할 수 있습니다.


11
그것은 가능할 수도 있지만 어떤 상황에서는 그렇지 않다는 것을 잘 알고있을 것입니다. 때때로 이것이 유용하다는 것을 알 수 있습니다.
Jon Skeet

8
null이 없다는 것을 알고 있다면 이것은 매우 좋습니다. Linq (이것이 내가 최근에하는 전부 임)에서 이러한 것들을 결합하는 것이 훨씬 쉬워진다
TheSoftwareJedi

1
ContainsKey 메서드를 사용하여?
MattDavey

4
예, 실제로 매우 유용합니다. 파이썬에는 이것이 있으며 파이썬에있을 때 일반 방법보다 훨씬 많이 사용합니다. null 결과 만 확인하는 추가 if 문을 많이 쓰지 않아도됩니다. (물론 null은 때때로 유용하지만, 보통은 ""또는 0을 원합니다.)
Jon Coombs

나는 이것을 높이 평가했다. 그러나 그것이 오늘이라면 나는 없을 것입니다. 캔트 지금 취소 :). 나는 내 의견을
밝히기
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.