Подтвердить что ты не робот

С# Слияние 2 словарей

Я разрабатываю приложение в С#, ориентированном на .NET 3.5. В нем у меня есть 2 похожих словаря, которые содержат критерии проверки для определенного набора элементов в моем приложении. Оба словаря имеют одинаковые подписи. Первый словарь имеет настройки по умолчанию, а второй словарь содержит некоторые пользовательские настройки.

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

Я хотел бы объединить 2 словаря в один, содержащий элементы обоих словарей.

Проблема, с которой я сталкиваюсь, - это то, что оба словаря могут иметь одни и те же ключевые значения. Основное правило, которое я хочу, состоит в том, чтобы иметь комбинацию словаря, и если в настройках custom_settings, которые уже существуют в параметрах default_settings, есть какие-либо ключи, значение custom_settings перезапишет значение default_settings. Лучшее решение у меня есть только цикл foreach, проверьте, существует ли ключ в другом словаре, а если нет, добавьте его.

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);
}

Я сделал некоторые основные запросы LINQ, но я все еще работаю над тем, чтобы изучить более продвинутые вещи. Я видел несколько запросов, которые объединит 2 словаря, но большинство из них связано с группировкой любого элемента с дублирующимися ключами или возвращением коллекции только с дублирующимися ключами/есть ли запрос или выражение LINQ, которые будут имитировать поведение цикла foreach Я использую?

4b9b3361

Ответ 1

Две точки:

  • LINQ не подходит для выполнения побочных эффектов. В этом случае вы пытаетесь мутировать существующую коллекцию, а не выполнять запрос, поэтому я бы уклонился от чистого решения LINQ.
  • setter в универсальном индексаторе словарей уже имеет эффект добавления пары ключ-значение, если ключ не существует, или переписывание значение, если это произойдет.

Когда вы устанавливаете значение свойства, если ключ находится в словаре, значение, связанное с этот ключ заменяется назначенным стоимость. Если ключ не находится в Словарь, ключ и значение добавляются в словарь.

Итак, ваш цикл foreach по существу эквивалентен:

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

Теперь, когда это довольно красное, поэтому я не думаю, что LINQ поможет вам в этом.

Ответ 2

Вот хороший метод расширения, основанный на ответе Ани.

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;
        }
    }
}

Ответ 3

Если вы собираетесь сделать это много, я бы рекомендовал написать сопоставитель равенства для клавиш словаря:

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

И тогда, когда вам нужно объединить словари, вы можете сделать следующее

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

Ответ 4

Я думаю, что ответ, который я выбрал изначально, по-прежнему является лучшим ответом для этого конкретного случая, я недавно оказался в другой подобной ситуации, когда у меня было 2 IEnumerable<> объекта, которые я хотел преобразовать в словарь, и объединить их таким образом, я думал, что добавлю это решение здесь, чтобы помочь кому-то в будущем. Вместо того, чтобы конвертировать оба слова в словарь и использовать метод в выбранном ответе, я нашел новый подход.

Я опубликовал начальное решение на SE-CodeReview и на самом деле предложил уточнить его. Здесь последний код, который я использовал:

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;
    }
}

Ключ к этому Foo должен переопределить Equals(), чтобы определить, какие объекты Foo можно считать дублирующими, а члены, которые определяют, какие объекты дублируются, также должны быть ключом Dictionary<> (в этом случае Name)

Если вы не можете переопределить Equals() в Foo, то другой вариант должен использовать Concat() и GroupBy() вместо 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;
}