【问题标题】:How would I convert List<Dictionary<string, object>> into a List<[new class with dynamic properties]>如何将 List<Dictionary<string, object>> 转换为 List<[new class with dynamic properties]>
【发布时间】:2014-11-19 16:46:32
【问题描述】:

我有一个设计,其中类包含一个 List 对象,每个 Summary 都是半动态属性的字典。也就是说,给定列表中的每个摘要都将在字典中具有相同的键。我正在使用这种设计来构建一组动态“属性”来跟踪汇总值,因为根据我的项目规范,这些汇总值将在运行时进行配置。

问题是:我怎样才能展平这个列表,以便列表中的每个项目都被视为字典中的键是实际属性?

我尝试了将字典转换为列表的不同变体,但这似乎本质上是错误的,因为我确实需要将键视为属性。我猜我需要使用 C# 4.0+ 和 ExpandoObject 的新动态特性,但我不能完全正确。

下面的代码显示了基本设置,“flattenedSummary”给了我我想要的——一个动态类型的新列表,其属性是摘要字典的键。但是,这是有缺陷的,因为我已经对属性名称进行了硬编码,而我不能这样做,因为直到运行时我才真正知道它们。

flattenedSummary2 版本尝试将列表展平,但由于返回的类型仍然是 List 而不是我想要的 List,因此未能实现。

    public class Summary : Dictionary<string, object>
    {
    }

    public class ClassA
    {
        public List<Summary> Summaries = new List<Summary>();
    }

    static void Main(string[] args)
    {
        ClassA a = new ClassA();
        var summary = new Summary();
        summary.Add("Year", 2010);
        summary.Add("Income", 1000m);
        summary.Add("Expenses", 500m);
        a.Summaries.Add(summary);

        summary = new Summary();
        summary.Add("Year", 2011);
        summary.Add("Income", 2000m);
        summary.Add("Expenses", 700m);
        a.Summaries.Add(summary);

        summary = new Summary();
        summary.Add("Year", 2012);
        summary.Add("Income", 1000m);
        summary.Add("Expenses", 800m);
        a.Summaries.Add(summary);

        var flattenedSummary = from s in a.Summaries select new { Year = s["Year"], Income = s["Income"], Expenses = s["Expenses"] };
        ObjectDumper.Write(flattenedSummary, 1);

        var flattenedSummary2 = Convert(a);
        ObjectDumper.Write(flattenedSummary2, 1);

        Console.ReadKey();
    }

    public static List<ExpandoObject> Convert(ClassA a)
    {
        var list = new List<ExpandoObject>();
        foreach (Summary summary in a.Summaries)
        {
            IDictionary<string, object> fields = new ExpandoObject();
            foreach (var field in summary)
            {
                fields.Add(field.Key.ToString(), field.Value);
            }
            dynamic s = fields;
            list.Add(s);
        }

        return list;
    }

【问题讨论】:

  • 那么,如果您在运行时才知道它们是什么,那么您如何在代码中使用这些属性呢?这只是用于数据绑定还是您自己从不直接使用属性?
  • 使用这种扁平化视图的两种方式:数据绑定到网格以进行调试和从 web api 序列化为 json(或 xml)。有关提供 json 解决方案的细微更改,请参阅下面的 cmets 接受的答案。

标签: c# linq dictionary dynamic-properties


【解决方案1】:

虽然我接受了 Ed 的回答,因为它非常接近,但我提供以下代码以防其他人发现它有用。关键更改是确保将 ExpandoObject 的所有使用设置为动态,以便最终列表是动态的。如果没有这些更改,检查列表中的类型仍会返回 ExpandoObject(例如,json 序列化给出的是 ExpandoObject 而不是预期的属性名称/值)。

首先,ToExpando() 方法(应该称为 ToDynamic):

public static dynamic ToExpando(this IDictionary<string, object> dictionary)
{
    dynamic expando = new ExpandoObject();
    var expandoDic = (IDictionary<string, object>)expando;

    // go through the items in the dictionary and copy over the key value pairs)
    foreach (var kvp in dictionary)
    {
        // if the value can also be turned into an ExpandoObject, then do it!
        if (kvp.Value is IDictionary<string, object>)
        {
            var expandoValue = ((IDictionary<string, object>)kvp.Value).ToExpando();
            expandoDic.Add(kvp.Key, expandoValue);
        }
        else if (kvp.Value is ICollection)
        {
            // iterate through the collection and convert any strin-object dictionaries
            // along the way into expando objects
            var itemList = new List<object>();
            foreach (var item in (ICollection)kvp.Value)
            {
                if (item is IDictionary<string, object>)
                {
                    var expandoItem = ((IDictionary<string, object>)item).ToExpando();
                    itemList.Add(expandoItem);
                }
                else
                {
                    itemList.Add(item);
                }
            }

            expandoDic.Add(kvp.Key, itemList);
        }
        else
        {
            expandoDic.Add(kvp);
        }
    }

    return expando;
}

调用代码如下所示:

    List<dynamic> summaries = new List<dynamic>();
    foreach (var s in a.Summaries)
    {
        summaries.Add(s.DynamicFields.ToExpando());
    }

或者更紧凑的版本:

    a.Summaries.Select(s => s.DynamicFields.ToExpando())

以上所有内容都提供了一个可以引用为的对象:

    int year = a.Summaries[0].Year; // Year is a dynamic property of type int
    decimal income = a.Summaries[0].Income; // Income is a dynamic property of type decimal

当然,这个想法是我不知道属性 - 但它们可以序列化为 json,或者通过一些调整,用于绑定网格或其他 UI 元素以进行显示。

【讨论】:

    【解决方案2】:

    丹尼尔,

    我发现这篇文章有一个可能适合您的解决方案。 http://theburningmonk.com/2011/05/idictionarystring-object-to-expandoobject-extension-method/

    所以你会创建扩展方法...

      public static ExpandoObject ToExpando(this IDictionary<string, object> dictionary)
            {
                var expando = new ExpandoObject();
                var expandoDic = (IDictionary<string, object>)expando;
    
                // go through the items in the dictionary and copy over the key value pairs)
                foreach (var kvp in dictionary)
                {
                    // if the value can also be turned into an ExpandoObject, then do it!
                    if (kvp.Value is IDictionary<string, object>)
                    {
                        var expandoValue = ((IDictionary<string, object>)kvp.Value).ToExpando();
                        expandoDic.Add(kvp.Key, expandoValue);
                    }
                    else if (kvp.Value is ICollection)
                    {
                        // iterate through the collection and convert any strin-object dictionaries
                        // along the way into expando objects
                        var itemList = new List<object>();
                        foreach (var item in (ICollection)kvp.Value)
                        {
                            if (item is IDictionary<string, object>)
                            {
                                var expandoItem = ((IDictionary<string, object>)item).ToExpando();
                                itemList.Add(expandoItem);
                            }
                            else
                            {
                                itemList.Add(item);
                            }
                        }
    
                        expandoDic.Add(kvp.Key, itemList);
                    }
                    else
                    {
                        expandoDic.Add(kvp);
                    }
                }
    
                return expando;
            }
    

    然后从您的 Main 函数中...

    List<ExpandoObject> flattenSummary3 = new List<ExpandoObject>();
    foreach ( var s in a.Summaries)
    {
        flattenSummary3.Add(s.ToExpando());
    }
    

    现在 flattenSummary3 变量将包含一个可以通过属性引用的 ExpandObject 列表。

    我希望这会有所帮助。

    【讨论】:

    • 我必须做一些小改动: (1) 将 ToExpando 更改为返回“动态”而不是 ExpandoObject (2) 在 ToExpando 方法中,将 expando 声明为动态而不是 var。 (3) 在调用代码中,将 flattenSummary3 声明为 List 这些更改允许将 flattenSummary3 视为真正的动态列表(序列化为 json 会给出预期的属性/值数组)。该解决方案仍然不允许数据绑定在 WinForm DataGridView 上工作 - 但这可能是一个单独的问题。谢谢你的帮助,埃德!
    猜你喜欢
    • 1970-01-01
    • 2017-09-20
    • 1970-01-01
    • 1970-01-01
    • 2021-05-20
    • 2020-09-04
    • 1970-01-01
    • 2020-11-27
    • 1970-01-01
    相关资源
    最近更新 更多