사전에 AddRange가없는 이유는 무엇입니까?


115

제목은 충분히 기본적입니다. 내가 할 수없는 이유 :

Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());


2
AddRange가없는 많은 것들이 있는데, 항상 저를 미스터리하게 만들었습니다. Collection <>처럼. List <>에 항상 이상하게 보였지만 Collection <>이나 다른 IList 및 ICollection 개체는 아닙니다.
Tim

39
여기에서 Eric Lippert를 끌어 올 것입니다. "아무도 그 기능을 설계, 지정, 구현, 테스트, 문서화 및 제공 한 적이 없기 때문입니다."
Gabe Moothart

3
@ Gabe Moothart-정확히 내가 생각했던 것입니다. 나는 다른 사람들에게 그 라인을 사용하는 것을 좋아합니다. 그들은 그것을 싫어합니다. :)
Tim

2
@GabeMoothart는 "할 수 없기 때문에", "그렇지 않기 때문에"또는 "때문에"라고 말하는 것이 더 간단하지 않을까요? -그게 말이나하는 것만 큼 재미없는 것 같아요? --- 귀하의 (인용되거나 의역 된) 응답에 대한 내 후속 질문은 "왜 아무도 그 기능을 설계, 지정, 구현, 테스트, 문서화 및 제공하지 않았습니까?"입니다. "아무도하지 않았기 때문에"로 응답하도록 강요했는데, 이는 내가 앞서 제안한 응답과 동등합니다. 내가 상상할 수있는 유일한 다른 옵션은 비꼬는 것이 아니며 OP가 실제로 궁금해하는 것입니다.
Code Jockey

답변:


76

원래 질문에 대한 의견은 이것을 아주 잘 요약합니다.

아무도 그 기능을 설계, 지정, 구현, 테스트, 문서화 및 제공하지 않았기 때문입니다. -@Gabe Moothart

그 이유는? 글쎄요, 딕셔너리 병합의 동작이 프레임 워크 지침에 맞는 방식으로 추론 될 수 없기 때문일 것입니다.

AddRange데이터 범위가 중복 항목을 허용하므로 범위가 연관 컨테이너에 의미가 없기 때문에 존재하지 않습니다. 예를 들어 IEnumerable<KeyValuePair<K,T>>해당 컬렉션 이있는 경우 중복 항목을 방지하지 않습니다.

키-값 쌍 모음을 추가하거나 두 사전을 병합하는 동작은 간단합니다. 그러나 여러 중복 항목을 처리하는 방법은 그렇지 않습니다.

중복을 처리 할 때 메서드의 동작은 어떻게됩니까?

내가 생각할 수있는 적어도 세 가지 해결책이 있습니다.

  1. 중복 된 첫 번째 항목에 대해 예외를 발생시킵니다.
  2. 모든 중복 항목을 포함하는 예외 발생
  3. 중복 무시

예외가 발생하면 원본 사전의 상태는 어떻습니까?

Add거의 항상 원자 적 작업으로 구현됩니다. 성공하고 컬렉션의 상태를 업데이트하거나 실패하고 컬렉션의 상태는 변경되지 않은 상태로 유지됩니다. 으로 AddRange복제 오류로 인해 실패 할 수 있습니다, 방법은 일관성있는 동작을 유지 Add또한 중복에 예외를 throw하여 원자 확인하고 변경으로 원래 사전의 상태를두고하는 것입니다.

API 소비자로서 중복 요소를 반복적으로 제거해야하는 것은 지루할 것입니다. 이는 모든 중복 값 AddRange을 포함하는 단일 예외를 throw해야 함을 의미 합니다.

선택은 다음과 같이 요약됩니다.

  1. 모든 중복 항목과 함께 예외를 throw하고 원본 사전은 그대로 둡니다.
  2. 중복을 무시하고 계속하십시오.

두 가지 사용 사례를 모두 지원하는 주장이 있습니다. 이를 위해 IgnoreDuplicates서명에 플래그를 추가 합니까?

IgnoreDuplicates(true로 설정) 플래그는 기본이되는 구현으로, 상당한 속도를 제공 할 것 중복 검사에 대한 코드 우회.

이제 AddRange두 경우를 모두 지원할 수 있지만 문서화되지 않은 부작용 이있는 플래그가 있습니다 (프레임 워크 디자이너가 피하기 위해 정말 열심히 노력한 것입니다).

요약

중복을 처리 할 때 명확하고 일관 적이며 예상되는 동작이 없기 때문에 모두 함께 처리하지 않고 시작할 방법을 제공하지 않는 것이 더 쉽습니다.

계속해서 사전을 병합해야하는 경우, 물론 응용 프로그램에 적합한 방식으로 작동하는 사전을 병합하는 자체 확장 메서드를 작성할 수 있습니다.


37
완전히 거짓입니다. 사전에는 AddRange (IEnumerable <KeyValuePair <K, T >> Values)가 있어야합니다.
Gusman 2015 년

19
Add를 가질 수 있으면 여러 개를 추가 할 수도 있어야합니다. 중복 키가있는 항목을 추가하려고 할 때의 동작은 단일 중복 키를 추가 할 때와 동일해야합니다.
Uriah Blatherwick 2015

5
AddMultiple과 다른 AddRange관계없이 구현이 남았습니다 될 것입니다 : 당신의 배열 예외가 발생 함 모든 중복 키? 아니면 처음 발견 한 중복 키에서 예외가 발생합니까? 예외가 발생하면 사전의 상태는 어떻습니까? 깨끗한 것, 아니면 성공한 모든 열쇠?
Alan

3
좋아, 이제 수동으로 열거 형을 반복하고 개별적으로 추가해야합니다. 중복에 대한 모든주의 사항은 언급했습니다. 프레임 워크에서 생략하면 어떻게 해결됩니까?
doug65536

4
@ doug65536 API 소비자는 이제 각 개인에 대해 수행 할 작업을 결정할 수 있기 때문에 Add각각 Add을 a로 감싸고 try...catch그런 방식으로 중복을 포착합니다. 또는 인덱서를 사용하고 첫 번째 값을 이후 값으로 덮어 씁니다. 또는을 ContainsKey시도하기 전에 사용을 사전에 확인하여 Add원래 값을 유지합니다. 프레임 워크에 AddRange또는 AddMultiple메서드 가있는 경우 발생한 일을 전달하는 유일한 간단한 방법은 예외를 통하는 것이며 관련된 처리 및 복구도 그다지 복잡하지 않을 것입니다.
Zev Spitz

36

몇 가지 해결책이 있습니다.

Dictionary<string, string> mainDic = new Dictionary<string, string>() { 
    { "Key1", "Value1" },
    { "Key2", "Value2.1" },
};
Dictionary<string, string> additionalDic= new Dictionary<string, string>() { 
    { "Key2", "Value2.2" },
    { "Key3", "Value3" },
};
mainDic.AddRangeOverride(additionalDic); // Overrides all existing keys
// or
mainDic.AddRangeNewOnly(additionalDic); // Adds new keys only
// or
mainDic.AddRange(additionalDic); // Throws an error if keys already exist
// or
if (!mainDic.ContainsKeys(additionalDic.Keys)) // Checks if keys don't exist
{
    mainDic.AddRange(additionalDic);
}

...

namespace MyProject.Helper
{
  public static class CollectionHelper
  {
    public static void AddRangeOverride<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> dicToAdd)
    {
        dicToAdd.ForEach(x => dic[x.Key] = x.Value);
    }

    public static void AddRangeNewOnly<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> dicToAdd)
    {
        dicToAdd.ForEach(x => { if (!dic.ContainsKey(x.Key)) dic.Add(x.Key, x.Value); });
    }

    public static void AddRange<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> dicToAdd)
    {
        dicToAdd.ForEach(x => dic.Add(x.Key, x.Value));
    }

    public static bool ContainsKeys<TKey, TValue>(this IDictionary<TKey, TValue> dic, IEnumerable<TKey> keys)
    {
        bool result = false;
        keys.ForEachOrBreak((x) => { result = dic.ContainsKey(x); return result; });
        return result;
    }

    public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
    {
        foreach (var item in source)
            action(item);
    }

    public static void ForEachOrBreak<T>(this IEnumerable<T> source, Func<T, bool> func)
    {
        foreach (var item in source)
        {
            bool result = func(item);
            if (result) break;
        }
    }
  }
}

즐기세요.


당신은 필요하지 않습니다 ToList(), 사전은 IEnumerable<KeyValuePair<TKey,TValue>. 또한 기존 키 값을 추가하면 두 번째 및 세 번째 메서드 메서드가 발생합니다. 좋은 생각이 아닙니다 . 찾고 TryAdd계십니까? 마지막으로, 두 번째는 교체 할 수 있습니다Where(pair->!dic.ContainsKey(pair.Key)...
파나지오티스 Kanavos

2
좋아, ToList()좋은 해결책이 아니므로 코드를 변경했습니다. try { mainDic.AddRange(addDic); } catch { do something }세 번째 방법이 확실하지 않은 경우 사용할 수 있습니다 . 두 번째 방법은 완벽하게 작동합니다.
ADM-IT

안녕하세요 Panagiotis Kanavos, 행복 하시길 바랍니다.
ADM-IT

1
고마워요 내가 훔 쳤어요
metabuddy

14

누군가가 나처럼이 질문을 접하게되면 IEnumerable 확장 메서드를 사용하여 "AddRange"를 달성 할 수 있습니다.

var combined =
    dict1.Union(dict2)
        .GroupBy(kvp => kvp.Key)
        .Select(grp => grp.First())
        .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

사전을 결합 할 때 주요 트릭은 중복 키를 처리하는 것입니다. 위의 코드에서는 부분 .Select(grp => grp.First())입니다. 이 경우 단순히 중복 그룹에서 첫 번째 요소를 가져 오지만 필요한 경우 더 정교한 논리를 구현할 수 있습니다.


기본 같음 비교자를 사용 dict1 하지 않으면 어떻게 됩니까?
mjwills

Linq 메서드를 사용하면 IEqualityComparer를 전달할 수 있습니다.var combined = dict1.Concat(dict2).GroupBy(kvp => kvp.Key, dict1.Comparer).ToDictionary(grp => grp.Key, grp=> grp.First(), dict1.Comparer);
Kyle McClellan

12

내 생각 엔 무슨 일이 일어 났는지에 대해 사용자에게 적절한 출력이 부족한 것 같습니다. 사전에 반복 키를 가질 수 없기 때문에 일부 키가 교차하는 두 사전 병합을 어떻게 처리할까요? 물론 "I do n't care"라고 말할 수 있지만 이는 false를 반환하거나 반복되는 키에 대한 예외를 던지는 규칙을 위반하는 것입니다.


5
Add두 번 이상 발생할 수 있다는 점을 제외 하면을 (를) 호출 할 때 키 충돌이 발생한 위치와 어떻게 다른가요 ? 이 같은 던질 것 ArgumentExceptionAdd확실하지를?
nicodemus13

1
@ nicodemus13 예,하지만 어떤 키가 Exception을 던 졌는지 알 수 없습니다. 단지 SOME 키만 반복되었습니다.
Gal

1
@Gal은 허용했지만 다음과 같이 할 수 있습니다. 예외 메시지에 충돌하는 키의 이름을 반환합니다 (그들이 무엇을하고 있는지 아는 사람에게 유용합니다 ...). 또는 다음과 같이 넣을 수 있습니다. 발생하는 ArgumentException에 대한 paramName 인수, OR-OR, NamedElementException충돌하는 명명 된 요소를 지정하는 ArgumentException의 innerException 대신 또는 ArgumentException의 innerException으로 throw 되는 새 예외 유형 (아마도 일반적인 옵션은 ?? 일 수 있음)을 만들 수 있습니다 . ... 몇 가지 다른 옵션, 내가 말하고 싶지만
코드 기수

7

당신은 이것을 할 수 있습니다

Dictionary<string, string> dic = new Dictionary<string, string>();
// dictionary other items already added.
MethodThatReturnAnotherDic(dic);

public void MethodThatReturnAnotherDic(Dictionary<string, string> dic)
{
    dic.Add(.., ..);
}

또는 addrange 및 / 또는 위의 패턴을 사용하는 목록을 사용합니다.

List<KeyValuePair<string, string>>

1
사전에는 다른 사전허용 하는 생성자가 이미 있습니다 .
Panagiotis Kanavos 2015 년

1
OP는 사전을 복제하지 않고 범위를 추가하려고합니다. 내 예제에서 메서드의 이름에 관해서 MethodThatReturnAnotherDic. OP에서 나옵니다. 질문과 내 답변을 다시 검토하십시오.
Valamas

1

새 딕셔너리를 처리하는 경우 (그리고 잃을 기존 행이없는 경우) 항상 다른 객체 목록에서 ToDictionary ()를 사용할 수 있습니다.

따라서 귀하의 경우 다음과 같이 할 수 있습니다.

Dictionary<string, string> dic = new Dictionary<string, string>();
dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);

5
당신은 심지어 바로 쓰기, 새 사전을 만들 필요가 없습니다Dictionary<string, string> dic = SomeList.ToDictionary...
파나지오티스 Kanavos을

1

중복 키가 없을 것임을 알고 있다면 다음을 수행 할 수 있습니다.

dic = dic.Union(MethodThatReturnAnotherDic()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

중복 키 / 값 쌍이있는 경우 예외가 발생합니다.

왜 이것이 프레임 워크에 없는지 모르겠습니다. 해야한다. 불확실성은 없습니다. 예외를 던지십시오. 이 코드의 경우 예외가 발생합니다.


원본 사전이 다음을 사용하여 생성 된 경우 어떻게 var caseInsensitiveDictionary = new Dictionary<string, int>( StringComparer.OrdinalIgnoreCase);됩니까?
mjwills

1

다음과 같은 확장 방법을 자유롭게 사용하십시오.

public static Dictionary<T, U> AddRange<T, U>(this Dictionary<T, U> destination, Dictionary<T, U> source)
{
  if (destination == null) destination = new Dictionary<T, U>();
  foreach (var e in source)
    destination.Add(e.Key, e.Value);
  return destination;
}

0

다음은 C # 7 ValueTuples (튜플 리터럴)를 사용하는 대체 솔루션입니다.

public static class DictionaryExtensions
{
    public static Dictionary<TKey, TValue> AddRange<TKey, TValue>(this Dictionary<TKey, TValue> source,  IEnumerable<ValueTuple<TKey, TValue>> kvps)
    {
        foreach (var kvp in kvps)
            source.Add(kvp.Item1, kvp.Item2);

        return source;
    }

    public static void AddTo<TKey, TValue>(this IEnumerable<ValueTuple<TKey, TValue>> source, Dictionary<TKey, TValue> target)
    {
        target.AddRange(source);
    }
}

같이 사용

segments
    .Zip(values, (s, v) => (s.AsSpan().StartsWith("{") ? s.Trim('{', '}') : null, v))
    .Where(zip => zip.Item1 != null)
    .AddTo(queryParams);

0

다른 사람들이 언급했듯이 Dictionary<TKey,TVal>.AddRange구현되지 않은 이유 는 중복이있는 경우를 처리하고 싶은 다양한 방법이 있기 때문입니다. 이것은 또한 대한 경우 Collection나 인터페이스 등 IDictionary<TKey,TVal>, ICollection<T>

단지 List<T>그것을 구현, 당신은주의 할 것이다 IList<T>인터페이스는 같은 이유로하지 않습니다 다음 예상 행동을 상황에 따라 크게 달라질 수 있습니다 컬렉션에 값의 범위를 추가 할 때.

질문의 맥락은 중복에 대해 걱정하지 않는다는 것을 암시합니다.이 경우 Linq를 사용하는 간단한 oneliner 대안이 있습니다.

MethodThatReturnAnotherDic().ToList.ForEach(kvp => dic.Add(kvp.Key, kvp.Value));
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.