web-dev-qa-db-ja.com

イベントの購読を解除する必要がありますか?

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

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

たとえば、このコードがあります:

Ctor:目的:データベースプロパティの更新用

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);
}
41
Elisabeth

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;
    }
}
28
Vlad

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

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

現在、多くの場合、これは問題ではありません。たとえば、フォームでは、Clickイベントを発生させるボタンは、通常、ハンドラーがサブスクライブされる作成されたフォームと同じくらい長く存続します。 ..そのため、退会する必要はありません。これは、GUIの非常に典型的な例です。

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

基本的に、プロデューサーとコンシューマーの関係を常に考慮する必要があります。プロデューサーがコンシューマーの期待よりも長生きする場合、または興味がなくなった後もイベントを発生させ続けるので、購読を解除する必要があります。

55
Jon Skeet

MSDNの この記事 をご覧ください。見積もり:

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

5
Darin Dimitrov

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

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

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

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

4

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

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

1
CodesInChaos