c# - 使用 LINQ 将新的 BindingList 与主 BindingList 协调

标签 c# linq merge list

我有一个看似简单的问题,我希望协调两个列表,以便“旧”主列表由包含更新元素的"new"列表更新。元素由键属性表示。这些是我的要求:

  • 仅当任何属性发生更改时,任一列表中具有相同键的所有元素都会导致将"new"列表中的元素分配给“旧”列表中的原始元素。
  • "new"列表中具有不在“旧”列表中的键的任何元素都将添加到“旧”列表中。
  • “旧”列表中具有不在"new"列表中的键的任何元素都将从“旧”列表中删除。

我在这里发现了一个等效问题 - Best algorithm for synchronizing two IList in C# 2.0 - 但它并没有真正得到正确的回答。因此,我想出了一种算法来遍历新旧列表并按照上述进行协调。在有人问我为什么不直接用新列表对象替换旧列表对象之前,这是为了演示目的——这是一个绑定(bind)到 GUI 上的网格的 BindingList,我需要防止刷新伪像,例如闪烁,滚动条移动等。因此列表对象必须保持不变,只有更新后的元素发生变化。

另一件需要注意的事情是,"new"列表中的对象,即使键相同并且所有属性都相同,与“旧”列表中的等效对象是完全不同的实例,因此复制引用不是一个选项。

下面是我到目前为止的想法 - 它是 BindingList 的通用扩展方法。我已添加评论以展示我正在尝试做的事情。

public static class BindingListExtension
{
    public static void Reconcile<T>(this BindingList<T> left,
                                    BindingList<T> right,
                                    string key)
    {
        PropertyInfo piKey = typeof(T).GetProperty(key);

        // Go through each item in the new list in order to find all updated and new elements
        foreach (T newObj in right)
        {
            // First, find an object in the new list that shares its key with an object in the old list
            T oldObj = left.First(call => piKey.GetValue(call, null).Equals(piKey.GetValue(newObj, null)));

            if (oldObj != null)
            {
                // An object in each list was found with the same key, so now check to see if any properties have changed and
                // if any have, then assign the object from the new list over the top of the equivalent element in the old list
                foreach (PropertyInfo pi in typeof(T).GetProperties())
                {
                    if (!pi.GetValue(oldObj, null).Equals(pi.GetValue(newObj, null)))
                    {
                        left[left.IndexOf(oldObj)] = newObj;
                        break;
                    }
                }
            }
            else
            {
                // The object in the new list is brand new (has a new key), so add it to the old list
                left.Add(newObj);
            }
        }

        // Now, go through each item in the old list to find all elements with keys no longer in the new list
        foreach (T oldObj in left)
        {
            // Look for an element in the new list with a key matching an element in the old list
            if (right.First(call => piKey.GetValue(call, null).Equals(piKey.GetValue(oldObj, null))) == null)
            {
                // A matching element cannot be found in the new list, so remove the item from the old list
                left.Remove(oldObj);
            }
        }
    }
}

可以这样调用:

_oldBindingList.Reconcile(newBindingList, "MyKey")

但是,我可能正在寻找一种使用 LINQ 类型方法(例如 GroupJoin<>、Join<>、Select<>、SelectMany<>、Intersect<> 等)执行相同操作的方法。到目前为止,问题我曾经遇到过,这些 LINQ 类型方法中的每一个都会产生全新的中间列表(作为返回值),实际上,出于上述所有原因,我只想修改现有列表。

如果有人可以提供帮助,将不胜感激。如果没有,不用担心,上面的方法(可以说是)现在就足够了。

谢谢, 杰森

最佳答案

你的主循环是 O(m*n),其中 mn 是旧的大小和新名单。这很糟糕。一个更好的想法可能是首先构建关键元素映射集,然后再处理它们。此外,避免反射是个好主意——可以使用 lambda 作为键选择器。所以:

 public static void Reconcile<T, TKey>(
     this BindingList<T> left,
     BindingList<T> right,
     Func<T, TKey> keySelector)
 {
     var leftDict = left.ToDictionary(l => keySelector(l));

     foreach (var r in right)
     {
         var key = keySelector(r);
         T l;
         if (leftDict.TryGetValue(key, out l))
         {
              // copy properties from r to l
              ...
              leftDict.RemoveKey(key);
         }
         else
         {
              left.Add(r);
         }
     }

     foreach (var key in leftDict.Keys)
     {
         left.RemoveKey(key);
     }
 }

对于复制属性,我也会避免反射 - 要么为此创建一个类似于 ICloneable 的接口(interface),但用于在对象之间传输属性而不是创建新实例,并拥有所有对象执行它;或者,通过另一个 lambda 将其提供给 Reconcile

关于c# - 使用 LINQ 将新的 BindingList 与主 BindingList 协调,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1633320/

相关文章:

c# - 阻塞问题 Deserializing XML to object 问题

c# - 如何格式化数据计时器以插入数据库

c# - OrderBy ThenBy - 捕获剩余(相等)项目的最简单方法?

c# - 在 EF 中保存对象时,不会在数据库中建立新的多对多连接

r - 从数据集查询

c# - 在 C++ 和 C# 应用程序之间进行异步通信的最简单方法是什么

C# MongoDB 存储库架构

c# - 如何在 Linq 查询中执行加法

php - 在 PHP 中合并两张图片并将其另存为一张(一张透明背景)

linux - 如何合并文件中的特定列?