web-dev-qa-db-ja.com

デリゲート、なぜ?

重複の可能性:
C#でデリゲートを使用するのはいつですか?
デリゲートの目的

デリゲートの使用に関して多くの質問を見てきました。私はまだどこがはっきりしていないので、メソッドを直接呼び出す代わりにデリゲートを使用する理由がわかりません。

私はこのフレーズを何度も聞いています。「デリゲートオブジェクトは、コンパイル時にどのメソッドが呼び出されるかを知らなくても、参照されるメソッドを呼び出すことができるコードに渡すことができます。」

その文がどのように正しいのか理解できません。

以下の例を書きました。同じパラメータを持つ3つのメソッドがあるとします。

   public int add(int x, int y)
    {
        int total;
        return total = x + y;
    }
    public int multiply(int x, int y)
    {
        int total;
        return total = x * y;
    }
    public int subtract(int x, int y)
    {
        int total;
        return total = x - y;
    }

次に、デリゲートを宣言します。

public delegate int Operations(int x, int y);

これでさらに一歩進んで、このデリゲート(または直接デリゲート)を使用するハンドラーを宣言します。

代理人に電話する:

MyClass f = new MyClass();

Operations p = new Operations(f.multiply);
p.Invoke(5, 5);

またはハンドラーで呼び出す

f.OperationsHandler = f.multiply;
//just displaying result to text as an example
textBoxDelegate.Text = f.OperationsHandler.Invoke(5, 5).ToString();

これらの両方のケースで、「乗算」メソッドが指定されているのがわかります。なぜ人々は「実行時に機能を変更する」というフレーズや上記のものを使用するのですか?

デリゲートを宣言するたびに、ポイントするメソッドが必要な場合、デリゲートが使用されるのはなぜですか?指すメソッドが必要な場合は、そのメソッドを直接呼び出すだけではどうですか。デリゲートを使用するには、関数を直接使用するだけではなく、より多くのコードを記述しなければならないようです。

誰かが私に実際の状況を教えてもらえますか?私は完全に混乱しています。

55
Mage

実行時に機能を変更することは、デリゲートが達成することではありません。

基本的に、デリゲートはタイピングの負担を軽減します。

例えば:

class Person
{
    public string Name { get; }
    public int Age { get; }
    public double Height { get; }
    public double Weight { get; }
}

IEnumerable<Person> people = GetPeople();

var orderedByName = people.OrderBy(p => p.Name);
var orderedByAge = people.OrderBy(p => p.Age);
var orderedByHeight = people.OrderBy(p => p.Height);
var orderedByWeight = people.OrderBy(p => p.Weight);

上記のコードでは、p => p.Namep => p.AgeなどはすべてFunc<Person, T>デリゲートに評価されるラムダ式です(ここで、Tstringintdouble、およびdouble)。

ここで、デリゲートなしで上記のをどのように達成できたかを考えてみましょう。 OrderByメソッドがデリゲートパラメーターを取る代わりに、汎用性を放棄してこれらのメソッドを定義する必要があります。

public static IEnumerable<Person> OrderByName(this IEnumerable<Person> people);
public static IEnumerable<Person> OrderByAge(this IEnumerable<Person> people);
public static IEnumerable<Person> OrderByHeight(this IEnumerable<Person> people);
public static IEnumerable<Person> OrderByWeight(this IEnumerable<Person> people);

これは完全に吸うです。つまり、最初に、コードはPerson型のコレクションにのみ適用されるため、再利用性が無限に低下しました。さらに、まったく同じコードを4回コピーして貼り付ける必要があり、各コピーで1行または2行のみを変更します(Personの関連プロパティが参照されている場合-それ以外はすべて同じに見えます)!これはすぐに維持不可能な混乱になります。

したがって、デリゲートを使用すると、コードの再利用可能性をおよび維持しやすくすることができます。

28
Dan Tao
18
PatrickSteele

デリゲートは、特にlinqとクロージャーの導入後に非常に役立ちます。

良い例は、標準のlinqメソッドの1つである「Where」関数です。 'Where'はリストとフィルターを受け取り、フィルターに一致するアイテムのリストを返します。 (filter引数は、Tを取りブール値を返すデリゲートです。)

デリゲートを使用してフィルターを指定するため、Where関数は非常に柔軟です。たとえば、奇数と素数をフィルターするために別のWhere関数は必要ありません。呼び出し構文も非常に簡潔です。これは、インターフェースまたは抽象クラスを使用した場合には当てはまりません。

より具体的には、デリゲートを取得すると、次のように記述できます。

var result = list.Where(x => x != null);
...

これの代わりに:

var result = new List<T>();
foreach (var e in list)
    if (e != null)
        result.add(e)
...
11
Craig Gidney

デリゲートを宣言するたびに、ポイントするメソッドが必要な場合、デリゲートが使用されるのはなぜですか?指すメソッドが必要な場合は、そのメソッドを直接呼び出すだけではどうですか。

インターフェイスと同様に、デリゲートを使用すると、コードを分離して一般化できます。通常、デリゲートを使用するのは、実行するメソッドが事前にわからない場合-特定のシグネチャに一致するsomethingを実行することだけがわかっている場合です。

たとえば、一定の間隔でいくつかのメソッドを実行するタイマークラスを考えます。

public delegate void SimpleAction();

public class Timer {
    public Timer(int secondsBetweenActions, SimpleAction simpleAction) {}
}

そのタイマーに何でも接続できるので、それを他のプロジェクトやアプリケーションで使用することができます。その使用方法を予測したり、その使用を、考えている少数のシナリオに限定したりする必要はありません

9
Jeff Sternal

例を挙げましょう。クラスがeventを公開する場合、実行時にいくつかのデリゲートを割り当てることができます。デリゲートは、何かが起こったことを知らせるために呼び出されます。クラスを作成したとき、どのデリゲートが実行されるのかわかりませんでした。代わりに、これはクラスを使用する人が決定します。

7
Steven Sudit

delegateが必要になる1つの例は、UIスレッドのコントロールを変更する必要があり、別のスレッドで操作している場合です。例えば、

public delegate void UpdateTextBox(string data);

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    ...
    Invoke(new UpdateTextBox(textBoxData), data);
    ...

}

private void textBoxData(string data)
{
            textBox1.Text += data;
}
2
user195488

あなたの例では、デリゲートを変数に割り当てたら、他の変数と同じように渡すことができます。デリゲートをパラメーターとして受け入れるメソッドを作成でき、メソッドが実際に宣言されている場所を知る必要なくデリゲートを呼び出すことができます。

_private int DoSomeOperation( Operations operation )
{
    return operation.Invoke(5,5);
}

...

MyClass f = new MyClass();
Operations p = new Operations(f.multiply);
int result = DoSomeOperation( p );
_

デリゲートは、intと同じ方法で渡すことができるものにメソッドを作成します。変数はあなたに何も特別なことを与えないと言うことができます

_int i = 5;
Console.Write( i + 10 );
_

値5が指定されているのがわかるので、Console.Write( 5 + 10 )とだけ言うこともできます。その場合は真実ですが、それは言うことができるための利点を逃します

_DateTime nextWeek = DateTime.Now.AddDays(7);
_

特定のDateTime.AddSevenDays()メソッド、AddSixDaysメソッドなどを定義する必要はありません。

2
stevemegson

具体的な例を示すと、デリゲートの特に最近の使用は、_System.Net.Mail.SmtpClient_のSendAsync()でした。大量の電子メールを送信するアプリケーションがあり、Exchangeサーバーがメッセージを受け入れるのを待っている間に顕著なパフォーマンスヒットがありました。ただし、そのサーバーとの対話の結果をログに記録する必要がありました。

そのため、そのロギングを処理するデリゲートメソッドを作成し、各メールを送信するときにSendAsync()に渡しました(以前はSend()を使用していただけです)。これにより、デリゲートにコールバックして結果をログに記録でき、アプリケーションスレッドは対話が完了するのを待たずに続行します。

同じことが外部のIOでも当てはまります。対話が完了するのを待たずにアプリケーションを続行します。Webサービスのプロキシクラスなどがこれを利用します。

2
David
1
a1ex07

Operationsの例を使用して、いくつかのボタンがある電卓を想像してみてください。このようにボタンのクラスを作成できます

class CalcButton extends Button {
   Operations myOp;
   public CalcButton(Operations op) {
      this.myOp=op; 
   }
   public void OnClick(Event e) {
      setA( this.myOp(getA(), getB()) ); // perform the operation
   }
}

ボタンを作成するときに、それぞれを異なる操作で作成できます

CalcButton addButton = new CalcButton(new Operations(f.multiply));

これはいくつかの理由でより良いです。ボタンのコードを複製するのではなく、ボタンは一般的なものです。たとえば、異なるパネルやメニューなどで、すべてが同じ操作を持つ複数のボタンを持つことができます。ボタンに関連付けられた操作をその場で変更できます。

1
Sanjay Manohar

デリゲートは、Accessの問題を解決するために使用されます。オブジェクトバーのfrobメソッドを呼び出す必要があるが、frobメソッドにアクセスしないオブジェクトfooが必要な場合。

オブジェクトgooはfooとbarの両方にアクセスできるため、デリゲートを使用して結び付けることができます。通常、barとgooは同じオブジェクトであることがよくあります。

たとえば、Buttonクラスには通常、Button_clickメソッドを定義するクラスへのアクセス権がありません。

これで、イベントだけでなく、さまざまな用途に使用できるようになりました。非同期パターンとLinqは2つの例です。

1
Conrad Frix

デリゲートを使用して、サブスクリプションとeventHandlerを実装できます。また、(ひどい方法で)それらを使用して循環依存関係を回避することもできます。

または、計算エンジンがあり、多くの可能な計算がある場合は、エンジンのさまざまな関数呼び出しの代わりにパラメーターデリゲートを使用できます。

答えの多くはインラインデリゲートに関係しているようです。私の意見では、これを "クラシックデリゲート"と呼ぶよりも簡単に理解できます。

以下は、デリゲートが消費クラスに動作を変更または拡張できるようにする方法の例です(「フック」を効果的に追加して、コンシューマーが重要なアクションの前または後に物事を実行したり、その動作を完全に防止したりできます)。意思決定ロジックはすべてStringSaverクラスの外部から提供されることに注意してください。ここで、このクラスの4つの異なるコンシューマーが存在する可能性があることを考慮してください。それぞれが独自のVerificationおよびNotificationロジックを実装するか、必要に応じてどれも実装できません。

internal class StringSaver
{
    public void Save()
    {
        if(BeforeSave != null)
        {
            var shouldProceed = BeforeSave(thingsToSave);
            if(!shouldProceed) return;
        }
        BeforeSave(thingsToSave);

        // do the save

        if (AfterSave != null) AfterSave();
    }

    IList<string> thingsToSave;
    public void Add(string thing) { thingsToSave.Add(thing); }

    public Verification BeforeSave;
    public Notification AfterSave;
}

public delegate bool Verification(IEnumerable<string> thingsBeingSaved);
public delegate void Notification();

public class SomeUtility
{
    public void SaveSomeStrings(params string[] strings)
    {
        var saver = new StringSaver
            {
                BeforeSave = ValidateStrings, 
                AfterSave = ReportSuccess
            };

        foreach (var s in strings) saver.Add(s);

        saver.Save();
    }

    bool ValidateStrings(IEnumerable<string> strings)
    {
        return !strings.Any(s => s.Contains("RESTRICTED"));
    }

    void ReportSuccess()
    {
        Console.WriteLine("Saved successfully");
    }
}

ポイントは、デリゲートが指すメソッドがデリゲートメンバーを公開するクラスに必ずしも含まれていないことだと思います。

1
Jay