TryGetValue보다 C # 사전을 사용하는 더 좋은 방법이 있습니까?


19

온라인에서 자주 질문을 찾고 있으며 많은 솔루션에 사전이 있습니다. 그러나 구현하려고 할 때마다 코드 에서이 끔찍한 악취가납니다. 예를 들어 값을 사용할 때마다 :

int x;
if (dict.TryGetValue("key", out x)) {
    DoSomethingWith(x);
}

기본적으로 다음을 수행하는 4 줄의 코드입니다. DoSomethingWith(dict["key"])

out 키워드를 사용하면 함수가 매개 변수를 변경하기 때문에 안티 패턴을 사용하는 것으로 들었습니다.

또한 키와 값을 뒤집는 "역전 된"사전이 필요한 경우가 종종 있습니다.

마찬가지로, 나는 종종 사전의 항목을 반복하고 키 또는 값을 목록 등으로 변환하여 더 잘 수행하고 싶습니다.

사전을 사용하는 것이 더 좋고 우아한 방법이 거의 항상있는 것처럼 느껴지지만 실력이 떨어졌습니다.


7
다른 방법이 존재할 수 있지만 일반적으로 값을 가져 오기 전에 ContainsKey를 먼저 사용 합니다.
로비 디

2
솔직히 몇 가지 예외가 있지만 사전 키가 무엇인지 미리 알고 있다면 더 나은 방법 있을 것 입니다 . 모르는 경우, 일반적으로 dict 키를 전혀 하드 코딩해서는 안됩니다. 사전은 필드가 응용 프로그램과 거의 직교하는 반 구조화 된 개체 또는 데이터를 작업하는 데 유용 합니다. IMO는 해당 분야가 관련 비즈니스 / 도메인 개념에 가까워 질수록 해당 개념을 다루는 데 도움이되는 사전이 줄어 듭니다.
svidgen

6
@RobbieDee : 그렇게하면 경쟁 조건이 만들어 지므로주의해야합니다. ContainsKey를 호출하고 값을 얻는 사이에 키를 제거 할 수 있습니다.
whatsisname

12
@whatsisname : 이러한 조건 하에서는 ConcurrentDictionary 가 더 적합합니다. System.Collections.Generic네임 스페이스의 컬렉션은 스레드로부터 안전하지 않습니다 .
Robert Harvey

4
내가 50 %를 사용하는 것을 보는 사람들의 좋은 50 %는 Dictionary그들이 정말로 원했던 것은 새로운 수업이었습니다. 그 50 % (단지)에게는 디자인 냄새입니다.
BlueRaja-대니 Pflughoeft

답변:


23

사전 (C # 또는 기타)은 단순히 키를 기준으로 값을 찾는 컨테이너입니다. 많은 언어에서 가장 일반적으로 HashMap을 구현하는 Map으로 더 정확하게 식별됩니다.

고려해야 할 문제는 키가 없을 때 발생하는 문제입니다. 일부 언어는 반환 null하거나 nil다른 동등한 값으로 작동 합니다. 값이 존재하지 않음을 알리지 않고 값을 자동으로 기본값으로 설정합니다.

더 나은지 나쁜지에 대해 C # 라이브러리 디자이너는이 동작을 처리하는 관용구를 생각해 냈습니다. 존재하지 않는 값을 찾는 기본 동작은 예외를 발생시키는 것이라고 추론했습니다. 예외를 피하려면 Try변형을 사용할 수 있습니다 . 문자열을 정수 또는 날짜 / 시간 객체로 구문 분석하는 데 사용하는 것과 동일한 접근 방식입니다. 기본적으로 그 영향은 다음과 같습니다.

T count = int.Parse("12T45"); // throws exception

if (int.TryParse("12T45", out count))
{
    // Does not throw exception
}

그리고 그것은 인덱서가 다음을 위임하는 사전으로 넘어갔습니다 Get(index).

var myvalue = dict["12345"]; // throws exception
myvalue = dict.Get("12345"); // throws exception

if (dict.TryGet("12345", out myvalue))
{
    // Does not throw exception
}

이것은 단순히 언어가 설계된 방식입니다.


해야 out변수는 낙담?

C #은 처음 사용하는 언어가 아니며 특정 상황에서 목적이 있습니다. 동시성이 높은 시스템을 구축하려는 경우 out동시성 경계에서 변수를 사용할 수 없습니다 .

여러 가지면에서 언어 및 핵심 라이브러리 제공 업체가지지하는 관용구가있는 경우 API에서이 관용구를 채택하려고합니다. 그러면 API가 더 일관성 있고 집에서 해당 언어로 느껴집니다. 따라서 Ruby로 작성된 메소드는 C #, C 또는 Python으로 작성된 메소드처럼 보이지 않습니다. 각각 코드를 작성하고 API 사용자가 더 빨리 학습하는 데 도움이되는 작업 방식을 선호합니다.


일반적으로지도는 안티 패턴입니까?

그들은 그들의 목적을 가지고 있지만, 여러 번 당신이 가진 목적에 대한 잘못된 해결책 일 수 있습니다. 특히 양방향 매핑이 필요한 경우 필요합니다. 많은 컨테이너와 데이터 구성 방법이 있습니다. 사용할 수있는 많은 접근 방법이 있으며 때로는 해당 컨테이너를 선택하기 전에 약간 생각해야합니다.

양방향 맵핑 값의 목록이 매우 짧은 경우 튜플 목록 만 필요할 수 있습니다. 또는 구조체의 목록에서 매핑의 양쪽에서 첫 번째 일치 항목을 쉽게 찾을 수 있습니다.

문제 영역을 생각하고 작업에 가장 적합한 도구를 선택하십시오. 없는 경우 만듭니다.


4
"그러면 튜플 목록 만 필요할 수도 있습니다. 또는 구조체의 양쪽에서 첫 번째 일치 항목을 쉽게 찾을 수있는 구조체 목록 만 있으면됩니다." -작은 세트에 대한 최적화가있는 경우 사용자 코드에 고무 스탬프가 아닌 라이브러리에서 만들어야합니다.
Blrfl

튜플 또는 사전 목록을 선택하면 구현 세부 사항입니다. 문제 영역을 이해하고 업무에 적합한 도구를 사용하는 것이 중요합니다. 분명히 목록이 있고 사전이 존재합니다. 일반적인 경우 사전은 정확하지만 하나 또는 두 개의 애플리케이션의 경우 목록을 사용해야합니다.
Berin Loritsch

3
그러나 동작이 여전히 동일하면 해당 결정이 라이브러리에 있어야합니다. 나는 엔트리 수가 적을 것이라는 것을 사전에 말하면 알고리즘을 전환하는 다른 언어로 컨테이너 구현을 실행했습니다 . 나는 당신의 대답에 동의하지만, 일단 이것이 이해되면, 그것은 라이브러리에 있어야합니다, 아마도 SmallBidirectionalMap 일 것입니다.
Blrfl

5
"더 나은지 나쁜지에 대해 C # 라이브러리 디자이너는이 동작을 처리 할 관용구를 고안했습니다." – 나는 당신이 머리에 못을 박았다고 생각합니다 : 그것들은 선택적인 리턴 타입 인 기존의 널리 사용되는 것을 사용하는 대신 관용구와 함께 "올랐습니다".
Jörg W Mittag

3
@ JörgWMittag 만약 당신이 같은 것에 대해 이야기하고 있다면 Option<T>, 패턴 일치가 없기 때문에 C # 2.0에서 메소드를 사용하기가 훨씬 어려울 것이라고 생각합니다.
svick

21

해시 테이블 / 사전의 일반적인 원칙에 대한 좋은 답변이 여기에 있습니다. 하지만 코드 예제를 다루겠다고 생각했습니다.

int x;
if (dict.TryGetValue("key", out x)) 
{
    DoSomethingWith(x);
}

C # 7 (약 2 세라고 생각)부터 다음과 같이 단순화 할 수 있습니다.

if (dict.TryGetValue("key", out var x))
{
    DoSomethingWith(x);
}

물론 한 줄로 줄일 수 있습니다.

if (dict.TryGetValue("key", out var x)) DoSomethingWith(x);

키가 없을 때의 기본값이 있으면 다음과 같이 될 수 있습니다.

DoSomethingWith(dict.TryGetValue("key", out var x) ? x : defaultValue);

따라서 합리적으로 최근에 추가 된 언어를 사용하여 간단한 형식을 얻을 수 있습니다.


1
V7 구문에 좋은 전화, 좋은 작은 옵션은 VAR +1의 사용을 허용하면서 추가로 정의 행 잘라해야합니다
BrianH

2
경우 있음을 유의하십시오 "key"존재하지 않는, x초기화됩니다default(TValue)
피터 Duniho

일반적인 확장으로 좋은 수 있습니다, 너무, 같은 호출"key".DoSomethingWithThis()
로스 압착기

getOrElse("key", defaultValue) Null 개체 를 사용하는 디자인 은 여전히 ​​내가 가장 좋아하는 패턴입니다. 그런 식으로 작업하면 TryGetValuetrue 또는 false를 반환 하는지 상관하지 않습니다 .
candied_orange

1
이 답변은 컴팩트하기 위해 컴팩트 코드를 작성하는 것이 좋지 않다는 면책이 필요합니다. 코드를 읽기가 더 어려워 질 수 있습니다. 또한, 내가 올바르게 기억한다면 TryGetValue원자 / 스레드 안전하지 않기 때문에, 존재 여부를 점검하고 다른 하나는 값
Marie

12

out 매개 변수와 함께 TryGet 스타일 함수를 사용하는 것이 관용적 C #이므로 코드 냄새 나 반 패턴이 아닙니다. 그러나 C #에는 사전 작업에 사용할 수있는 3 가지 옵션이 있으므로 상황에 맞는 올바른 옵션을 사용해야합니다. 나는 out 매개 변수를 사용하여 문제가 있다는 소문이 어디에서 왔는지 알기 때문에 결국 처리 할 것입니다.

C # 사전 작업시 사용할 기능 :

  1. 키가 사전에 있다고 확신하면 Item [TKey] 속성을 사용하십시오.
  2. 키가 일반적으로 사전에 있어야하지만 존재하지 않는 것이 불량 / 희귀 / 문제인 경우 오류가 발생하도록 Try ... Catch를 사용해야 오류를 정상적으로 처리 할 수 ​​있습니다.
  3. 키가 사전에 있는지 확실하지 않으면 out 매개 변수와 함께 TryGet을 사용하십시오.

이를 정당화하려면 "설명"아래의 Dictionary TryGetValue 설명서 만 참조하면됩니다 .

이 메서드는 ContainsKey 메서드와 Item [TKey] 속성의 기능을 결합합니다.

...

코드에서 사전에없는 키에 자주 액세스하려고하면 TryGetValue 메서드를 사용하십시오. 이 메소드를 사용하는 것은 Item [TKey] 특성에 의해 발생 된 KeyNotFoundException을 포착하는 것보다 효율적입니다.

이 방법은 O (1) 연산에 접근합니다.

TryGetValue가 존재하는 전체적인 이유는 DictionaryKey를 두 번 검색 할 필요가없는 상태에서 ContainsKey 및 Item [TKey]를 사용하는보다 편리한 방법으로 작동하기위한 것입니다. 선택.

실제로이 간단한 최대 값으로 인해 원시 사전을 사용한 적이 거의 없습니다 . 필요한 기능을 제공 하는 가장 일반적인 클래스 / 컨테이너 선택하십시오 . 사전은 키가 아닌 값을 기준으로 검색하도록 설계되지 않았으므로 원하는 경우 대체 구조를 사용하는 것이 더 합리적 일 수 있습니다. 작년 한 해 동안 개발 한 프로젝트에서 Dictionary를 한 번 사용한 적이있을 것입니다. 왜냐하면 내가하려고하는 일에 적합한 도구는 아니었기 때문입니다. 사전은 확실히 C # 툴박스의 스위스 군용 칼이 아닙니다.

매개 변수가없는 것은 무엇입니까?

CA1021 : 매개 변수를 피하십시오

반환 값은 일반적이며 많이 사용되지만 out 및 ref 매개 변수를 올바르게 적용하려면 중간 설계 및 코딩 기술이 필요합니다. 일반 사용자를 위해 디자인하는 라이브러리 설계자는 사용자가 매개 변수를 사용하거나 참조하는 작업을 마스터하지 않아도됩니다.

나는 그것이 매개 변수가 반 패턴과 같다는 것을 들었던 곳이라고 추측합니다. 모든 규칙과 마찬가지로 '이유'를 이해하기 위해 자세히 읽으십시오.이 경우 Try 패턴이 규칙을 위반하지 않는 방법에 대한 명시 적 언급이 있습니다 .

System.Int32.TryParse와 같은 Try 패턴을 구현하는 메서드는이 위반을 발생시키지 않습니다.


그 지침의 전체 목록은 더 많은 사람들이 읽고 싶어하는 것입니다. 따르는 사람들을 위해 여기에 그 뿌리가 있습니다. docs.microsoft.com/en-us/visualstudio/code-quality/…
Peter Wone

10

내 의견으로는 다른 언어로 된 많은 상황에서 코드를 상당히 정리하는 C # 사전에서 누락 된 두 가지 방법이 있습니다. 첫 번째는를 반환하여 Option스칼라에서 다음과 같은 코드를 작성할 수 있습니다.

dict.get("key").map(doSomethingWith)

두 번째는 키를 찾지 못하면 사용자 지정 기본값을 반환합니다.

doSomethingWith(dict.getOrElse("key", "key not found"))

Try패턴 과 같이 언어가 적절할 때 제공하는 관용구를 사용한다고 할 말이 있지만 , 언어가 제공 하는 것만 사용해야 한다는 의미는 아닙니다 . 우리는 프로그래머입니다. 특히 반복이 많이 필요하지 않은 경우 특정 상황을 이해하기 쉽도록 새로운 추상화를 생성해도됩니다. 역방향 조회 또는 값 반복과 같은 무언가가 자주 필요한 경우이를 수행하십시오. 원하는 인터페이스를 만듭니다.


나는 두 번째 옵션을 좋아한다. 어쩌면 나는 확장 방법이나 두 가지를 작성해야 할 것이다 :)
Adam B

2
더하기 측면 C # 확장 방법은 이러한 것을 직접 구현할 수 있음을 의미합니다
jk.

5

기본적으로 다음을 수행하는 4 줄의 코드입니다. DoSomethingWith(dict["key"])

나는 이것이 우아하지 않다는 데 동의합니다. 이 경우에 값을 구조체 형식으로 사용하는 메커니즘은 다음과 같습니다.

public static V? TryGetValue<K, V>(
      this Dictionary<K, V> dict, K key) where V : struct => 
  dict.TryGetValue(key, out V v)) ? new V?(v) : new V?();

이제 새로운 버전의 TryGetValue리턴이 int?있습니다. 그런 다음 확장을 위해 비슷한 트릭을 수행 할 수 있습니다 T?.

public static void DoIt<T>(
      this T? item, Action<T> action) where T : struct
{
  if (item != null) action(item.GetValueOrDefault());
}

그리고 이제 그것을 합치십시오.

dict.TryGetValue("key").DoIt(DoSomethingWith);

우리는 단 하나의 명확한 진술에 달려 있습니다.

out 키워드를 사용하면 함수가 매개 변수를 변경하기 때문에 안티 패턴을 사용하는 것으로 들었습니다.

나는 약간 덜 강력하게 말하고, 가능할 때 돌연변이를 피하는 것이 좋은 생각이라고 말합니다.

나는 종종 "역전 된 (reversed)"사전을 필요로하는데, 여기서 키와 값을 뒤집습니다.

그런 다음 양방향 사전을 구현하거나 얻습니다. 그것들은 작성하기 간단하거나 인터넷에서 많은 구현이 가능합니다. 예를 들어 다음과 같은 많은 구현이 있습니다.

/programming/268321/bidirectional-1-to-1-dictionary-in-c-sharp

마찬가지로, 나는 종종 사전의 항목을 반복하고 키 또는 값을 목록 등으로 변환하여 더 잘 수행하고 싶습니다.

물론 우리 모두는 그렇습니다.

사전을 사용하는 것이 더 좋고 우아한 방법이 거의 항상있는 것처럼 느껴지지만 실력이 떨어졌습니다.

" Dictionary내가 원하는 정확한 작업을 구현 한 것 이외의 다른 클래스가 있다고 가정 해보십시오 . 그 클래스는 어떤 모습입니까?" 그런 다음 해당 질문에 대답하면 해당 클래스를 구현하십시오 . 당신은 컴퓨터 프로그래머입니다. 컴퓨터 프로그램을 작성하여 문제를 해결하십시오!


감사. 액션 방법이 실제로 무엇을하고 있는지 좀 더 설명 할 수 있다고 생각하십니까? 나는 행동이 처음이다.
Adam B

1
@AdamB 액션은 void 메소드를 호출하는 기능을 나타내는 객체입니다. 보시다시피, 호출자 측에서 매개 변수 목록이 조치의 일반 유형 인수와 일치하는 void 메소드의 이름을 전달합니다. 호출자 측에서 다른 메소드와 같이 조치가 호출됩니다.
Eric Lippert

1
@AdamB : 당신은 생각할 수있는 Action<T>매우 유사하다고 interface IAction<T> { void Invoke(T t); }하지만, 인터페이스 것 "구현"에 대한이 매우 관대 한 규칙 및 방법에 대한 Invoke호출 할 수 있습니다. 더 알고 싶다면 C #의 "delegates"에 대해 배우고 람다 식에 대해 배우십시오.
Eric Lippert

알았어. 알았어. 액션을 통해 메소드를 DoIt ()의 매개 변수로 넣을 수 있습니다. 그리고 그 방법을 호출합니다.
Adam B

@AdamB : 정확히 맞습니다. "고차 함수를 사용한 함수형 프로그래밍"의 예입니다. 대부분의 함수는 데이터를 인수로 사용합니다. 고차 함수는 함수 를 인수로 취한 다음 해당 함수로 작업을 수행합니다. 더 많은 C #을 배우면 LINQ가 전체적으로 고차 함수로 구현된다는 것을 알 수 있습니다.
Eric Lippert

4

TryGetValue()당신이 "키"사전 여부 내의 키와 존재 여부를 모르는 경우 구조 그렇지 않으면에만 필요합니다 DoSomethingWith(dict["key"])완벽하게 유효합니다.

"더러워지지 않은"접근 방식은 ContainsKey()대신 검사 로 사용 하는 것입니다.


6
""더러워지지 않은 "접근 방식은 ContainsKey ()를 대신 확인하는 것입니다." 동의하지 않습니다. 차선책 인 것처럼 TryGetValue, 빈 케이스를 다루는 것을 잊어 버리기가 어렵습니다. 이상적으로는 이것이 선택 사항을 반환하기를 바랍니다.
알렉산더-복원 모니카

1
ContainsKeyTOCTOU (Check To Time of Time) 취약점 인 멀티 스레드 응용 프로그램에 대한 접근 방식에 잠재적 인 문제가 있습니다. 어떤 다른 스레드가 호출 사이의 키 삭제 경우 ContainsKeyGetValue?
David Hammen

2
@JAD 옵션 구성. 다음을 가질 수 있습니다Optional<Optional<T>>
Alexander-Reinstate Monica

2
@DavidHammen 명확하게하려면에 필요 TryGetValue합니다 ConcurrentDictionary. 일반 사전은 동기화되지 않을 것입니다
Alexander-Reinstate Monica

2
@JAD 아니오, (Java의 선택적 유형이 불면 시작하지 마십시오). 더 추상적으로 말하고 있습니다
Alexander-Reinstate Monica

2

다른 답변에는 큰 요점이 포함되어 있으므로 여기서 다시 언급하지는 않지만 지금까지는 무시할 것 같습니다.

마찬가지로, 나는 종종 사전의 항목을 반복하고 키 또는 값을 목록 등으로 변환하여 더 잘 수행하고 싶습니다.

실제로는 다음을 구현하므로 사전을 반복하는 것이 매우 쉽습니다 IEnumerable.

var dict = new Dictionary<int, string>();

foreach ( var item in dict ) {
    Console.WriteLine("{0} => {1}", item.Key, item.Value);
}

Linq를 선호한다면 그것도 잘 작동합니다.

dict.Select(x=>x.Value).WhateverElseYouWant();

대체로 사전을 반 패턴으로 생각하지는 않습니다. 특정 용도의 특정 도구 일뿐입니다.

또한 더 자세한 내용 SortedDictionary은 RB 트리를 사용하여 더 예측 가능한 성능을 확인하십시오. 그리고 SortedList검색 속도를 위해 삽입 속도를 희생하지만 사전에 고정 된 정렬 세트). 나는 교체하는 경우 했어 DictionaryA를가 SortedDictionary빠른 진도의 순서로 실행 결과를 (하지만 너무 둥근 다른 방법으로 일어날 수있다).


1

사전을 사용하는 것이 어색하다고 생각되면 문제에 대한 올바른 선택이 아닐 수 있습니다. 사전은 훌륭하지만 한 의견자가 알아 차린 것처럼 종종 수업이어야 할 무언가에 대한 바로 가기로 사용됩니다. 또는 사전 자체가 핵심 저장 방법으로 맞을 수도 있지만 원하는 서비스 방법을 제공하기 위해 래퍼 클래스가 있어야합니다.

Dict [key]와 TryGet에 대해 많은 이야기가있었습니다. 내가 많이 사용하는 것은 KeyValuePair를 사용하여 사전을 반복하는 것입니다. 분명히 이것은 덜 일반적으로 알려진 구조입니다.

사전의 주요 장점은 항목 수가 증가함에 따라 다른 컬렉션에 비해 실제로 빠릅니다. 키가 많이 있는지 확인해야하는 경우 사전 사용이 적절한 지 스스로에게 물어볼 수 있습니다. 고객은 일반적으로 무엇을 넣었는지, 따라서 쿼리하기에 안전한지 알아야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.