2012-01-27 56 views
8

我需要在C#中“合并”2个动态对象。我在stackexchange上找到的所有内容仅涵盖非递归合并。但我期待的是递归或深度合并,与jQuery's $.extend(obj1, obj2)函数非常相似。C#深度/嵌套/递归合并动态/扩展对象

在两个成员的碰撞,下列规则应适用:

  • 如果类型不匹配,异常必须抛出和合并被中止。例外:obj2值可能为null,在这种情况下,值使用&类型的obj1。
  • 对于琐碎类型(值类型+字符串)OBJ1值总是者优先
  • 对于非平凡的类型,则应用下面的规则:
    • IEnumerable & IEnumberables<T>被简单地合并(也许.Concat()?)
    • IDictionary & IDictionary<TKey,TValue>被合并; OBJ1键具有优先在碰撞
    • Expando & Expando[]类型必须被递归合并,而为Expando []将始终具有相同类型的元件仅
    • 人们可以假定有类别(IEnumerabe & IDictionary的)
    • 内没有Expando的对象
  • 所有其他类型的可以被丢弃,并且不需要存在于所得到的动态对象

这里是一个可能的合并的例子:

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

任何想法如何做到这一点?

+0

您是否遇到了递归逻辑或需要确定类型的实际调用问题。 – 2012-01-27 19:51:19

回答

3

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

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

您可以使用Expando的对象摆脱Reflection.Emit的废话,以及,在循环的底部:

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

我不能看到周围的乱码的方式进行解析原来的对象树,但不是立即。我想听听其他一些意见。 DLR对我来说是比较新的领域。

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