【问题标题】:Fastest way to execute LINQ against a BindingList?针对 BindingList 执行 LINQ 的最快方法?
【发布时间】:2013-02-11 16:16:36
【问题描述】:

我正在编写一个包含这样一个简单对象的 WinForms 应用程序:

public class MyObject : INotifyPropertyChanged //  for two-way data binding
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged([CallerMemberName] string caller = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(caller));
        }
    }

    private int _IndexValue;
    public int IndexValue
    {
        get { return Value; }
        set
        {
            if (value != Value)
            {
                Value = value;
                RaisePropertyChanged();
            }
        }
    }

    private string _StringValue;
    public string StringValue
    {
        get { return _StringValue; }
        set
        {
            if (value != _StringValue)
            {
                _StringValue = value;
                _Modified = true;
                RaisePropertyChanged();
            }
        }
    }

    private bool _Modified;
    public bool Modified
    {
        get { return _Modified; }
        set
        {
            if (value != _Modified)
            {
                _Modified = value;
                RaisePropertyChanged();
            }
        }
    }

    public MyObject(int indexValue)
    {
        IndexValue = indexValue;
        StringValue = string.Empty;
        Modified = false;
    }
}

我有一个 BindingList,它将包含我的对象的固定数量 (100,000) 以及一个 BindingSource。这两个都是这样定义的:

BindingList<MyObject> myListOfObjects = new BindingList<MyObject>();
BindingSource bindingSourceForObjects = new BindingSource();
bindingSourceForObjects .DataSource = myListOfObjects;

最后,我有了我的 DataGridView 控件。它有单列(“STRINGVALUECOLUMN”),显示我的对象的 StringValue 属性,并且绑定到我刚才提到的 BindingSource:

dataGridViewMyObjects.DataSource = bindingSourceForObjects;

当我的应用程序启动时,我将 100,000 个对象添加到 myListOfObjects。由于我的 DGV 中只有一列,并且它显示的属性初始化为 string.Empty,因此我基本上有一个包含 100,000 个“空白”行的 DGV。此时,我的用户可以开始编辑行以输入字符串。他们不必按任何顺序编辑它们,因此他们可能会在第一行中放置一个字符串,在第 17 行中放置下一个字符串,在第 24581 行中放置下一个字符串等。有时,我的用户会想要从文本文件中导入字符串.由于我有固定数量的对象 (100,000),并且可能已经输入了一些现有字符串,也可能没有输入,因此在添加新字符串之前,我需要在导入过程中执行一些检查。在下面的代码中,我删除了这些检查,但它们似乎不会影响我的应用程序的性能。但是,如果我使用下面的代码导入数万个字符串,它会非常慢(比如导入 50k 行需要 4 或 5 分钟)。我已将其范围缩小到这段代码中的某些内容:

// this code is inside the loop that reads each line from a file...

// does this string already exist?
int count = myListOfObjects.Count(i => i.StringValue == stringFromFile);
if (count > 0)
{
    Debug.WriteLine("String already exists!"); // don't insert strings that already exist
}
else
{   
    // find the first object in myListOfObjects that has a .StringValue property == string.Empty and then update it with the string read from the file
    MyObject myObject = myListOfObjects.FirstOrDefault(i => i.StringValue == string.Empty);
    myObject.StringValue = stringFromFile;
}

据我了解,我需要双向绑定,这样我才能更新基础数据并将其反映在 DGV 控件中,但我还了解到 INotifyPropertyChanged 有时会很慢。有没有人遇到过这个问题?如果有,你是怎么解决的?

-- 更新--

仅出于测试目的,我替换了:

// does this string already exist?
int count = myListOfObjects.Count(i => i.StringValue == stringFromFile);
if (count > 0)
{
    Debug.WriteLine("String already exists!"); // don't insert strings that already exist
}
else
{   
    // find the first object in myListOfObjects that has a .StringValue property == string.Empty and then update it with the string read from the file
    MyObject myObject = myListOfObjects.FirstOrDefault(i => i.StringValue == string.Empty);
    myObject.StringValue = stringFromFile;
}

for 循环包含:

myListOfObjects[counter].StringValue = "some random string";

即使有 100,000 个对象,这也非常快。但是,我现在失去了以下能力:1)在分配之前检查我从文件中读取的字符串是否已经分配给列表中的对象,以及 2)在列表中找到其 StringValue 的第一个可用对象property == string.Empty 然后相应地更新该值。所以看来:

int count = myListOfObjects.Count(i => i.StringValue == stringFromFile);

MyObject myObject = myListOfObjects.FirstOrDefault(i => i.StringValue == string.Empty);

...是我的性能问题的根源。有没有更快、更有效的方法来对我的 BindingList 执行这两个操作?

【问题讨论】:

  • 我不确定,但我认为您不需要 INotifyPropertyChanged。我一直发现 Gridview 和 BindingSource 在跟踪对象方面做得“足够好”。你试过去掉接口吗?
  • @JensKloster 删除 INotifyPropertyChanged 似乎会导致对基础数据的更改未反映在我的 DataGridView 中。
  • 那么你不应该这样做:)。我认为我从来没有遇到过这个问题。 什么改变了底层数据源?

标签: c# winforms performance linq datagridview


【解决方案1】:

关于 Linq 的问题在于它实际上只是标准循环,当然经过优化,但仍然是常规的旧循环,回到后面的代码中。

可以加快您的代码速度的一件事是:

myListOfObjects.Any(i => i.StringValue.Equals(stringFromFile));

这返回一个简单的布尔值,X 是否存在。它会提前退出,因此如果不需要,它不会扫描整个集合。 .Count() 不仅需要扫描整个事物,还需要保持运行计数。

另外需要指出的是,由于您使用的是FirstOrDefault,这表明结果可能为空。在尝试使用之前,请确保您对 myobject 进行了空检查。

最后,按照 Saunders 先生的建议,检查事件堆栈并确保运行的代码没有超出您的想象。在这样的操作中这是一个危险。您可能需要从初始化引擎中借用一些代码并使用this.SuspendLayout()this.ResumeLayout()

【讨论】:

    【解决方案2】:

    问题可能是当您更新基础数据时,会触发事件以导致网格更新。大量数据变化 == 大量更新。

    我已经很久没有使用 Windows 窗体做了很多工作了,但请查看 SuspendLayout 方法。

    【讨论】:

    • 哦,好点。注意您的事件堆栈,确保在导入数据时禁用网格更新,不要在执行此类操作时运行重绘。
    猜你喜欢
    • 2011-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-15
    • 1970-01-01
    • 2019-10-14
    • 1970-01-01
    • 2018-07-26
    相关资源
    最近更新 更多