Unione # profonda / nidificata / ricorsiva di oggetti dinamici / expando

Ho bisogno di “unire” 2 oggetti dinamici in C #. Tutto quello che ho trovato su stackexchange riguardava solo la fusione non ricorsiva. Ma sto cercando qualcosa che faccia una fusione ricorsiva o profonda, molto simile alla funzione $.extend(obj1, obj2) .

In seguito alla collisione di due membri, si dovrebbero applicare le seguenti regole:

  • Se i tipi non corrispondono, deve essere generata un’eccezione e l’unione viene interrotta. Eccezione: obj2 Valore forse nullo, in questo caso viene utilizzato il valore e il tipo di obj1.
  • Per i tipi banali (tipi di valore + stringa) i valori obj1 sono sempre preferiti
  • Per i tipi non banali, vengono applicate le seguenti regole:
    • IEnumerable & IEnumberables sono semplicemente uniti (forse .Concat() ?)
    • IDictionary & IDictionary sono uniti; i tasti obj1 hanno la precedenza sulla collisione
    • Expando & Expando[] devono essere uniti in modo ricorsivo, mentre Expando [] avrà sempre solo elementi dello stesso tipo
    • Si può supporre che non vi siano oggetti Expando all’interno di Collections (IEnumerabe & IDictionary)
  • Tutti gli altri tipi possono essere scartati e non è necessario essere presenti nell’object dinamico risultante

Ecco un esempio di una ansible unione:

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

Qualche idea su come fare questo?

Giusto, questo è un po ‘prolisso ma dai un’occhiata. è un’implementazione che usa Reflection.Emit.

Il problema aperto per me è come implementare un override di ToString () in modo da poter eseguire un confronto tra stringhe. Questi valori provengono da un file di configurazione o qualcosa del genere? Se sono in formato JSON, potresti fare peggio di usare un JsonSerializer, penso. Dipende da cosa vuoi.

È ansible utilizzare l’object Expando per sbarazzarsi delle sciocchezze di Reflection.Emit pure, nella parte inferiore del ciclo:

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

Non riesco però a vedere un modo per aggirare il codice disordinato per analizzare l’albero degli oggetti originale, non immediatamente. Mi piacerebbe sentire altre opinioni su questo. Il DLR è relativamente nuovo per 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)); } ///  /// Merge the properties of two dynamic objects, taking the LHS as primary ///  ///  ///  ///  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(); var resVals = new Dictionary(); var lProps = lhsType.GetProperties().ToDictionary(prop => prop.Name); var rProps = rhsType.GetProperties().ToDictionary(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 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; } } 

Questo funziona per me, ma sono sicuro che potrebbe essere dato un po ‘di amore e attenzione e un aspetto migliore. Non include il tuo controllo di tipo, ma sarebbe piuttosto banale da aggiungere. Quindi, anche se questa non è una risposta perfetta, spero che possa avvicinarti a una soluzione.

Le chiamate successive a DynamicIntoExpando (…) continueranno ad accodare e sovrascrivere i valori nuovi ed esistenti alla struttura Source esistente. Puoi chiamarlo tutte le volte che vuoi. La funzione MergeDynamic () illustra come due dinamiche vengono unite in un ExpandoObject.

Il codice in pratica esegue iterazioni sul valore dinamico, controlla il tipo e si unisce in modo appropriato e ricorsivo a qualsiasi profondità.

L’ho avvolto in una class di supporto per i miei scopi.

 using System.Dynamic; // For ExpandoObject ... public static class DynamicHelper { // We expect inputs to be of type IDictionary public static ExpandoObject MergeDynamic(dynamic Source, dynamic Additional) { ExpandoObject Result = new ExpandoObject(); // First copy 'source' to Result DynamicIntoExpando(Result, Source); // Then copy additional fields, boldy overwriting the source as needed DynamicIntoExpando(Result, Additional); // Done return Result; } public static void DynamicIntoExpando(ExpandoObject Result, dynamic Source, string Key = null) { // Cast it for ease of use. var R = Result as IDictionary; if (Source is IDictionary) { var S = Source as IDictionary; ExpandoObject NewDict = new ExpandoObject(); if (Key == null) { NewDict = Result; } else if (R.ContainsKey(Key)) { // Already exists, overwrite NewDict = R[Key]; } var ND = NewDict as IDictionary; foreach (string key in S.Keys) { ExpandoObject NewDictEntry = new ExpandoObject(); var NDE = NewDictEntry as IDictionary; if (ND.ContainsKey(key)) { NDE[key] = ND[key]; } else if (R.ContainsKey(key)) { NDE[key] = R[key]; } DynamicIntoExpando(NewDictEntry, S[key], key); if(!R.ContainsKey(key)) { ND[key] = ((IDictionary)NewDictEntry)[key]; } } if (Key == null) { R = NewDict; } else if (!R.ContainsKey(Key)) { R.Add(Key, NewDict); } } else if (Source is IList) { var S = Source as IList; List NewList = new List(); if (Key != null && R.ContainsKey(Key)) { // Already exists, overwrite NewList = (List)R[Key]; } foreach (dynamic D in S) { ExpandoObject ListEntry = new ExpandoObject(); DynamicIntoExpando(ListEntry, D); // in this case we have to compare the ListEntry to existing entries and on NewList.Add(ListEntry); } if (Key != null && !R.ContainsKey(Key)) { R[Key] = NewList.Distinct().ToList(); } } else { R[Key] = Source; } } }