C# - 将对象列表合并为一个对象

时间:2017-05-01 16:42:31

标签: c#

我传递了一个小对象列表:

var smalls = new List<Small>();
smalls.AddRange( new Small[] { new Small{Name = "Aa", Id = 1, Value = "v1"},
                               new Small{Name = "Bb", Id = 1, Value = "v2"},
                               new Small{Name = "Cc", Id = 1, Value = "v3"},
                               new Small{Name = "Dd", Id = 1, Value = "v4"},
                               new Small{Name = "Ee", Id = 1, Value = "v5"},
                               new Small{Name = "Ff", Id = 1, Value = "v6"},
                               new Small{Name = "Gg", Id = 1, Value = "v7"} } );

从上面的列表中我想填充一个如下所示的对象:

var large = new Large
    {
        Id = 1,
        Aa = "v1",
        Bb = "v2",
        Cc = "v3",
        Dd = "v4",
        Ee = "v5",
        Ff = "v6",
        Gg = "v7"
    }

当前代码依赖于列表的顺序来填充Large对象,但这不够安全,并且正在寻找一种更可靠的方法将列表映射到对象中。

当前代码:

Large large = new Large
{
    Id = smalls[0].Id,
    Aa = smalls[0].Value,
    Bb = smalls[1].Value,
    Cc = smalls[2].Value,
    Dd = smalls[3].Value,
    Ee = smalls[4].Value,
    Ff = smalls[5].Value,
    Gg = smalls[6].Value
}

所以我希望消除它们处于正确顺序的假设,并根据Small对象中的Name字符串将新字段填充到Large对象的相应字段中。

感谢您提出任何意见!!

5 个答案:

答案 0 :(得分:2)

您可以按Id对值进行分组,并根据Name字段提取一些方法来提取值:

private static string GetValueByName(IDictionary<string,string> data, string name) {
    string res;
    return data.TryGetValue(name, out res) ? res : null;
}
private static Large MakeFromAttributes(IEnumerable<Small> data, int id) {
    var byName = data.ToDoctionary(s => s.Name, s => s.Value);
    return new Large {
        Id = id
    ,   Aa = GetValueByName(byName, "Aa")
    ,   Bb = GetValueByName(byName, "Bb")
    ,   Cc = GetValueByName(byName, "Cc")
    ,   Dd = GetValueByName(byName, "Dd")
    ,   Ee = GetValueByName(byName, "Ee")
    ,   Ff = GetValueByName(byName, "Ff")
    ,   Gg = GetValueByName(byName, "Gg")
    };
}

使用这些辅助方法,您可以构建LINQ查询,如下所示:

var largeList = smalls
    .GroupBy(s => s.Id)
    .Select(g => MakeFromAttributes(g, g.Key))
    .ToList();

答案 1 :(得分:1)

  

所以我希望消除它们的顺序正确的假设

也许是这样的事情?:

Aa = smalls.Single(s => s.Name == "Aa").Value

这至少仍然依赖于记录将存在的假设,尽管它并不关心记录的顺序。如果您也想删除该假设,可以添加一些错误检查。也许是这样的事情:

Aa = smalls.Any(s => s.Name == "Aa") ? smalls.First(s => s.Name == "Aa") : string.Empty

它不是世界上效率最高的东西,但至少保持在当前用途的一条线上。分成多行将使它更长,但可能更高性能(如果性能甚至是一个问题......在非常小的例子中,如果它真的不是。)

然后可以将这些多行重新计算到自定义扩展方法中以将其重新放回一行?天空是极限,真的。

答案 2 :(得分:1)

Dictionary(TKey, TValue)可能是用例更合适的数据结构,因为密钥可以是动态的,任何从IEnumerable(Small)Large的解决方案都需要对组合做出假设集合或集合对象。

一个简单的动态解决方案是使用反射,但与已经提出的静态查找相比,这会产生更多的开销。

public static Large CreateLargeFromSmalls(int id, IEnumerable<Small> smalls)
{
    var largeType = typeof(Large);
    var large = new Large { Id = id };

    foreach (var small in smalls)
    {
        var prop = largeType.GetProperty(small.Name);
        if (prop != null)
        {
            prop.SetValue(large, small.Value);
        }
    }

    return large;
}

假设

  • Name的{​​{1}}将与相应的Small属性完全匹配。
  • Large的{​​{1}}不是唯一的,迭代次序很重要。
  • 如果Name中不存在与Small Name对应的属性,则该地图未映射。

赞成

  • Small的合同更改不会影响映射逻辑。

缺点

  • 反思开销

示例:

Large

答案 3 :(得分:0)

如果我正确理解了您的问题,您可以使用反射设置Large类中的所有属性,而不必担心Small集合的顺序:

Large large = new Large();
foreach (var propertyInfo in large.GetType().GetProperties())
{
    var sm = smalls.FirstOrDefault(small => string.Equals(small.Name, propertyInfo.Name, StringComparison.InvariantCultureIgnoreCase));
    if (sm != null)
        propertyInfo.SetValue(large, Convert.ChangeType(sm.Value, propertyInfo.PropertyType), null);
}

重要提示:请注意,要使此解决方案正常运行,需要更新必须的所有Large属性都会被getter和setter标记。例如。 public string Aa { get; set; }

我们首先使用获取所有属性的Large来获取large.GetType().GetProperties()的所有属性。然后我们将名称与.Name类集合中的Small属性进行比较,如果找到匹配,则设置属性的值。您可以阅读有关反射here的更多信息。

尝试后Large的屏幕截图:

答案 4 :(得分:0)

您可以将Small的列表分组,然后将Id设置为组的Key,使用反射将其他属性设置为

var large = smalls.GroupBy(small => small.Id)
    .Select(group =>
    {
        var result = new Large();
        result.Id = group.Key;
        var largeType = result.GetType();
        foreach (var small in group)
        {
                largeType.GetProperty(small.Name).SetValue(result, small.Value);
        }
        return result;
    }).First();