2010-10-25 14 views
17

Zajmuję się tworzeniem aplikacji w C# kierowania .NET 3.5. Mam w nim 2 podobne słowniki, które zawierają kryteria sprawdzania poprawności dla określonego zestawu elementów w mojej aplikacji. Oba słowniki mają identyczne podpisy. Pierwszy słownik ma ustawienia domyślne, a drugi słownik zawiera pewne ustawienia zdefiniowane przez użytkownika.C# Scalanie 2 słowników

var default_settings = new Dictionary<string, MyElementSettings>(); 
var custom_settings = new Dictionary<string, MyElementSettings>(); 

Chciałbym połączyć dwa słowniki w taki, który zawiera elementy obu słowników.

Problem, na który napotykam, jest możliwe dla obu słowników o tych samych kluczowych wartościach. Podstawową zasadą, której chcę, jest posiadanie kombinacji zarówno słownika, jak i jeśli istnieją jakieś klucze w custom_settings, które już istnieją w default_settings, wartość custom_settings nadpisze wartość default_settings. Najlepszym rozwiązaniem jest po prostu pętla foreach, sprawdź, czy klucz istnieje w innym słowniku, a jeśli nie, dodaj go.

foreach (var item in custom_settings) 
{ 
    if (default_settings.ContainsKey(item.Key)) 
     default_settings[item.Key] = item.Value; 
    else 
     default_settings.Add(item.Key, item.Value); 
} 

Zrobiłem kilka podstawowych zapytań LINQ, ale wciąż pracuję nad uczeniem się bardziej zaawansowanych rzeczy. Widziałem kilka zapytań, które połączą 2 słowniki, ale większość z nich wymaga grupowania dowolnego elementu z duplikatami kluczy, lub zwraca tylko kolekcję z duplikatami kluczy/Czy istnieje zapytanie lub wyrażenie LINQ, które będzie naśladować zachowanie pętli foreach Ja używam?

Odpowiedz

38

dwa punkty:

  1. LINQ nie jest dobre dla wykonywania efektów ubocznych. W tym przypadku próbujesz zmutować istniejącą kolekcję zamiast wykonywać zapytanie, więc unikałbym czystego rozwiązania LINQ.
  2. Już w wyniku dodania pary klucz-wartość efekt
  3. już istnieje, jeśli klucz nie istnieje lub nadpisanie wartości, jeśli tak się dzieje.

Po ustawieniu wartości właściwości, jeśli klucz jest w słowniku, wartości związanej z że klucz jest zastąpione przez wartości przypisanej . Jeśli klucz nie znajduje się w Słowniku , klucz i wartość zostaną dodane do słownika.

Więc pętla foreach jest zasadniczo równoważne:

foreach (var item in custom_settings) 
{ 
    default_settings[item.Key] = item.Value; 
} 

Teraz to już dość lakoniczny, więc nie sądzę, LINQ ma pomóc wszystkim, że dużo.

+3

+1 do punktu # 2 (http://msdn.microsoft.com/en-us/library/k7z0zy8k.aspx) – Brad

+1

+1 do nauczania mi coś, czego nie wiem. Jestem stosunkowo samoukiem z C# i nigdy nie wiedziałem, że możesz to zrobić za pomocą słowników. – psubsee2003

+0

@ psubsee2003: Pozdrawiam. Jestem prawie pewien, że użyłem tego samego wzoru co twój kod, zanim odkryłem, że jest zbędny (przez przypadek, kręcił się z reflektorem). – Ani

2

Jeśli macie zamiar to zrobić dużo, to polecam pisanie porównywarka równości kluczy słownika:

private class KeyEqualityComparer<T, U> : IEqualityComparer<KeyValuePair<T, U>> 
{ 
    public bool Equals(KeyValuePair<T, U> x, KeyValuePair<T, U> y) 
    { 
     return x.Key.Equals(y.Key); 
    } 

    public int GetHashCode(KeyValuePair<T, U> obj) 
    { 
     return obj.Key.GetHashCode(); 
    } 
} 

I wtedy, kiedy trzeba połączyć ze słowników, można wykonać następujące

var comparer = new KeyEqualityComparer<string, MyElementSettings>(); 
dict1 = dict1.Union(dict2,comparer).ToDictionary(a => a.Key, b => b.Value); 
+0

ten działał dla mnie, nawet bez klasy/parametru "KeyEqualityComparer". Dzięki – AceMark

1

Myślę, że odpowiedź, którą wybrałem pierwotnie, wciąż jest najlepszą odpowiedzią dla tego konkretnego przypadku. Niedawno znalazłem się w innej podobnej sytuacji, gdzie miałem 2 IEnumerable<> obiektów, które chciałem przekonwertować na słownik i połączyć się w w podobny sposób, więc pomyślałem, że dodam to rozwiązanie, aby pomóc komuś w przyszłości.Zamiast konwersji do słownika i użycia metody w wybranej odpowiedzi, znalazłem nowe podejście.

Tak naprawdę wysłałem initial solution na SE-CodeReview i faktycznie miałem sugestię, aby zawęzić to dalej. Oto ostateczny kod użyłem:

public Dictionary<String, Foo> Merge(XElement element1, XElement element2) 
{ 
    IEnumerable<Foo> firstFoos = GetXmlData(element1); // parse 1st set from XML 
    IEnumerable<Foo> secondFoos = GetXmlData(element2); // parse 2nd set from XML 

    var result = firstFoos.Union(secondFoos).ToDictionary(k=>k.Name, v=>v); 

    return result; 
} 

public class Foo 
{ 
    public String Name { get; } 

    // other Properties and Methods 
    // . 
    // . 
    // . 

    public override Boolean Equals(Object obj) 
    { 
     if (obj is Foo) 
     { 
      return this.Name == ((Foo)obj).Name;    
     } 

     return false; 
    } 
} 

Kluczem do tego jest Foo musi zastąpić Equals() zdefiniować co Foo obiekty mogą być uznane za duplikat, a członkowie, które określają, które obiekty są duplikat powinien być także kluczem Dictionary<> (w ten przypadek Name)

Jeśli nie można zastąpić Equals() w Foo, potem drugą opcją jest użycie Concat() i GroupBy() zamiast Union()

public Dictionary<String, Foo> Merge(XElement element1, XElement element2) 
{ 
    IEnumerable<Foo> firstFoos = GetXmlData(element1); // parse 1st set from XML 
    IEnumerable<Foo> secondFoos = GetXmlData(element2); // parse 2nd set from XML 

    var result = firstFoos.Concat(secondFoos) 
          .GroupBy(foo => foo.Name) 
          .Select(grp => grp.First()) 
          .ToDictionary(k=>k.Name, v=>v); 

    return result; 
} 
8

Oto ładna metoda rozszerzenia oparta na odpowiedzi Ani.

public static class DictionaryExtensionMethods 
{ 
    public static void Merge<TKey, TValue>(this Dictionary<TKey, TValue> me, Dictionary<TKey, TValue> merge) 
    { 
     foreach (var item in merge) 
     { 
      me[item.Key] = item.Value; 
     } 
    } 
} 
+1

Zmieniłbym słownik na IDictionary –

+0

Jeśli jest to kolekcja słownikowa 'IEnumerable >' jak można je dopasować? – barteloma