动态/扩展对象的C#深/嵌套/递归合并 [英] C# deep/nested/recursive merge of dynamic/expando objects

查看:152
本文介绍了动态/扩展对象的C#深/嵌套/递归合并的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要在C#中合并2个动态对象。我在stackexchange中找到的只包含非递归合并。但是我正在寻找一些可以递归或深入合并的东西,就像 jQuery的 $。extend(obj1,obj2) 功能。



两个成员冲突时,应适用以下规则: / p>


  • 如果类型不匹配,则必须抛出异常,并且中止合并。异常:obj2值可能为null,在这种情况下为值&

  • 对于简单类型(值类型+字符串)obj1值始终是优先的

  • 对于非平凡类型,以下规则适用:


    • IEnumerable & IEnumberables< T> 只是合并(可能 .Concat()?)

    • IDictionary & IDictionary< TKey,TValue> 被合并; obj1键优先于碰撞

    • Expando & Expando [] 类型必须递归合并,而Expando []将始终只有相同类型的元素

    • 可以假设有集合中没有Expando对象(IEnumerabe& IDictionary)


  • 所有其他类型都可以被丢弃,不需要在生成的动态对象中



以下是可能合并的示例:

  dynamic DefaultConfig = new {
BlacklistedDomains = new string [] {domain1.com},
ExternalConfigFile =blacklist.txt,
UseSockets = new [] {
new {IP =127.0.0.1,Port =80},
new {IP =127.0.0.2,Port =8080}
}
};

动态UserSpecifiedConfig = new {
BlacklistedDomain = new string [] {example1.com},
ExternalConfigFile =C:\\my_blacklist.txt
};

var result = Merge(UserSpecifiedConfig,DefaultConfig);
//结果应该等于:
var result_equal = new {
BlacklistedDomains = new string [] {domain1.com,example1.com},
ExternalConfigFile =C:\\my_blacklist.txt,
UseSockets = new [] {
new {IP =127.0.0.1,Port =80},
new {IP =127.0.0.2,Port =8080}
}
};

任何想法如何做?

解决方案

对,这是有点长的,但看看。这是一个使用Reflection.Emit的实现。



我的开放问题是如何实现ToString()覆盖,以便您可以进行字符串比较。这些值是来自配置文件还是某些东西?如果他们是JSON格式,我可以做的比使用JsonSerializer更糟糕。取决于你想要的。



你可以使用Expando Object来摆脱Reflection.Emit废话,在循环的底部:

  var result = new ExpandoObject(); 
var resultDict = result as IDictionary< string,object> ;;
foreach(string key in resVals.Keys)
{
resultDict.Add(key,resVals [key]);
}
返回结果;

我看不到解决原始对象树的凌乱的代码,而不是立即。我想听一些其他意见。 DLR对于我来说是比较新的。

  using System; 
使用System.Collections.Generic;
使用System.Diagnostics;
使用System.Linq;
使用System.Reflection;
使用System.Reflection.Emit;
使用System.Runtime.CompilerServices;
使用System.Threading;

命名空间ConsoleApplication1
{
类程序
{
static void Main(string [] args)
{
dynamic DefaultConfig = new
{
BlacklistedDomains = new string [] {domain1.com},
ExternalConfigFile =blacklist.txt,
UseSockets = new [] {
new {IP =127.0.0.1,Port =80},
new {IP =127.0.0.2,Port =8080}
}
};

动态UserSpecifiedConfig = new
{
BlacklistedDomains = new string [] {example1.com},
ExternalConfigFile =C:\\my_blacklist。 txt
};

var result = Merge(UserSpecifiedConfig,DefaultConfig);

//结果应该等于:

var result_equal = new
{
BlacklistedDomains = new string [] {domain1.com ,example1.com},
ExternalConfigFile =C:\\\mymyblacklist.txt,
UseSockets = new [] {
new {IP =127.0.0.1 Port =80},
new {IP =127.0.0.2,Port =8080}
}
};
Debug.Assert(result.Equals(result_equal));
}

///< summary>
///合并两个动态对象的属性,将LHS作为主要
///< / summary>
///< param name =lhs>< / param>
///< param name =rhs>< / param>
///< returns>< / returns>
静态动态合并(动态lhs,动态rhs)
{
//获取匿名类型定义
类型lhsType =((Type)((dynamic)lhs).GetType ));
类型rhsType =((Type)((dynamic)rhs).GetType());

对象result = new {};
var resProps = new Dictionary< string,PropertyInfo>();
var resVals = new Dictionary< string,object>();

var lProps = lhsType.GetProperties()。ToDictionary< PropertyInfo,string>(prop => prop.Name);
var rProps = rhsType.GetProperties()。ToDictionary< PropertyInfo,string>(prop => prop.Name);


foreach(lProps.Keys中的字符串leftPropKey)
{
var lPropInfo = lProps [leftPropKey];
resProps.Add(leftPropKey,lPropInfo);
var lhsVal = Convert.ChangeType(lPropInfo.GetValue(lhs,null),lPropInfo.PropertyType);
if(rProps.ContainsKey(leftPropKey))
{
PropertyInfo rPropInfo;
rPropInfo = rProps [leftPropKey];
var rhsVal = Convert.ChangeType(rPropInfo.GetValue(rhs,null),rPropInfo.PropertyType);
对象setVal = null;

if(lPropInfo.PropertyType.IsAnonymousType())
{
setVal = Merge(lhsVal,rhsVal);
}
else if(lPropInfo.PropertyType.IsArray)
{
var bound =((Array)lhsVal).Length +((Array)rhsVal).Length;
var cons = lPropInfo.PropertyType.GetConstructor(new Type [] {typeof(int)});
dynamic newArray = cons.Invoke(new object [] {bound});
// newArray =((Array)lhsVal).Clone();
int i = 0;
while(i<((Array)lhsVal).Length)
{
newArray [i] = lhsVal [i];
i ++;
}
while(i< bound)
{
newArray [i] = rhsVal [i - ((Array)lhsVal).Length]
i ++;
}
setVal = newArray;
}
else
{
setVal = lhsVal == null? rhsVal:lhsVal;
}
resVals.Add(leftPropKey,setVal);
}
else
{
resVals.Add(leftPropKey,lhsVal);
}
}
foreach(rProps.Keys中的string rightPropKey)
{
if(lProps.ContainsKey(rightPropKey)== false)
{
PropertyInfo rPropInfo;
rPropInfo = rProps [rightPropKey];
var rhsVal = rPropInfo.GetValue(rhs,null);
resProps.Add(rightPropKey,rPropInfo);
resVals.Add(rightPropKey,rhsVal);
}
}

键入resType = TypeExtensions.ToType(result.GetType(),resProps);

result = Activator.CreateInstance(resType);

foreach(resVals.Keys中的字符串键)
{
var resInfo = resType.GetProperty(key);
resInfo.SetValue(result,resVals [key],null);
}
返回结果;
$

$ b public static class TypeExtensions
{
public static Type ToType(Type type,Dictionary< string,PropertyInfo>属性)
{
AppDomain myDomain = Thread.GetDomain();
装配asm = type.Assembly;
AssemblyBuilder assemblyBuilder =
myDomain.DefineDynamicAssembly(
asm.GetName(),
AssemblyBuilderAccess.Run
);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(type.Module.Name);
TypeBuilder typeBuilder = moduleBuilder.DefineType(type.Name,TypeAttributes.Public);

foreach(property.Keys中的字符串键)
{
string propertyName = key;
键入propertyType = properties [key] .PropertyType;

FieldBuilder fieldBuilder = typeBuilder.DefineField(
_+ propertyName,
propertyType,
FieldAttributes.Private
);

PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(
propertyName,
PropertyAttributes.HasDefault,
propertyType,
new Type [] {}
) ;
//首先,我们将定义属性的getacctor作为一种方法的行为。
MethodBuilder getMethodBuilder = typeBuilder.DefineMethod(
Get+ propertyName,
MethodAttributes.Public,
propertyType,
new Type [] {}
) ;

ILGenerator getMethodIL = getMethodBuilder.GetILGenerator();

getMethodIL.Emit(OpCodes.Ldarg_0);
getMethodIL.Emit(OpCodes.Ldfld,fieldBuilder);
getMethodIL.Emit(OpCodes.Ret);

//现在,我们定义属性的set访问器的行为。
MethodBuilder setMethodBuilder = typeBuilder.DefineMethod(
Set+ propertyName,
MethodAttributes.Public,
null,
new Type [] {propertyType}
);

ILGenerator custNameSetIL = setMethodBuilder.GetILGenerator();

custNameSetIL.Emit(OpCodes.Ldarg_0);
custNameSetIL.Emit(OpCodes.Ldarg_1);
custNameSetIL.Emit(OpCodes.Stfld,fieldBuilder);
custNameSetIL.Emit(OpCodes.Ret);

//最后,我们必须将上面创建的两个方法映射到我们的PropertyBuilder到
//对应的行为,get和set。
propertyBuilder.SetGetMethod(getMethodBuilder);
propertyBuilder.SetSetMethod(setMethodBuilder);
}

// MethodBuilder toStringMethodBuilder = typeBuilder.DefineMethod(
//ToString,
// MethodAttributes.Public,
// typeof string),
// new Type [] {}
//);

return typeBuilder.CreateType();
}
public static Boolean IsAnonymousType(此类型类型)
{
Boolean hasCompilerGeneratedAttribute = type.GetCustomAttributes(
typeof(CompilerGeneratedAttribute),false).Count()> ; 0;
Boolean nameContainsAnonymousType =
type.FullName.Contains(AnonymousType);
Boolean isAnonymousType = hasCompilerGeneratedAttribute&& nameContainsAnonymousType;
return isAnonymousType;
}
}


I need to "merge" 2 dynamic objects in C#. All that I've found on stackexchange covered only non-recursive merging. But I am looking to something that does recursive or deep merging, very much the same like jQuery's $.extend(obj1, obj2) function.

Upon collision of two members, the following rules should apply:

  • If the types mismatch, an exception must be thrown and merge is aborted. Exception: obj2 Value maybe null, in this case the value & type of obj1 is used.
  • For trivial types (value types + string) obj1 values are always prefered
  • For non-trivial types, the following rules are applied:
    • IEnumerable & IEnumberables<T> are simply merged (maybe .Concat() ? )
    • IDictionary & IDictionary<TKey,TValue> are merged; obj1 keys have precedence upon collision
    • Expando & Expando[] types must be merged recursively, whereas Expando[] will always have same-type elements only
    • One can assume there are no Expando objects within Collections (IEnumerabe & IDictionary)
  • All other types can be discarded and need not be present in the resulting dynamic object

Here is an example of a possible merge:

dynamic DefaultConfig = new {
    BlacklistedDomains = new string[] { "domain1.com" },
    ExternalConfigFile = "blacklist.txt",
    UseSockets = new[] {
        new { IP = "127.0.0.1", Port = "80"},
        new { IP = "127.0.0.2", Port = "8080" }
    }
};

dynamic UserSpecifiedConfig = new {
    BlacklistedDomain = new string[] { "example1.com" },
    ExternalConfigFile = "C:\\my_blacklist.txt"
};

var result = Merge (UserSpecifiedConfig, DefaultConfig);
// result should now be equal to:
var result_equal = new {
    BlacklistedDomains = new string[] { "domain1.com", "example1.com" },
    ExternalConfigFile = "C:\\my_blacklist.txt",
    UseSockets = new[] {
        new { IP = "127.0.0.1", Port = "80"},
        new { IP = "127.0.0.2", Port = "8080" }
    }
};

Any ideas how to do this?

解决方案

Right, this is a bit longwinded but have a look. it's an implementation using Reflection.Emit.

Open issue for me is how to implement a ToString() override so that you can do a string comparison. Are these values coming from a config file or something? If they are in JSON Format you could do worse than use a JsonSerializer, I think. Depends on what you want.

You could use the Expando Object to get rid of the Reflection.Emit nonsense as well, at the bottom of the loop:

var result = new ExpandoObject();
var resultDict = result as IDictionary<string, object>;
foreach (string key in resVals.Keys)
{
    resultDict.Add(key, resVals[key]);
}
return result;

I can't see a way around the messy code for parsing the original object tree though, not immediately. I'd like to hear some other opinions on this. The DLR is relatively new ground for me.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            dynamic DefaultConfig = new
            {
                BlacklistedDomains = new string[] { "domain1.com" },
                ExternalConfigFile = "blacklist.txt",
                UseSockets = new[] { 
                    new { IP = "127.0.0.1", Port = "80" }, 
                    new { IP = "127.0.0.2", Port = "8080" } 
                }
            };

            dynamic UserSpecifiedConfig = new
            {
                BlacklistedDomains = new string[] { "example1.com" },
                ExternalConfigFile = "C:\\my_blacklist.txt"
            };

            var result = Merge(UserSpecifiedConfig, DefaultConfig);

            // result should now be equal to: 

            var result_equal = new
            {
                BlacklistedDomains = new string[] { "domain1.com", "example1.com" },
                ExternalConfigFile = "C:\\my_blacklist.txt",
                UseSockets = new[] {         
                    new { IP = "127.0.0.1", Port = "80"},         
                    new { IP = "127.0.0.2", Port = "8080" }     
                }
            };
            Debug.Assert(result.Equals(result_equal));
        }

        /// <summary>
        /// Merge the properties of two dynamic objects, taking the LHS as primary
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        static dynamic Merge(dynamic lhs, dynamic rhs)
        {
            // get the anonymous type definitions
            Type lhsType = ((Type)((dynamic)lhs).GetType());
            Type rhsType = ((Type)((dynamic)rhs).GetType());

            object result = new { };
            var resProps = new Dictionary<string, PropertyInfo>();
            var resVals = new Dictionary<string, object>();

            var lProps = lhsType.GetProperties().ToDictionary<PropertyInfo, string>(prop => prop.Name);
            var rProps = rhsType.GetProperties().ToDictionary<PropertyInfo, string>(prop => prop.Name); 


            foreach (string leftPropKey in lProps.Keys)
            {
                var lPropInfo = lProps[leftPropKey];
                resProps.Add(leftPropKey, lPropInfo);
                var lhsVal = Convert.ChangeType(lPropInfo.GetValue(lhs, null), lPropInfo.PropertyType);
                if (rProps.ContainsKey(leftPropKey))
                {
                    PropertyInfo rPropInfo;
                    rPropInfo = rProps[leftPropKey];
                    var rhsVal = Convert.ChangeType(rPropInfo.GetValue(rhs, null), rPropInfo.PropertyType);
                    object setVal = null;

                    if (lPropInfo.PropertyType.IsAnonymousType())
                    {
                        setVal = Merge(lhsVal, rhsVal);
                    }
                    else if (lPropInfo.PropertyType.IsArray)
                    {
                        var bound = ((Array) lhsVal).Length + ((Array) rhsVal).Length;
                        var cons = lPropInfo.PropertyType.GetConstructor(new Type[] { typeof(int) });
                        dynamic newArray = cons.Invoke(new object[] { bound });
                        //newArray = ((Array)lhsVal).Clone();
                        int i=0;
                        while (i < ((Array)lhsVal).Length)
                        {
                            newArray[i] = lhsVal[i];
                            i++;
                        }
                        while (i < bound)
                        {
                            newArray[i] = rhsVal[i - ((Array)lhsVal).Length];
                            i++;
                        }
                        setVal = newArray;
                    }
                    else
                    {
                        setVal = lhsVal == null ? rhsVal : lhsVal;
                    }
                    resVals.Add(leftPropKey, setVal);
                }
                else 
                {
                    resVals.Add(leftPropKey, lhsVal);
                }
            }
            foreach (string rightPropKey in rProps.Keys)
            {
                if (lProps.ContainsKey(rightPropKey) == false)
                {
                    PropertyInfo rPropInfo;
                    rPropInfo = rProps[rightPropKey];
                    var rhsVal = rPropInfo.GetValue(rhs, null);
                    resProps.Add(rightPropKey, rPropInfo);
                    resVals.Add(rightPropKey, rhsVal);
                }
            }

            Type resType = TypeExtensions.ToType(result.GetType(), resProps);

            result = Activator.CreateInstance(resType);

            foreach (string key in resVals.Keys)
            {
                var resInfo = resType.GetProperty(key);
                resInfo.SetValue(result, resVals[key], null);
            }
            return result;
        }
    }
}

public static class TypeExtensions
{
    public static Type ToType(Type type, Dictionary<string, PropertyInfo> properties)
    {
        AppDomain myDomain = Thread.GetDomain();
        Assembly asm = type.Assembly;
        AssemblyBuilder assemblyBuilder = 
            myDomain.DefineDynamicAssembly(
            asm.GetName(), 
            AssemblyBuilderAccess.Run
        );
        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(type.Module.Name);
        TypeBuilder typeBuilder = moduleBuilder.DefineType(type.Name,TypeAttributes.Public);

        foreach (string key in properties.Keys)
        {
            string propertyName = key;
            Type propertyType = properties[key].PropertyType;

            FieldBuilder fieldBuilder = typeBuilder.DefineField(
                "_" + propertyName,
                propertyType,
                FieldAttributes.Private
            );

            PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(
                propertyName,
                PropertyAttributes.HasDefault,
                propertyType,
                new Type[] { }
            );
            // First, we'll define the behavior of the "get" acessor for the property as a method.
            MethodBuilder getMethodBuilder = typeBuilder.DefineMethod(
                "Get" + propertyName,
                MethodAttributes.Public,
                propertyType,
                new Type[] { }
            );

            ILGenerator getMethodIL = getMethodBuilder.GetILGenerator();

            getMethodIL.Emit(OpCodes.Ldarg_0);
            getMethodIL.Emit(OpCodes.Ldfld, fieldBuilder);
            getMethodIL.Emit(OpCodes.Ret);

            // Now, we'll define the behavior of the "set" accessor for the property.
            MethodBuilder setMethodBuilder = typeBuilder.DefineMethod(
                "Set" + propertyName,
                MethodAttributes.Public,
                null,
                new Type[] { propertyType }
            );

            ILGenerator custNameSetIL = setMethodBuilder.GetILGenerator();

            custNameSetIL.Emit(OpCodes.Ldarg_0);
            custNameSetIL.Emit(OpCodes.Ldarg_1);
            custNameSetIL.Emit(OpCodes.Stfld, fieldBuilder);
            custNameSetIL.Emit(OpCodes.Ret);

            // Last, we must map the two methods created above to our PropertyBuilder to 
            // their corresponding behaviors, "get" and "set" respectively. 
            propertyBuilder.SetGetMethod(getMethodBuilder);
            propertyBuilder.SetSetMethod(setMethodBuilder);
        }

        //MethodBuilder toStringMethodBuilder = typeBuilder.DefineMethod(
        //    "ToString",
        //    MethodAttributes.Public,
        //    typeof(string),
        //    new Type[] { }
        //);

        return typeBuilder.CreateType();
    }
    public static Boolean IsAnonymousType(this Type type)
    {
        Boolean hasCompilerGeneratedAttribute = type.GetCustomAttributes(
            typeof(CompilerGeneratedAttribute), false).Count() > 0;
        Boolean nameContainsAnonymousType =
            type.FullName.Contains("AnonymousType");
        Boolean isAnonymousType = hasCompilerGeneratedAttribute && nameContainsAnonymousType;
        return isAnonymousType;
    }
}

这篇关于动态/扩展对象的C#深/嵌套/递归合并的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆