【问题标题】:Merging C# objects with rules将 C# 对象与规则合并
【发布时间】:2020-12-29 01:12:49
【问题描述】:

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

虽然合并似乎并不简单,但在概念上似乎很容易

  • 如果有一些空值,请使用非空值
  • 如果存在冲突的对象,请深入挖掘并合并它们的内部结构,或者只选择一个,也许编写自定义代码来合并这些类。
  • 如果有集合,一定要合并它们,如果有键,比如在字典里,尝试添加它们,当有键冲突时,像以前一样,合并它们或者更喜欢一个。

我看到很多人在堆栈周围推荐我使用 Automapper 来尝试实现这一点,尽管这似乎有缺陷。 Automapper 不是为此而设计的,整体任务似乎不够复杂,不足以保证它的存在。将所有特定于类的合并代码放在除该类之外的任何地方也不是令人惊奇的封装。与代码的给定方面有关的数据应位于中心位置,例如类对象,以使程序员能够更容易地理解围绕它们的数据结构的用法。所以我不认为 automapper 是合并对象的好解决方案,而不是简单地保留一个。

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

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

【问题讨论】:

    标签: c# merge


    【解决方案1】:

    虽然@JodySowald 的回答描述了一种很好的通用方法,但 merging 在我看来可能涉及大量特定于类的业务逻辑。

    我只需将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);
        }
    }
    

    【讨论】:

    • 我同意在每个类中都有合并功能会很好,但它的可扩展性不高,开发人员每次实现新结构时都需要记住实现这两个功能,并且具体列出每个类中每个属性的合并功能很麻烦。这是反射和通用递归的目标。并且 ICombinable 接口的目的是完全允许您仅在需要时引用此功能。
    • 我想这取决于用例。如果它必须从一开始就具有可扩展性,那么您的解决方案当然更可取。另一方面,对于直接的一次性实现(例如连接到不再维护的遗留应用程序),我更喜欢我的,因为它具有较少的概念开销。如果有必要,我总是可以从我的想法切换到你的想法,只需要一堆复制/粘贴操作,所以最初我倾向于 YAGNI(虽然我不知道 OP 用例的细节)跨度>
    • 这很公平,我的预期用例不是迁移。为此,请使用 Automapper 之类的工具,它非常适合。我的是对称数据结构,其中大部分数据是配置和字典,因此偏爱非空数据和罕见的冲突
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 2019-08-06
      • 1970-01-01
      • 2016-03-16
      • 2019-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-28
      • 1970-01-01
      相关资源
      最近更新 更多