我需要在C#中“合并”2个动态对象。我在stackexchange上发现的所有内容都只包含非递归合并。但我正在寻找一些递归或深度合并的东西,就像jQuery's $.extend(obj1, obj2)
函数一样。
两名成员发生碰撞时,应遵循以下规则:
IEnumerable
&amp; IEnumberables<T>
只是简单合并(也许是.Concat()
?)IDictionary
&amp; IDictionary<TKey,TValue>
已合并; obj1键在碰撞时具有优先权Expando
&amp; Expando[]
类型必须递归合并,而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 :(得分: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;
}
}
答案 1 :(得分:0)
这对我有用,但是我相信它可以得到一些爱和关注,并且看起来会更好。它不包括您的类型检查,但是添加起来很简单。因此,尽管这不是一个完美的答案,但我希望它能使您更接近解决方案。
随后对DynamicIntoExpando(...)的调用将继续向现有Source结构附加和覆盖新值和现有值。您可以根据需要多次调用它。函数MergeDynamic()说明了两个动态如何合并到一个ExpandoObject中。
代码基本上遍历动态值,检查类型,并适当地递归合并到任何深度。
出于个人目的,我将其包装在帮助器类中。
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<string, dynamic>;
if (Source is IDictionary<string, dynamic>)
{
var S = Source as IDictionary<string, dynamic>;
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<string, dynamic>;
foreach (string key in S.Keys)
{
ExpandoObject NewDictEntry = new ExpandoObject();
var NDE = NewDictEntry as IDictionary<string, dynamic>;
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<string, dynamic>)NewDictEntry)[key];
}
}
if (Key == null)
{
R = NewDict;
}
else if (!R.ContainsKey(Key))
{
R.Add(Key, NewDict);
}
}
else if (Source is IList<dynamic>)
{
var S = Source as IList<dynamic>;
List<ExpandoObject> NewList = new List<ExpandoObject>();
if (Key != null && R.ContainsKey(Key))
{
// Already exists, overwrite
NewList = (List<ExpandoObject>)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;
}
}
}