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.
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.