简体   繁体   English

在C#中,如何在递归遍历ExpandoObject时更改键值属性的值?

[英]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. 使用C#,我需要遍历从XML ExpandoObject转换为ExpandoObject的对象,并用新值替换所有“ price”属性。

This object is very unstructured and has many layers of nested nodes (nested ExpandoObjects, actually). 该对象非常不结构化,具有多层嵌套节点(实际上是嵌套的ExpandoObjects)。 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. 我遇到的问题是kvp.Value没有设置器,因此我无法递归运行此方法。

Any suggestions are appreciated. 任何建议表示赞赏。 Thanks! 谢谢!

I don't know much about ExpandoObject. 我对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. 您也可以将整个KeyValuePair值保留在列表中,以避免第二次检索该值,但是我猜想这并不是一个重要的优化。

Since ExpandoObject implements IDictionary<string, Object> things can get a bit easier. 由于ExpandoObject实现IDictionary<string, Object>会变得容易一些。 We can also change the return type to void because we don't need to reassign the result. 我们也可以将返回类型更改为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);
    }
}

I just ran a little test on this and was able to get it to work by having the expando be dynamic: 我对此进行了一些测试,并通过使expando动态化来使其工作:

    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 : 它限制了类型安全性,但是根据MSDN实际上,动态似乎是与expandos交互的最佳方式,反正是您没有奢侈的地方:

"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)." “在C#中,要为ExpandoObject类的实例启用后期绑定,必须使用dynamic关键字。有关更多信息,请参见使用Type dynamic(C#编程指南)。”

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. 这意味着,如果不使用dynamic关键字,则将在CLR中运行Expando而不是DLR,这将产生一些奇怪的后果,例如无法设置值。 Hopefully this helps. 希望这会有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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