前の記事では、最も一般的なイベント パターンについて説明しました。 .NET Core には、より緩やかなパターンがあります。 このバージョンでは、EventHandler<TEventArgs>
定義には、TEventArgs
から派生したクラスである必要System.EventArgs
制約がなくなりました。
これにより、柔軟性が向上し、下位互換性があります。 柔軟性から始めましょう。
System.EventArgsの実装では、System.Object 1 つのメソッド (オブジェクトの浅いコピーを作成する MemberwiseClone()) で定義されたメソッドを使用します。 そのメソッドは、 EventArgs
から派生したクラスの機能を実装するためにリフレクションを使用する必要があります。 この機能は、特定の派生クラスで作成する方が簡単です。 つまり、System.EventArgs から派生することは、設計を制限する制約ですが、追加の利点はありません。 実際には、FileFoundArgs
から派生しないように、SearchDirectoryArgs
とEventArgs
の定義を変更できます。 プログラムはまったく同じように動作します。
さらに変更を加えた場合は、 SearchDirectoryArgs
を構造体に変更することもできます。
internal struct SearchDirectoryArgs
{
internal string CurrentSearchDirectory { get; }
internal int TotalDirs { get; }
internal int CompletedDirs { get; }
internal SearchDirectoryArgs(string dir, int totalDirs, int completedDirs) : this()
{
CurrentSearchDirectory = dir;
TotalDirs = totalDirs;
CompletedDirs = completedDirs;
}
}
追加の変更は、すべてのフィールドを初期化するコンストラクターに入る前に、パラメーターなしのコンストラクターを呼び出す方法です。 この追加がないと、C# の規則では、割り当てられる前にプロパティにアクセスされていることが報告されます。
FileFoundArgs
をクラス (参照型) から構造体 (値型) に変更しないでください。 キャンセルを処理するためのプロトコルでは、イベント引数を参照渡しする必要があります。 同じ変更を行った場合、ファイル検索クラスはイベント サブスクライバーによって行われた変更を観察できませんでした。 構造体の新しいコピーがサブスクライバーごとに使用され、そのコピーはファイル検索オブジェクトに表示されるコピーとは異なります。
次に、この変更を互換性がある形で実施する方法を考えてみましょう。 制約を削除しても、既存のコードには影響しません。 既存のイベント引数の型は、引き続き System.EventArgs
から派生します。 下位互換性は、 System.EventArgs
から引き続き派生する主な理由の 1 つです。 既存のイベント サブスクライバーは、クラシック パターンに従ったイベントのサブスクライバーです。
同様のロジックに従って、現在作成されたイベント引数の型には、既存のコードベースにサブスクライバーは含まれません。
System.EventArgs
から派生しない新しいイベントの種類は、これらのコードベースを中断しません。
非同期サブスクライバーを含むイベント
最後に、非同期コードを呼び出すイベント サブスクライバーを正しく記述する方法を学習する必要があります。 この課題については、 async と await に関する記事で説明されています。 非同期メソッドは void 戻り値の型を持つことができますが、これはお勧めしません。 イベント サブスクライバー コードが非同期メソッドを呼び出すときは、 async void
メソッドを作成する以外に選択肢はありません。 イベント ハンドラー署名には必要です。
この反対のガイダンスを調整する必要があります。 どういうわけか、安全な async void
メソッドを作成する必要があります。 実装する必要があるパターンの基本を次のコードに示します。
worker.StartWorking += async (sender, eventArgs) =>
{
try
{
await DoWorkAsync();
}
catch (Exception e)
{
//Some form of logging.
Console.WriteLine($"Async task failure: {e.ToString()}");
// Consider gracefully, and quickly exiting.
}
};
まず、ハンドラーが非同期ハンドラーとしてマークされていることに注意してください。 イベント ハンドラーのデリゲート型に割り当てられているため、戻り値の型が void です。 つまり、ハンドラーに示されるパターンに従う必要があります。非同期ハンドラーのコンテキストから例外をスローすることを許可しません。 タスクは返されないため、エラー状態を入力してエラーを報告できるタスクはありません。 メソッドは非同期であるため、例外をスローできません。 (呼び出し元のメソッドは async
であるため、実行を続行します)。実際のランタイム動作は、環境ごとに異なる方法で定義されます。 スレッドまたはスレッドを所有するプロセスを終了したり、プロセスを不確定な状態のままにしたりする可能性があります。 これらの潜在的な結果はすべて非常に望ましくありません。
非同期タスクの await
式は、独自の try ブロックでラップする必要があります。 エラーが発生したタスクが発生した場合は、エラーをログに記録できます。 アプリケーションが回復できないエラーの場合は、プログラムを迅速かつ適切に終了できます
この記事では、.NET イベント パターンの主要な更新について説明しました。 使用しているライブラリには、以前のバージョンの多くの例が表示される場合があります。 ただし、最新のパターンも理解しておく必要があります。 サンプルの完成したコードは 、Program.csで確認できます。
このシリーズの次の記事は、デザインで delegates
と events
を使用する方法を区別するのに役立ちます。 これらは同様の概念であり、その記事は、プログラムに最適な意思決定を行うのに役立ちます。
.NET