0

次のような単純なオブジェクトを含む 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;
    }
}

固定数 (100,000) のオブジェクトと BindingSource を含む BindingList があります。どちらも次のように定義されています。

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

最後に、DataGridView コントロールがあります。オブジェクトの StringValue プロパティを表示する単一の列 ("STRINGVALUECOLUMN") があり、先ほど述べた BindingSource にバインドされています。

dataGridViewMyObjects.DataSource = bindingSourceForObjects;

アプリケーションの開始時に、100,000 個のオブジェクトを myListOfObjects に追加します。DGV には 1 つの列しかなく、表示されるプロパティは string.Empty に初期化されているため、基本的に 100,000 の「空白」行を含む DGV があります。この時点で、ユーザーは行の編集を開始して文字列を入力できます。それらを任意の順序で編集する必要がないため、1 つの文字列を最初の行に、次の文字列を 17 行目に、次の文字列を 24581 行目に、というように配置できます。ユーザーは、テキスト ファイルから文字列をインポートしたい場合があります。 . オブジェクトの数は固定 (100,000) で、既存の文字列が既に入力されている場合と入力されていない場合があるため、新しい文字列を追加する前に、インポート プロセス中にいくつかのチェックを実行する必要があります。以下のコードでは、これらのチェックを削除しましたが、アプリケーションのパフォーマンスには影響していないようです。ただし、以下のコードを使用して何万もの文字列をインポートすると、非常に遅くなります (5 万行をインポートするのに 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 に対してこれら 2 つの操作を実行する、より高速で効率的な方法はありますか?

4

2 に答える 2

2

Linq についてのことは、それが実際には単なる標準ループであり、もちろん最適化されていますが、それでも通常の古いループであり、バック コードに戻っています。

コードを高速化する可能性があることの 1 つは、次のとおりです。

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

これは単純なブール値、X が存在するかを返します。早期に終了するため、必要がなければコレクション全体をスキャンしません。 .Count()全体をスキャンするだけでなく、実行中のカウントを維持する必要があります。

を使用しているため、指摘すべきもう1つのことはFirstOrDefault、結果がnullになる可能性があることを示しています。使用する前に、null チェックがオンになっていることを確認してくださいmyobject

最後に、Saunders 氏の提案に従って、イベント スタックをチェックし、思ったより多くのコードが実行されていないことを確認します。これは、このような操作では危険です。this.SuspendLayout()初期化エンジンからコードを借りて、とを使用する必要がある場合があります。this.ResumeLayout()

于 2013-02-11T19:11:30.050 に答える
1

問題は、基になるデータを更新すると、イベントが発生してグリッドが更新されることです。大量のデータ変更 == 大量の更新。

Windows フォームで多くのことを行ってから長い時間が経ちましたが、そのSuspendLayout方法を確認してください。

于 2013-02-11T16:18:31.420 に答える