2012-01-27 88 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; 
    } 
}