web-dev-qa-db-ja.com

イベントを行わない場合、なぜデリゲートを使用するのですか?

C#でイベントを実行する方法を学習しようとしています MSDNイベントチュートリアル によると、

イベントはデリゲートを使用して宣言されます。代理人チュートリアルをまだ学習していない場合は、続行する前に学習する必要があります

ですから、私はまずデリゲートを理解しようとしていますが、ひどく失敗しています。それらは何のため?何かを委任する場合、どのような問題を解決しようとしていますか?

他のメソッドにメソッドを渡すためのものだと思いますが、それの意味は何ですか?そもそも、あなたがしていることをすべて実行するメソッドを持たないのはなぜですか。

26
medivh

他の関数に関数を渡すことは、コードを一般化する非常に良い方法です、特に、真ん中のロジックのみが異なり、ほぼ重複するコードがたくさんある場合。

私のお気に入りの(簡単な)例は、ベンチマークするコードを含むデリゲートを受け入れるBenchmark関数を作成することです。 lambda syntaxおよびFunc/Actionオブジェクトを使用します。これは、明示的に作成されたデリゲートを最近よりもはるかに簡潔(かつ一般的)にするためです。

public void Benchmark(int times, Action func)
{
    var watch = new Stopwatch();
    double totalTime = 0.0;

    for (int i = 0; i < times; i++)
    {
        watch.Start();
        func(); // Execute our injected function
        watch.Stop();

        totalTime += watch.EllapsedTimeMilliseconds;
        watch.Reset();
    }

    double averageTime = totalTime / times;
    Console.WriteLine("{0}ms", averageTime);
}

これで、コードブロックをベンチマーク関数に(実行したい回数とともに)渡して、平均実行時間を取得できます。

// Benchmark the amount of time it takes to ToList a range of 100,000 items
Benchmark(5, () =>
{
    var xs = Enumerable.Range(0, 100000).ToList();
});

// You can also pass in any void Function() by its handler
Benchmark(5, SomeExpensiveFunction);

ベンチマークしたいコードをその関数の真ん中に挿入できなかった場合、使用したい場所の周りにロジックをコピーして貼り付けることになるでしょう。

Linqは関数の受け渡しを広範囲に使用するため、データのセットに対して非常に柔軟な操作のホスト全体を使用できます。 Where関数を例に考えてみましょう。比較関数が渡されたときに「true」を返すリストの要素をフィルタリングします。

var xs = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// This function compares each element, and returns true for those that are above 5
//   Result = { 6, 7, 8, 9, 10 }
var above5 = xs.Where((x) => x > 5);

// This function only returns true if an element is even
//   Result = { 2, 4, 6, 8, 10 }
var evens = xs.Where((x) => x % 2 == 0);

Where関数自体は非常に一般化されています。単にコレクションをループし、いくつかの述語関数に一致する値のみを含む新しいコレクションを生成します。それが探しているwhatを正確に伝えるコードを挿入するのはあなた次第です。

58
KChaloux

丸めを実行することを含む、複雑な数学的計算があるとします。丸めを偶数に丸める(別名銀行家の丸め)にしたい場合もあれば、丸めゼロから丸め(別名 '丸め[絶対値]')。

今、あなたは2つのメソッドを書くことができました

double DoTheCalculationWithRoundToEven(double input1, double input2)
{
    var intermediate1 = Math.Pow(input1, input2);
    // more calculations...
    var intermediate5 = Math.Round(intermediate4, MidpointRounding.ToEven);
    // more calculations...
    var intermediate10 = Math.Abs(intermediate9);

    return intermediate10;
}

double DoTheCalculationWithRoundAwayFromZero(double input1, double input2)
{
    var intermediate1 = Math.Pow(input1, input2);
    // more calculations...
    var intermediate5 = Math.Round(intermediate4, MidpointRounding.AwayFromZero);
    // more calculations...
    var intermediate10 = Math.Abs(intermediate9);

    return intermediate10;
}

2つのメソッドは同じexceptが中央の1行です。

これでうまくいきますが、コードが重複しており、将来のメンテナ(たとえば、あなた)がintermediate2の1つのバージョンをintermediate3に変更して、その他。

デリゲートタイプとパラメーターは、この重複を解決できます。

最初にデリゲートタイプを宣言します。

    public delegate double Rounder(double value);

これは、Rounderdoubleを受け入れ、doubleを返すメソッドであることを示しています。

次に、Rounderを受け入れ、それを使用して丸めるone計算メソッドを作成します。

double DoTheCalculation(double input1, double input2, Rounder rounder)
{
    var intermediate1 = Math.Pow(input1, input2);
    // more calculations...
    var intermediate5 = rounder(intermediate4);
    // more calculations...
    var intermediate10 = Math.Abs(intermediate9);

    return intermediate10;
}

最後に、計算を実行する場合は、適切なRounderを渡します。あなたはそれをすべてメソッドで綴ることができます:

double RounderAwayFromZero(double value)
{
    return Math.Round(value, MidpointRounding.AwayFromZero);
}
// same for ToEven

次に、次のいずれかを呼び出します。

var result = DoTheCalculation(input1, input2, RounderAwayFromZero);

または

var result = DoTheCalculation(input1, input2, RounderToEven);

しかし、C#ではラムダを使用してこれをすべてインラインで実行できます。

var result = DoTheCalculation(input1, input2, 
     value => Math.Round(value, MidpointRounding.AwayFromZero));

ここで、ラムダ式は、正しい署名を持つメソッドをRounderとして定義します。

11
AakashM

デリゲートは、ある種のアクションをパラメーター化する方法として機能します。デリゲートに渡される関数は、アクションがどのように実行されるかを気にせず、実行されるだけです。

たとえば、 IComparer インターフェイスを見てください。リストのタイプが「自然に」ソートされる方法に依存せずにリストをソートできます。ただし、インターフェースを見ると、1つのメソッドしか定義されていません。単一の関数のみを実装するクラス全体を定義するのはかなり無駄です。

デリゲートはこの問題を解決します1。クラスを定義する必要はなく、メソッドを定義するだけです。ボイラープレートコードを削除して、やりたいことに集中できます。ラムダはこれを一歩前進させ、関数定義をクラスの他の場所で定義する代わりにインライン化できるようにします。

1 Sort() を見ると、デリゲートを使用するオーバーロードバージョンが存在します。

1
unholysampler

デリゲートはインターフェースに似ています。あなたは単にデリゲートを使用するクラスを消費している何でも満たすことができるコントラクトを定義しています。インターフェイスとデリゲートの大きな違いは、インターフェイスがクラス全体のコントラクトを定義するのに対し、デリゲートは単一のメソッドのコントラクトを定義することです。

デリゲートを使用する最も基本的な実例としては、「中間の穴」パターンがあります。このパターンはあまり必要ありませんが、必要なときに非常に役立ちます。継承および抽象メソッドを使用してこれと同じパターンを実現できますが、デリゲートを使用すると、合成を使用してこれを実現できます。

public delegate void MyDelegate(string param1, string param2);

public void SomeMethod(string param1, string param2) {
    Console.WriteLine(param1);
    //do some more work
}

public void DoSomething(MyDelegate somemethod) {
    //do something here
    somemethod(param1, param2);
    //do something else here
}
1
Mike Cellini

メソッドを別のメソッドに渡す目的は、これら2つのメソッドの目的を分離することです。これは、オブジェクト指向設計の一般に認められた原則、特に単一責任の原則に準拠しています。

例として、特定の形式(パイプ区切りのテキストなど)でファイルを読み取るメソッドと、特定の方法でそのファイルの情報を要約して情報を抽出するメソッドがあるとします。これらを1つの方法にまとめる必要があると言います。プログラムがCSVファイルからの読み取りを開始する必要がある場合はどうなるでしょうか。含まれているデータの別のサブセットが必要な場合はどうなりますか?メソッドを変更する必要があります。ただし、プログラムはパイプで区切られたテキストを処理し、古いデータセットをプルする必要があります。これで、2つの異なるファイルタイプから読み取り、2つの異なるデータセットを生成する1つのメソッドができました。この方法は急速に保守不可能になりつつあります。

代わりに、パイプ区切りのテキストを読み取るメソッドと、CSVを読み取るメソッドを作成できます。 1つのデータセットを抽出するメソッドと、2番目のデータセットを抽出するメソッドを作成できます。次に、ファイル読み取りメソッドをデータ抽出メソッドのデリゲートとして、またはその逆に指定できます。または、両方を順番に実行する3番目のクラスのメソッドに渡し、ファイルのデータをデータセット抽出メソッドに渡すこともできます。

これにより、作成するメソッドの数が増えますが、各メソッドには1つの目的しかありません。メソッドが適切に機能するために必要な変更は、他の目的や実行に影響を与えません。結果のコードは理解しやすく、保守も容易であるため、正確さを保証するために必要なテストは少なくなります。

1
KeithS

デリゲートをパラメーターとして使用する操作を持つフレームワーククラスライブラリの種類はたくさんあります。

Task parallel Library 、LINQ、またはEntity Frameworkを使用する場合、デリゲートを引数として渡します。

使用したい多くのデリゲートシグネチャは既に定義されており( ActionFuncPredicate ))ラムダ式を使用して構文が単純化されています。

0
brian

おそらく、デリゲートとイベントを理解する最も簡単な方法は、GUIプログラミングを調べることです。非常に一般的なケースを考えてみましょう。フォーム上にボタンがあり、ユーザーがボタンをクリックすると、特定のコードを実行したいとします。

ボタンのクラスを書いた人は、あなたがそれをどうしたいのか全く分かりません。したがって、彼は「このボタンが何をすべきか」というコードをクラスに組み込むことができません。そして、たとえ彼ができたとしても、それは「GUIボタン」ではなくなり、「Medivhのthis-specific-use-caseボタン」に変わり、一般的なケースではあまり役に立たなくなるので、彼は本当にそうすべきではありません。

したがって、代わりに、ボタンクラスを作成した人は、オブジェクトの機能へのフックのようなイベントを配置しました。イベントは基本的に、他の誰かがコードを挿入できる場所で実行されるコードの一部です。そのため、誰かがボタンをクリックすると、OnClickイベントが発生し、そのイベントに何か(新しいフォームを開くメソッド(デリゲート)など)をアタッチした場合、そのデリゲートはこの場所で実行されますポイント。

メソッドを、呼び出すことができるプロシージャまたは関数だけでなく、他のコードに渡すことができるオブジェクトとして考えると、あらゆる種類の新しい可能性が開かれます。ここにいる他の人々の一部は、すでにLINQとタスクパラレルライブラリについて言及しています。これらには、何かの基本的な考え方を定義するアルゴリズムが含まれていて、デリゲートを渡して詳細を入力できます。

0
Mason Wheeler