54

イベントに関して3つの質問があります。

  1. 購読したイベントは常に購読を解除する必要がありますか?
  2. そうしないとどうなりますか?
  3. 以下の例では、サブスクライブされたイベントからどのようにサブスクライブを解除しますか?

たとえば、次のコードがあります。

コンストラクター:目的:データベースプロパティの更新用

this.PropertyChanged += (o, e) =>
{
    switch (e.PropertyName)
    {
        case "FirstName": break;
        case "LastName": break;
    }
};

そしてこれ:目的:GUIバインディングの場合、モデルをビューモデルにラップします

ObservableCollection<Period> periods = _lpRepo.GetDailyLessonPlanner(data.DailyDate);
PeriodListViewModel = new ObservableCollection<PeriodViewModel>();

foreach (Period period in periods)
{
    PeriodViewModel periodViewModel = new PeriodViewModel(period,_lpRepo);
    foreach (DocumentListViewModel documentListViewModel in periodViewModel.DocumentViewModelList)
    {
        documentListViewModel.DeleteDocumentDelegate += new Action<List<Document>>(OnDeleteDocument);
        documentListViewModel.AddDocumentDelegate += new Action(OnAddDocument);
        documentListViewModel.OpenDocumentDelegate += new Action<int, string>(OnOpenDocument);
    }
    PeriodListViewModel.Add(periodViewModel);
}
4

5 に答える 5

66

さて、最初に最後の質問をしましょう。ラムダ式を使用して直接サブスクライブしたイベントから確実にサブスクライブを解除することはできません。デリゲートを使用して変数を保持する必要がある(ラムダ式を引き続き使用できるようにするため)代わりにメソッドグループ変換を使用する必要があります。

実際に登録を解除する必要があるかどうかは、イベントプロデューサーとイベントコンシューマーの関係によって異なります。イベントプロデューサーがイベントコンシューマーよりも長く存続する必要がある場合は、サブスクライブを解除する必要があります。そうしないと、プロデューサーがコンシューマーへの参照を持ち、本来よりも長く存続するためです。イベントハンドラーも、プロデューサーが作成する限り呼び出され続けます。

多くの場合、これは問題ではありません。たとえば、イベントを発生させるボタンはClick、ハンドラーが通常サブスクライブされている、イベントが作成されたフォームとほぼ同じ長さの間存続する可能性があります...したがって、必要はありません。退会するには。これはGUIでは非常に一般的です。

同様WebClientに、単一の非同期リクエストのみを目的として作成し、関連するイベントをサブスクライブして非同期リクエストを開始するとWebClient、リクエストが終了したときに、それ自体がガベージコレクションの対象になります(他の場所に参照を保持していない場合)。 )。

基本的に、あなたは常に生産者と消費者の間の関係を考慮する必要があります。プロデューサーが消費者の希望よりも長生きする場合、またはイベントに興味がなくなった後もイベントを継続して開催する場合は、登録を解除する必要があります。

于 2010-11-13T13:54:07.293 に答える
37

1)状況によります。通常はそれは良い考えですが、あなたがそうする必要がない典型的なケースがあります。基本的に、サブスクライブするオブジェクトがイベントソースよりも長持ちすることが確実な場合は、サブスクライブを解除する必要があります。そうしないと、不要な参照が作成されます。

ただし、次のように、オブジェクトが独自のイベントをサブスクライブしている場合:

<Window Loaded="self_Loaded" ...>...</Window>

-そうする必要はありません。

2)イベントをサブスクライブすると、サブスクライブしているオブジェクトへの追加の参照が作成されます。したがって、サブスクライブを解除しないと、この参照によってオブジェクトが存続し、事実上メモリリークが発生する可能性があります。登録を解除すると、その参照が削除されます。セルフサブスクリプションの場合、問題は発生しないことに注意してください。

3)あなたはそのようにすることができます:

this.PropertyChanged += PropertyChangedHandler;
...
this.PropertyChanged -= PropertyChangedHandler;

どこ

void PropertyChangedHandler(object o, PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case "FirstName": break;
        case "LastName": break;
    }
}
于 2010-11-13T13:53:49.690 に答える
7

サブスクライブしているインスタンスがサブスクライブしているインスタンスと同じスコープを持っている場合は、イベントからサブスクライブを解除する必要はありません。

あなたがフォームであり、コントロールにサブスクライブしているとしましょう。これら2つが一緒になってグループを形成します。ただし、フォームを管理する中央クラスがありClosed、そのフォームのイベントにサブスクライブしている場合、これらは一緒にグループを形成しないため、フォームを閉じたらサブスクライブを解除する必要があります。

イベントをサブスクライブすると、サブスクライブされたインスタンスは、サブスクライブされているインスタンスへの参照を作成します。これにより、ガベージコレクションが防止されます。したがって、フォームインスタンスを管理する中央クラスがある場合、これによりすべてのフォームがメモリに保持されます。

WPFは、イベントが弱参照を使用してサブスクライブされる弱いイベントモデルを持ち、フォームをメモリに保持しないため、例外です。ただし、フォームに参加していない場合は、登録を解除することをお勧めします。

于 2010-11-13T13:55:39.070 に答える
5

MSDNのこの記事をご覧ください。引用:

イベントが発生したときにイベントハンドラーが呼び出されないようにするには、イベントのサブスクライブを解除するだけです。リソースリークを防ぐために、サブスクライバーオブジェクトを破棄する前にイベントのサブスクライブを解除することが重要です。イベントのサブスクライブを解除するまで、パブリッシングオブジェクトのイベントの基礎となるマルチキャストデリゲートには、サブスクライバーのイベントハンドラーをカプセル化するデリゲートへの参照があります。パブリッシングオブジェクトがその参照を保持している限り、サブスクライバーオブジェクトはガベージコレクションされません。

于 2010-11-13T13:51:19.770 に答える
1

1.)サブスクライブされたイベントを常にデサブスクライブする必要がありますか?
通常はそうです。唯一の例外は、サブスクライブしたオブジェクトが参照されなくなり、まもなくガベージコレクションされる場合です。

2.)そうしないとどうなりますか?
サブスクライブしたオブジェクトはデリゲートへの参照を保持し、デリゲートはそのポインターへの参照を保持するthisため、メモリリークが発生します。
または、ハンドラーがlamdaの場合、バインドされたローカル変数を保持するため、どちらも収集されません。

于 2010-11-13T13:53:58.080 に答える