Как в C# изменить значение свойства Key-Value-Property при рекурсивном обходе ExpandoObject?

Эта проблема

Используя C#, мне нужно пройти через объект, который был приведен к ExpandoObject из XML, и заменить любое свойство цены новым значением.

Этот объект очень неструктурирован и имеет много слоев вложенных узлов (фактически вложенных ExpandoObjects). В частности, иерархия может выглядеть так:

Товар =› цена, количество, аксессуары

Каждый аксессуар может иметь цену и количество и сам может иметь аксессуары, поэтому мне нужна рекурсия.

Что у меня есть до сих пор

  public ExpandoObject UpdatePricing(ExpandoObject exp)
    {
        //Is there a price property?
        var hasPrice = exp.Any(a => a.Key == "price");
        if (hasPrice)
        {
            //update price here
            exp.price = 0; //Some other price
        }

        //Now loop through the whole object.  If any of the properties contain an expando, then call this method again
        foreach (var kvp in exp)
        {
            if (kvp.Value is ExpandoObject)
            {
                //THIS CODE IS NO GOOD BECAUSE kvp.Value has no setter!!
                kvp.Value = UpdatePricing(kvp.Value);
            }
        }

        return exp;
    }

Проблема, с которой я сталкиваюсь, заключается в том, что у kvp.Value нет сеттера, поэтому я не могу запустить этот метод рекурсивно.

Любые предложения приветствуются. Спасибо!


person Matt Cashatt    schedule 17.10.2014    source источник
comment
Я бы реорганизовал структуру в виде дерева.   -  person BlueTrin    schedule 18.10.2014
comment
Какую ошибку вы получаете конкретно? Когда вы запускаете его, какое исключение возникает?   -  person Haney    schedule 18.10.2014
comment
Какой смысл возвращать квп? Почему бы просто не рекурсивно обработать метод void? Или просто не присваивайте результат kvp.Value.   -  person codenheim    schedule 18.10.2014


Ответы (3)


Поскольку ExpandoObject реализует IDictionary<string, Object>, все может стать немного проще. Мы также можем изменить тип возвращаемого значения на void, потому что нам не нужно переназначать результат.

void UpdatePrice(ExpandoObject expando, decimal price)
{
    var map = (IDictionary<string, Object>)expando;
    if (map.ContainsKey("price"))
        map["price"] = price;
    foreach (var value in map.Values) 
    {
        if (value is ExpandoObject)
            UpdatePrice((ExpandoObject)value, price);
    }
}
person ChaosPandion    schedule 17.10.2014

Я мало что знаю об ExpandoObject. Но, как и в большинстве реализаций словаря, я предполагаю, что в целом, если вы хотите, чтобы ваша пара ключ-значение обновлялась, чтобы иметь другое значение, вам нужно пройти через интерфейс словаря.

Обратите внимание, что вам (вероятно) не будет разрешено изменять словарь, пока вы перечисляете его содержимое. Поэтому вам нужно создать список элементов для обновления и сделать это отдельной операцией.

Например:

List<string> keysToUpdate = new List<string>();

foreach (var kvp in exp)
{
    if (kvp.Value is ExpandoObject)
    {
        keysToUpdate.Add(kvp.Key);
    }
}

foreach (string key in keysToUpdate)
{
    exp[key] = UpdatePricing(exp[key]);
}

Вы также можете сохранить все значение KeyValuePair в своем списке, чтобы избежать второго извлечения значения, но я предполагаю, что это не важная оптимизация здесь.

person Peter Duniho    schedule 17.10.2014

Я только что провел небольшой тест и смог заставить его работать, сделав расширение динамическим:

    public static ExpandoObject DoWork(ExpandoObject obj)
    {
        dynamic expando = obj;

        //update price
        if (obj.Any(c => c.Key == "price"))
            expando.price = 354.11D;

        foreach (var item in expando)
        {
            if (item.Value is ExpandoObject)
            {
                //call recursively
                DoWork(item.Value);
            }
        }
        return expando;
    }

он ограничивает безопасность типов, но похоже, что у вас все равно нет такой роскоши, динамический — лучший способ взаимодействия с Expandos на самом деле согласно MSDN:

«В C#, чтобы включить позднее связывание для экземпляра класса ExpandoObject, необходимо использовать ключевое слово dynamic. Дополнительные сведения см. в разделе Использование динамического типа (Руководство по программированию на C#)».

это означает, что если вы не используете ключевое слово dynamic, вы запускаете Expando в CLR, а не в DLR, что будет иметь некоторые странные последствия, такие как невозможность установки значений. Надеюсь, это поможет.

person tophallen    schedule 17.10.2014