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

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

问题描述

我要合并在C#2动态对象。所有这一切我已经在stackexchange只覆盖非递归合并找到。但我期待的东西,做递归或深部合并,大同小异,如 jQuery的 $。延长( 。OBJ1,OBJ2) 功能

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:


  • 如果类型不匹配,异常必须抛出和合并被中止。例外:obj2的值可能为空,在这种情况下,值&放大器; OBJ1的类型使用。

  • 对于琐碎的类型(值类型+字符串)OBJ1值总是首选

  • 对于重要的类型,下面的规则应用于:

    • 的IEnumerable &安培; IEnumberables< T> 被简单地合并(?也许 .Concat()

    • 的IDictionary &安培; 的IDictionary< TKEY的,TValue> 合并; OBJ1键有冲突时优先

    • 为Expando &安培; Expando的[] 类型必须递归合并,而为Expando []总会有同类型的元素只有

    • 我们可以设想有没有为Expando类别中的对象(IEnumerabe&放大器; IDictionary的)

    • 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)

      下面是一个可能的合并的例子:

      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?

      推荐答案

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

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

      对我开放的问题是如何实现的ToString()重写,这样你可以做一个字符串比较。这些数值从一个配置文件或东西来吗?如果他们在JSON格式,你可以做得比使用JsonSerializer糟糕的是,我想。 ,取决于你想要什么

      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.

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

      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;
      



      我看不到周围的乱码一种方式,尽管解析原始对象树,不会立即。我想听听这方面的一些其他的意见。该DLR是相对较新的地对我

      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#深/嵌套/递归动态/的expando对象合并的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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