イベント - C#のデリゲートとイベント(2)で

イベントについて

そして、ここのように、このまだ半要約翻訳半分の途中で。

イベントでもあります遅延バインディングメカニズム、および委員会の設立のサポートに基づいています。イベントは、放送オブジェクト(システムへの関心のコンポーネントすべてのイベント)何が起こっているかの方法です。その他のコンポーネントは、このイベントを購読することができ、イベントが発生したときに通知されます。

例えば、多くのシステムは、例えば、マウスを動かすと、イベントにユーザーの行動を報告するためのグラフィカルなモデルを持っているボタンなどを押してください。

するイベントをサブスクライブする二つのオブジェクト(イベント・ソースおよびイベント加入者)との間の結合を確立します

イベントソース、イベントサブスクライバ、イベントソースコンポーネント:私たちは、第1項の数を決定しましょう。以下のように彼らの関係は次のとおりです。

イベントソースが使用することですeventトリガイベントに使用される定義されたイベントのキーワードを、。

イベントソース・コンポーネントは、イベントソースは、イベントをトリガー満たすためにイベントを発生させた条件は、イベント・ソースを呼び出すことができたときに、通常クラス、場所を定義しています。

イベントサブスクライバメソッドは、特定の実行ロジックを含む、トリガイベント後に呼び出されます。

イベント設計目標

  • イベント・ソースおよびイベントサブスクライバとの間の結合の小さな程度。
  • イベントをサブスクライブ、単に退会。
  • イベントソースは、加入することができ、複数のイベントをサブスクライブします。

イベント言語サポート

定義されたイベントは、イベントをサブスクライブ、退会イベントの構文は、手数料の構文を拡張したものです。

使用してイベントを定義しeventたキーワードを:

public event EventHandler<FileListArgs> Progress;

イベントタイプは、EventHandler<FileListArgs>デリゲート型でなければなりません。

定義されたイベントは、規則の多くに従う値を返さないようなイベントのデリゲート必要性のタイプとして、イベント名は、現在時制切迫したレポートのどのような使用、何が起こったかのタイプ、過去の報告書の動詞または動詞句、使用する必要があります。

イベントが発生すると、デリゲートの呼び出し構文を使用すると、イベントハンドラを呼び出します

Progress?.Invoke(this, new FileListArgs(file));

?. オペレータは簡単ときにイベントがない加入者イベントが発生していないことを確認することができます。

使用することにより+=、オペレータのサブスクリプションのイベントを:

//1个委托
EventHandler<FileListArgs> onProgress = (sender, eventArgs) =>
Console.WriteLine(eventArgs.FoundFile);
//事件的注册
lister.Progress += onProgress;

あなたは委託、イベントが登録されているオブジェクトを見ることができます。ハンドラはさらに、通常の接頭辞で、供何か体を生じるOn上記のように、。

使用-=退会する演算子:

lister.Progress -= onProgress;

私たちは、上記参照購読および購読中止する地元のデリゲートを宣言することができます。あなたが購読するラムダ式を使用する場合は、加入者を削除することはできません。

標準の.NETイベントモデル

.NETイベントは、通常、いくつかの既知のパターンに従ってください。

イベントデリゲート署名

イベントのための標準デリゲートシグネチャは次のとおりです。

//委托的返回值是void,参数是object sender, EventArgs args
void OnEventRaised(object sender, EventArgs args);

戻り値の型だけでは、戻り値があいまいになるので、単一メソッドの戻り値は、複数のイベントの加入者に拡張することができない、無効です。

イベントソースコンポーネントとイベントパラメータ:パラメータリストは、2つのパラメータが含まれています。コンパイル時型イベント・ソース・コンポーネントSystem.Objectイベントパラメータが通常さに由来しSystem.EventArgsますが、特別な値を使用することができ、EventArgs.Emptyその他の情報が含まれていないイベントを表すために。

次は、私たちは、クラスの作成FileSearcher、トリガー・イベントの両方の要件を満たすように各ファイルのクラスの要件を満たすために、ディレクトリ内のすべてのファイルを一覧表示することができます関数のクラスを。

まず、目的のファイルを見つけるために、次のパラメータのためのイベントを作成しますFileFoundArgs

public class FileFoundArgs : EventArgs
{
    public string FoundFile { get; }
    public FileFoundArgs(string fileName)
    {
        FoundFile = fileName;
    }
}

これは、データが含まれているもののわずかな種類を見ることができますが、我々は参照によって渡されたパラメータは、すべてのイベントの加入者は、パラメータ更新のデータを参照することを意味し、クラス、に設定します。上記の例では、唯一のビューには、パラメータを変更することはできません。

次の必要性ではFileSearcherイベント文を作成するために、我々は、デリゲート型がすでに存在して使用しEventHandler<T>、直接宣言し、event変数を。一致がファイルで発見されたときに最後に、私たちはイベントを発生させます。

public class FileSearcher
{
    //系统定义的委托,把它用于声明一个事件
    public event EventHandler<FileFoundArgs> FileFound;
    public void Search(string directory, string searchPattern)
    {
        //如果文件符合要求
        foreach (var file in Directory.EnumerateFiles(directory,searchPattern))
        {
            //引发事件
            FileFound?.Invoke(this, new FileFoundArgs(file));
        }
    }
}

同様のフィールドイベントを定義し、引き上げ

セクションに示すように、イベントクラスを追加する最も簡単な方法は、イベントのpublicフィールドとして宣言されています。

public event EventHandler<FileFoundArgs> FileFound;

これは、パブリックドメインの声明であるように思わ良いオブジェクト指向設計ではありませんが、コンパイラはラッパーを生成し、そして唯一のイベントオブジェクトにアクセスするための安全な方法インチ イベントにのみ使用可能なアクションをフィールドに似て/削除ハンドラを追加することです。

//用lambda的方式定义一个委托
EventHandler<FileFoundArgs> onFileFound = (sender, eventArgs) => {
    Console.WriteLine(eventArgs.FoundFile);
    filesFound++;
};
//fileLister是FileSearcher的一个实例
fileLister.FileFound += onFileFound;
fileLister.FileFound -= onFileFound;

ありますがここでは、onFileFoundローカル変数は、それはラムダによって定義されているため、削除が正常に動作しません。

クラスの外部コードはイベントや操作を行った他のイベントを発生させることができないことを言及する価値があります。

イベントの加入者からの戻り値

キャンセル:私たちは、新しい機能を検討します。

ここのシーンはセットトリガーでFileFound、イベント・ソース・コンポーネントは、次のアクションを停止する必要があり、ファイルがファイルの要件を満たすために最後のものであるかどうかを確認、イベント。

あなたは他の方法で情報を転送する必要があるので、イベントハンドラ(イベントサブスクライバ)ので、値を返しません。標準モードでは、イベントの使用EventArgs(イベント加入者が情報を渡すために、これらのフィールドを変更することができる)いくつかのフィールドを含むオブジェクトを。

「キャンセル」の意味に基づいて、2つの異なるモードで使用することができます(どちらのモードでも必要とEventArgsの1 boolフィールド)。

モード1は、操作をキャンセルする任意のイベントサブスクライバを可能にします。このモードでは、boolフィールドが初期化されfalse、任意のイベントの加入者は、それを変更することができtrue、すべての加入者がイベント通知イベントが発生した受信したときに、FileSearcherこの値をチェックし、さらなる行動を取るだろう。

モデルIIすべてのイベントの加入者は、さらにアクションをキャンセルしたい場合FileSearcherにのみ、次のアクションをキャンセルします。このモードでは、boolフィールドが初期化されtrue、任意のイベントの加入者は、それを変更することができますfalse(次のアクションが継続することを示しています)。すべての加入者がイベント通知イベントが発生した受信すると、FileSearcher我々はこの値をチェックし、さらなる行動を取るだろう。このモデルは、余分なステップがあります:開始イベントコンポーネントがイベントを受け取るすべての加入者があるかどうかを知る必要があります。何の加入者が存在しない場合は、フィールドが誤って表示されます。

するパターン、あなたの最初の必要性の例で見てみましょうEventArgsで追加boolのフィールドCancelRequested

public class FileFoundArgs : EventArgs
{
    //文件名
    public string FoundFile { get; }
    //是否取消动作
    public bool CancelRequested { get; set;}
    public FileFoundArgs(string fileName)
    {
        FoundFile = fileName;
    }
}

このフィールドは自動的に初期化されてfalse次のアクションをキャンセルする任意の加入者の要求があるかどうかを判断するためにイベントを発生させ、その後フラグをチェックするために、コンポーネントのニーズを:

public void List(string directory, string searchPattern)
{
    foreach (var file in Directory.EnumerateFiles(directory,searchPattern))
    {
        //创建参数
        var args = new FileFoundArgs(file);
        //触发事件
        FileFound?.Invoke(this, args);
        //检查结果
        if (args.CancelRequested)
            break;
    }
}

このモデルの利点は、それが今キャンセルこの新しい検査項目に関するものではありません、新しい検査項目の後に増加するが、加入者が前にキャンセルする必要がないという重大な変化が発生しないということです。ユーザーは、フィールドをサポートするために、新規加入者をチェックしたい場合を除き。このような非常に疎結合。

我々は、イベントに次のアクションをキャンセルする最初の実行ソースコンポーネントでそれを発見した後、加入者を更新します。

//订阅者改变参数
EventHandler<FileFoundArgs> onFileFound = (sender, eventArgs) =>
{
    Console.WriteLine(eventArgs.FoundFile);
    eventArgs.CancelRequested = true;
};

もう一つの例

別の従来の方法のイベントを示して1つの以上の例で見てみましょう。例では、我々はすべてのサブディレクトリをトラバース。長い操作することができ、多数のサブディレクトリを含むディレクトリでは、新しいディレクトリの検索を開始するたびにイベントが発生する、のイベントを追加してみましょう。これは、加入者がユーザーに進捗状況、レポートの進行状況を追跡することができますことができます。今回は内部イベントとしてこのイベントを参照してください。この手段をEventArgs設定することも可能privateに。

まず、新規作成EventArgs新しいレポートカタログと進歩のための派生クラスを。

//internal关键字表示只能在程序集中使用,程序集外部无法访问
internal class SearchDirectoryArgs : EventArgs
{
    internal string CurrentSearchDirectory { get; }
    internal int TotalDirs { get; }
    internal int CompletedDirs { get; }
    internal SearchDirectoryArgs(string dir, int totalDirs, int completedDirs)
    {
        CurrentSearchDirectory = dir;
        TotalDirs = totalDirs;
        CompletedDirs = completedDirs;
    }
}

、イベントを定義し、フィールドの構文を使用することに加えて、この異なる構文を試してみてください、あなたはまた、追加および削除ハンドラ明示的に作成されたプロパティを使用することができます。これらのハンドラは、余分なコードを必要としませんが、それはそれらを作成する方法を示しています。

//事件定义
internal event EventHandler<SearchDirectoryArgs> DirectoryChanged
{
    //事件属性
    add { directoryChanged += value; }
    remove { directoryChanged -= value; }
}
//事件声明
private event EventHandler<SearchDirectoryArgs> directoryChanged;

上記のこのコードは、以前に暗黙的に生成されたコードを見フィールドイベント用に定義されたコンパイラで、通常の状況下では、それは前の属性構文を使用してイベントを作成することであることは非常に似ています。

見てみましょうの一緒のSearchすべてのサブディレクトリをループする方法、および2つのイベントをトリガ。

public void Search(string directory, string searchPattern, bool searchSubDirs)
{
    //如果需要搜索子目录
    if (searchSubDirs)
    {
        //获取所有子目录
        var allDirectories = Directory.GetDirectories(directory, "*.*", SearchOption.AllDirectories);
        //当前已完成搜索的目录
        var completedDirs = 0;
        //目录总数
        var totalDirs = allDirectories.Length + 1;
        foreach (var dir in allDirectories)
        {
            //每搜索到1个子目录,触发事件
            directoryChanged?.Invoke(this, new SearchDirectoryArgs(dir,totalDirs,completedDirs++));
            // 递归搜索子目录
            SearchDirectory(dir, searchPattern);
        }
        // 当前目录也触发事件
        directoryChanged?.Invoke(this,new SearchDirectoryArgs(directory,totalDirs,completedDirs++));
        //递归对当前目录处理
        SearchDirectory(directory, searchPattern);
    }
    else//如果不需要搜索子目录
    {
        SearchDirectory(directory, searchPattern);
    }
}
private void SearchDirectory(string directory, string searchPattern)
{
    foreach (var file in Directory.EnumerateFiles(directory,searchPattern))
    {
        var args = new FileFoundArgs(file);
        //对于符合要求每个文件,都引发事件
        FileFound?.Invoke(this, args);
        //如果订阅者需要取消,则不进行继续搜索
        if (args.CancelRequested)
            break;
    }
}  

場合はdirectoryChanged何も加入者が存在しない、使用の?.Invoke()フレーズは、それが正常に動作していることを確認することができます。

ここでは論理の加入者は、イベントが発生したときに、印刷して、完全なディレクトリは、コンソールの進捗状況を確認することです。

lister.DirectoryChanged += (sender, eventArgs) =>
{
    Console.Write($"Entering '{eventArgs.CurrentSearchDirectory}'.");
    Console.WriteLine($" {eventArgs.CompletedDirs} of {eventArgs.TotalDirs} completed...");
};

イベントがあるC#ことを学習することで、あなたはすぐにいつも書くことができ、重要なモードC#.NETコード。これらのモードは、次の最新バージョンに見られる.NET変化いくつか。

更新ネットコア・イベント・モード

.NET Coreよりリラックスモード、EventHandler<TEventArgs>定義がされなくなりましたTEventArgsからであることをSystem.EventArgsクラスを派生制約。

柔軟性と下位互換性を高めるために、System.EventArgsクラスメソッドを導入しMemberwoseClone()、作成されたオブジェクトの浅いクローンを、本方法は、そうからの、反射性でなければならない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;
    }
}

あなたはすべきでないFileFoundArgs値型の参照型から変更、またはイベント・ソース・コンポーネントは、すべての加入者を変更観察することはできません。

この変更下位互換性、ドロップ制約が既存のコードには影響しませんどのように見てみましょうが、どのコースから既存のイベントパラメータの種類はできSystem.EventArgs派生。下位互換性は、彼らがから続けることができるということであるSystem.EventArgs派生主な理由の一つ。だから今作成した新しいタイプは、既存のコードベースでの任意の加入者を持っていません。

イベントの非同期加入者

正しくイベント・サブスクライバ・コール非同期コードを作成する方法:あなたが学ぶ必要がある最後のモード。これが後になりますasync and await紹介記事。

エージェントとイベントを区別

以下のためのベースの設計とイベントベースの設計の間で決定する委員会では、.Net Core初心者のプラットフォーム、多くの場合、通常は激しいです。二つの言語の機能は非常に似ているので。イベントでも、言語を構築するための委員会があります。彼らは、共通の以下にあります。

  • メカニズムを遅延バインディングを提供(コンポーネントがノウハウを実行したときにメソッドを呼び出すことによってのみ通信します)。
  • 単一および複数の加入者をサポートしています。
  • ハンドラを追加および削除に似た構文を持っています。
  • イベントを上げて、同じ構文を使用してコールを委任。
  • サポートInvoke().?一緒に使用します。

イベントに耳を傾けるはオプションです

利用機能にどの言語を決定する際、考慮すべき最も重要な因子である加入者が添付されなければならないかどうかあなたのコードは、コードの呼び出しに登録する必要があります場合は、すべての加入者を呼び出すことなく、そのすべての作業を完了することができれば、あなたは、デリゲートベースの設計、コードを使用する必要があり、あなたは、イベントベースの設計を使用する必要があります。

前の例を考慮して組み合わせます。使用して、List.Sort()比較関数は、要素をソート提供するために、正しくなければなりません。LINQクエリは、返される要素を決定するために、委員会で提供されなければなりません。どちらが主な用途に基づいて設計されなければなりません。(これが試みられているパラメータ法としてデリゲートに相当し、イベントは、パラメータ法とはなりません)

配慮の上Benpian結合の例。Progressレポートタスクの進捗状況についてのイベントは、関係なく、任意の加入者かどうか、タスクが継続されます。FileSearcherイベントではなくても追加のイベントの加入者ならば、それはまだを検索し、あなたが探しているすべてのファイルを検索し、別の例です。どちらも、イベントベースの設計を使用する必要があります。

###の値への復帰は、委員会に必要

私たちが使用することができますが、イベントのデリゲート型は、無効な戻り値の型を持つEventArgsパラメータを渡すが、自然なので、メソッドから直接結果を返すことをお勧めします。

イベントの加入者は、戻り値を持っているときに、我々はベースのデリゲートを選びました。

イベントの加入者は、通常、長いライフサイクルを持っています

イベント・ソース・コンポーネントは、時間の非常に長い期間でイベントを発生させますとき、これが弱い正当化ですが、あなたはそれを見つけることができ、イベントベースのデザインは、より自然になります。あなたは、イベントソースは、プログラムのライフサイクルを通じてイベントをトリガすることができ、イベントサブスクリプション、多くのシステム上UXコントロールの例を見ることができます。

これは、多くのデリゲートコントラストの設計パラメータに基づいており、元本に基づく設計で、方法は、デリゲートとして使用され、もはや委任した後に使用した方法に戻ります。

厳選

上記の考察は、彼らはあなたの選択を導くことができますよう反し、必須ではありません。彼らは、遅延バインディングの方式に対処することは非常に良いです。最良のデザインを伝える情報を選択します。

おすすめ

転載: www.cnblogs.com/czjk/p/12112439.html