我认为在大约 70% 的用例中,有人会在类库中拥有包含许多类的大型层次结构,并且希望能够一次合并整个层次结构。为此,我认为代码应该遍历对象的属性和子类的嵌套属性,但仅限于在您创建的程序集中定义的属性。没有合并 System.String 的内部,谁知道会发生什么。因此,应该只挖掘该程序集的内部类型以进行进一步合并
var internalTypes = Assembly.GetExecutingAssembly().DefinedTypes;
我们还需要一种在给定类上定义自定义代码的方法,总会有边缘情况。我相信这就是创建接口的目的,为几个类通用定义功能并为特定类提供特定的实现。但是我发现,如果合并需要了解该类之上的分层数据,例如存储在字典中的键或指示存在数据类型或模式的枚举,则应该可以使用对包含数据结构的引用。所以我定义了一个快速接口,ICombinable
internal interface ICombinable
{
/// <summary>
/// use values on the incomingObj to set correct values on the current object
/// note that merging comes after the individual DO has been loaded and populated as necessary and is the last step in adding the objects to the central DO that already exists.
/// </summary>
/// <param name="containingDO"></param>
/// <param name="incomingObj">an object from the class being merged in.</param>
/// <returns></returns>
ICombinable Merge(object containingDO, ICombinable incomingObj);
}
将这些组合成一段功能代码基本上需要一点属性反射、一点递归和一点逻辑,所有这些都是有细微差别的,所以我只是评论了我的代码而不是解释它之前。由于目标是影响中心对象而不是创建新的合并副本,因此这是数据结构基类中的实例方法。但您可能很容易将其转换为辅助方法。
internal void MergeIn(Object singleDO)
{
var internalTypes = Assembly.GetExecutingAssembly().DefinedTypes;
var mergableTypes = internalTypes.Where(c => c.GetInterfaces().Contains(typeof(ICombinable)));
MergeIn(this, this, singleDO, internalTypes, mergableTypes);
}
private void MergeIn(Object centralDORef, object centralObj, object singleObj, IEnumerable<TypeInfo> internalTypes, IEnumerable<TypeInfo> mergableTypes)
{
var itemsToMerge = new List<MergeMe>();
//all at once to open up parallelization later.
IterateOver(centralObj, singleObj, (f, t, i) => itemsToMerge.Add(new MergeMe(t, f, i)));
//check each property on these structures.
foreach (var merge in itemsToMerge)
{
//if either is null take non-null
if (merge.From == null || merge.To == null)
merge.Info.SetValue(centralObj, merge.To ?? merge.From);
//if collection merge
else if (merge.Info.PropertyType.IsGenericType && merge.Info.PropertyType.GetGenericTypeDefinition().IsAssignableFrom(typeof(IList<>)))
foreach (var val in (IList)merge.From)
((IList)merge.To).Add(val);
//if dictionary merge
else if (merge.Info.PropertyType.IsGenericType && merge.Info.PropertyType.GetGenericTypeDefinition().IsAssignableFrom(typeof(IDictionary<,>)))
{
var f = ((IDictionary)merge.From);
var t = ((IDictionary)merge.To);
foreach (var key in f.Keys)
if (t.Contains(key))
{
//if conflicted, check for icombinable
if (merge.Info.PropertyType.GenericTypeArguments[1].IsAssignableFrom(typeof(ICombinable)))
t[key] = ((ICombinable)t[key]).Merge(centralDORef, (ICombinable)f[key]);
}
else
t.Add(key, f[key]);
}
//if both non null and not collections, merge.
else if (merge.From != null && merge.To != null)
{
//check for Icombinable.
if (merge.Info.PropertyType.IsAssignableFrom(typeof(ICombinable)))
merge.Info.SetValue(centralObj, ((ICombinable)merge.To).Merge(centralDORef, (ICombinable)merge.From));
//if we made the object, dig deeper
else if (internalTypes.Contains(merge.Info.PropertyType))
{
//recurse.
MergeIn(centralDORef, merge.To, merge.From, internalTypes, mergableTypes);
}
//else do nothing, keeping the original
}
}
}
private class MergeMe{
public MergeMe(object from, object to, PropertyInfo info)
{
From = from;
To = to;
Info = info;
}
public object From;
public object To;
public PropertyInfo Info;
}
private static void IterateOver<T>(T destination, T other, Action<object, object, PropertyInfo> onEachProperty)
{
foreach (var prop in destination.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
onEachProperty(prop.GetValue(destination), prop.GetValue(other), prop);
}