【问题标题】:How to easily merge two anonymous objects with different data structure?如何轻松合并两个具有不同数据结构的匿名对象?
【发布时间】:2021-08-28 23:35:20
【问题描述】:

我想合并这两个匿名对象:

var man1 = new {
    name = new {
        first = "viet"
    },
    age = 20
};

var man2 = new {
    name = new {
        last = "vo"
    },
    address = "123 street"
};

合为一体:

var man = new {
    name = new {
        first = "viet",
        last = "vo"
    },
    age = 20,
    address = "123 street"
};

我寻找解决方案,但没有发现任何聪明之处。

【问题讨论】:

  • 恕我直言,当需要接口(或使用具体类)时,这是一个很好的用例。您想访问这些属性,因此您应该确保这些实例保证具有这些属性。这就是接口提供的
  • 我正在使用 VS2019 和 C#8 @OlivierRogier。
  • 你最好创建类,因为这两种类型都是匿名的,所以无论如何你都需要第三种类型。
  • 我已经编辑了你的问题
  • 如果您在编译时不知道属性:您可以 JSON 序列化和反序列化成字典并轻松合并字典;如果您在编译时确实知道类型:只需创建类

标签: c# data-structures reflection merge anonymous-types


【解决方案1】:

C# 语言中没有内置任何内容来支持您的用例。因此,您的标题中的问题需要用“抱歉,没有简单的方法”来回答。

我可以提供以下替代方案:

  1. 手动操作:

    var man = new {
        name = new {
            first = man1.name.first,
            last = man2.name.first
        },
        age = man1.age,
        address = man2.address
    };
    
  2. 对结果类型使用类而不是匿名类型(我们称之为CompleteMan)。然后,你可以

    • 创建一个新实例var man = new CompleteMan();
    • 使用反射从你的“偏男”(man1man2)那里收集属性和值,
    • 将这些值分配给man 的属性。

    从某种意义上说,这很“简单”,实现将相当简单,但仍然需要大量代码,并且您需要考虑嵌套类型 (name)。

    如果你非常想避免使用非匿名类型,可以使用一个空的匿名目标对象,但是创建这个对象 (var man = new { name = new { first = (string)null, last = (string)null, ...) 并不比在第一名。

  3. 使用专用的动态数据结构而不是匿名 C# 类:

【讨论】:

  • 非常感谢@Heinzi。 Newtonsoft JSON 库正是我想要的!
【解决方案2】:

将匿名对象转换为ExpandoObject,它本质上是string键和object值的字典:

var man1Expando = man1.ToDynamic();
var man2Expando = man2.ToDynamic();
public static ExpandoObject ToDynamic(this object obj)
{
    IDictionary<string, object> expando = new ExpandoObject();

    foreach (var propertyInfo in obj.GetType().GetProperties())
    {
        var currentValue = propertyInfo.GetValue(obj);
        if (propertyInfo.PropertyType.IsAnonymous())
        {
            expando.Add(propertyInfo.Name, currentValue.ToDynamic());
        }
        else
        {
            expando.Add(propertyInfo.Name, currentValue);
        }
    }

    return expando as ExpandoObject;
}

我正在使用辅助扩展来确定类型是否为匿名类型:

public static bool IsAnonymous(this Type type)
{
    return type.DeclaringType is null
        && type.IsGenericType
        && type.IsSealed
        && type.IsClass
        && type.Name.Contains("Anonymous");
}

然后,将两个生成的 expando 对象合并为一个,但递归地检查嵌套的 expando 对象:

var result = MergeDictionaries(man1Expando, man2Expando, overwriteTarget: true);
public static IDictionary<string, object> MergeDictionaries(
    IDictionary<string, object> targetDictionary,
    IDictionary<string, object> sourceDictionary,
    bool overwriteTarget)
{
    foreach (var pair in sourceDictionary)
    {
        if (!targetDictionary.ContainsKey(pair.Key))
        {
            targetDictionary.Add(pair.Key, sourceDictionary[pair.Key]);
        }
        else
        {
            if (targetDictionary[pair.Key] is IDictionary<string, object> innerTargetDictionary)
            {
                if (pair.Value is IDictionary<string, object> innerSourceDictionary)
                {
                    targetDictionary[pair.Key] = MergeDictionaries(
                        innerTargetDictionary,
                        innerSourceDictionary,
                        overwriteTarget);
                }
                else
                {
                    // What to do when target propety is nested, but source is not?
                    // Who takes precedence? Target nested property or source value?
                    if (overwriteTarget)
                    {
                        // Replace target dictionary with source value.
                        targetDictionary[pair.Key] = pair.Value;
                    }
                }
            }
            else
            {
                if (pair.Value is IDictionary<string, object> innerSourceDictionary)
                {
                    // What to do when target propety is not nested, but source is?
                    // Who takes precedence? Target value or source nested value?
                    if (overwriteTarget)
                    {
                        // Replace target value with source dictionary.
                        targetDictionary[pair.Key] = innerSourceDictionary;
                    }
                }
                else
                {
                    // Both target and source are not nested.
                    // Who takes precedence? Target value or source value?
                    if (overwriteTarget)
                    {
                        // Replace target value with source value.
                        targetDictionary[pair.Key] = pair.Value;
                    }
                }
            }
        }
    }

    return targetDictionary;
}

overwriteTarget 参数决定合并时哪个对象优先。

使用代码:

var man1 = new
{
    name = new
    {
        first = "viet",
    },
    age = 20,
};

var man2 = new
{
    name = new
    {
        last = "vo",
    },
    address = "123 street",
};

var man1Expando = man1.ToDynamic();
var man2Expando = man2.ToDynamic();

dynamic result = MergeDictionaries(man1Expando, man2Expando, overwriteTarget: true);

Console.WriteLine(JsonConvert.SerializeObject(result, Formatting.Indented));

结果:

{
  "name": {
    "first": "viet",
    "last": "vo"
  },
  "age": 20,
  "address": "123 street"
}

注意我如何将结果分配给dynamic。让编译器分配类型将为您留下以IDictionary&lt;string, object&gt; 表示的expando 对象。使用字典表示,您不能以与匿名对象相同的方式访问属性:

var result = MergeDictionaries(man1Expando, man2Expando, overwriteTarget: true);

result.name; // ERROR

这就是dynamic 的原因。使用dynamic,您将失去编译时间检查,但将两个匿名对象合并为一个。你必须自己判断它是否适合你。

【讨论】:

  • 真的很管用。你的代码有很多我可以学习的东西。谢谢
猜你喜欢
  • 2017-02-11
  • 2019-12-16
  • 2021-01-16
  • 2013-03-24
  • 2010-10-07
  • 2013-09-06
  • 2013-09-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多