首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用LINQ将新的BindingList调整为主BindingList

使用LINQ将新的BindingList调整为主BindingList
EN

Stack Overflow用户
提问于 2009-10-27 20:00:37
回答 5查看 2.6K关注 0票数 1

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

  • 只有当任何属性发生更改时,两个列表中具有相同键的所有元素都会从“旧”列表中的原始元素的“新”列表中分配该元素。
  • “新”列表中的任何元素如果有键不在“旧”列表中,将被添加到“旧”列表中。
  • “旧”列表中的任何元素如果有键不在“新”列表中,都将从“旧”列表中删除。

我在这里发现了一个类似的问题-- 在IList 2.0中同步两个IList的最佳算法 --但是这个问题并没有得到正确的回答。因此,我想出了一个算法来迭代旧的和新的列表,并按照上面的方法执行协调。在有人问我为什么不只是将旧的list对象全部替换为新的list对象之前,这是为了表示--这是一个绑定到图形用户界面上的网格的BindingList,我需要防止刷新工件,比如闪烁、滚动条等。所以列表对象必须保持不变,只有更新的元素才会改变。

另外要注意的是,“新”列表中的对象(即使键相同,所有属性都相同)与“旧”列表中的等效对象是完全不同的实例,因此复制引用不是一个选项。

下面是我到目前为止提出的--这是一个BindingList的泛型扩展方法。我已经发表了一些评论来展示我想要做的事情。

代码语言:javascript
复制
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);
            }
        }
    }
}

它可以这样称呼:

代码语言:javascript
复制
_oldBindingList.Reconcile(newBindingList, "MyKey")

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

如果有人能帮忙的话,我会非常感激的。如果没有,不用担心,上述方法(以前的方法)目前已经足够了。

谢谢,杰森

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2009-10-27 20:18:55

您的主循环是O(m*n),其中m和n是新旧列表的大小。这太糟糕了。更好的方法可能是先构建一组关键元素映射,然后再对它们进行处理。同时,避免反射也是个好主意--可以用lambda作为键选择器。所以:

代码语言:javascript
复制
 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,而是在对象之间传输属性,而不是创建新实例,并让所有对象实现它;或者,通过另一个lambda将其提供给Reconcile

票数 4
EN

Stack Overflow用户

发布于 2009-10-27 20:16:30

我不确定BindingList,但是您可以使用连续LINQ来对抗ObservableCollection<T>来完成这个任务。与定期协调列表不同,连续LINQ将创建一个只读列表,该列表将根据您查询的列表中的更改通知进行更新,如果您的对象实现了INotifyPropertyChanged,则从列表中的对象中更新。

这将允许您使用LINQ,而无需每次生成新的列表。

票数 1
EN

Stack Overflow用户

发布于 2009-10-27 20:11:32

建议:

不要使用string key,而是使用Expression<Func<T,object>> key

举个例子让你去:

代码语言:javascript
复制
class Bar
{
  string Baz { get; set; }

  static void Main()
  {
    Foo<Bar>(x => x.Baz);
  }

  static void Foo<T>(Expression<Func<T, object>> key)
  {
    // what do we have here?
    // set a breakpoint here
    // look at key
  }
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/1633320

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档