将C#对象与规则合并

时间:2020-09-10 14:01:39

标签: c# merge

我周围有一些很大的文件,我想通过反序列化自动加载它们,然后简单地合并在一起,如果可能的话,在一个中央对象中维护内存引用,以使合并尽可能地良性。

尽管合并似乎并不简单,但从概念上看似乎很简单

  • 如果有一些空值,请使用非空值
  • 如果存在冲突的对象,请进行更深入的挖掘并合并其内部结构,或者只喜欢其中一个,也许只是为那些要合并的类编写自定义代码。
  • 如果有集合,则一定要合并它们;如果有键(例如在字典中),请尝试添加它们;如果存在键冲突,则像以前一样,合并它们或选择一个。

我看到很多人都建议我使用Automapper尝试实现这一目标,尽管这似乎有缺陷。 Automapper不是为此而创建的,并且总体任务似乎还不够复杂,无法保证。将您所有特定于类的合并代码放到除该类之外的任何位置的封装也并不令人惊奇。与代码的给定方面相关的数据应位于类对象的中央位置,以使程序员能够更轻松地了解其周围数据结构的用法。因此,我认为自动映射器不是合并对象而不是简单地保留对象的好方法。

您如何建议自动将两个结构相同的c#对象与自定义类的嵌套继承结构合并?

我也会发布自己的解决方案,但是我鼓励其他开发人员(当然比我聪明得多)推荐解决方案。

2 个答案:

答案 0 :(得分:1)

我认为在约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);
}

答案 1 :(得分:1)

@JodySowald的答案描述了一种不错的通用方法,而合并在我看来听起来可能涉及很多类特定的业务逻辑。

我只需向层次结构中的每个类添加一个MergeWith方法,直到“合并”意味着简单的可重复泛型操作的级别。

class A
{
    string Description;
    B MyB {get; set;}

    public void MergeWith(A other)
    {
         // Simple local logic
         Description = string.IsNullOrWithSpace(Description) ? other.Description : Description;
         // Let class B do its own logic
         MyB = MyB.MergeWith(other.MyB);
    }
}