简体   繁体   中英

In C#, how do I change a Key-Value-Property's value while recursively traversing an ExpandoObject?

The Problem

Using C#, I need to traverse an object that has been cast to an ExpandoObject from XML and replace any "price" property with a new value.

This object is very unstructured and has many layers of nested nodes (nested ExpandoObjects, actually). More specifically, the hierarchy may look like this:

Product => price, quantity, accessories

Each accessory may have a price and quantity and may itself have accessories, this is why I need recursion.

What I have so far

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

The problem I run into is that the kvp.Value has no setter, so I can't run this method recursively.

Any suggestions are appreciated. Thanks!

I don't know much about ExpandoObject. But like most dictionary implementations, I assume that in general if you want your key-value pair to be updated to have a different value, you need to go through the dictionary interface.

Note that you (probably) won't be allowed to modify the dictionary while you're enumerating its contents. So you'll need to build a list of elements to update and do that in a separate operation.

For example:

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

You could also keep the whole KeyValuePair value in your list, to avoid the second retrieval of the value, but I'm guessing that's not an important optimization here.

Since ExpandoObject implements IDictionary<string, Object> things can get a bit easier. We can also change the return type to void because we don't need to reassign the result.

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

I just ran a little test on this and was able to get it to work by having the expando be dynamic:

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

it elimitates type safety, but it looks like you don't have that luxury anyways, dynamic is the best way to interact with expandos in fact according to MSDN :

"In C#, to enable late binding for an instance of the ExpandoObject class, you must use the dynamic keyword. For more information, see Using Type dynamic (C# Programming Guide)."

this means that if you don't use the dynamic keyword, you are running the Expando in the CLR instead of the DLR which will have some odd consequences like not being able to set values. Hopefully this helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM