如何轻松合并两个具有不同数据结构的匿名对象?

How to easily merge two anonymous objects with different data structure?

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

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"
};

我寻找解决方案,但没有找到任何巧妙的方法。

C# 语言中没有任何东西 built-in 可以支持您的用例。因此,您标题中的问题需要用“抱歉,没有简单的方法”来回答

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

  1. 手动操作:

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

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

    从实现相当 straight-forward 的意义上说,它很“简单”,但它仍然会有很多代码,您需要将嵌套类型 (name)帐号。

    如果你非常想避免 non-anonymous 类型,你 可以 可能使用一个空的匿名目标 object,但是创建这个 object (var man = new { name = new { first = (string)null, last = (string)null, ...) 并不比首先创建 class 少工作。

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

将匿名对象转换为 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<string, object> 的 expando 对象。使用字典表示,您不能像访问匿名对象那样访问属性:

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

result.name; // ERROR

这就是 dynamic 的原因。使用 dynamic 您将丢失编译时检查,但会将两个匿名对象合并为一个。适不适合自己就得自己判断了。