web-dev-qa-db-ja.com

いつ、なぜデゲゲートを使用するのですか?

私はC#が比較的新しいです、そして デリゲートを適切に使用するとき と思っています。それらはイベント宣言で広く使われていますが、私のコードでいつ使うべきですか?そして なぜそれらは便利なのですか? なぜ他のものを使わないのですか?

代理人を使わなければならず、他に選択肢がないときは と思っています。

お手伝いありがとう!

編集:私は デリゲートの必要な使用法を見つけたと思います ここ

327
iChaib

他の言葉を載せようとしているだけで、私はすでに言ったことすべてに同意します。

デリゲートは、/ someメソッドのプレースホルダーと見なすことができます。

デリゲートを定義することによって、あなたはあなたのクラスのユーザに "このシグネチャにマッチする任意のメソッドを自由に割り当ててください、そして私のデリゲートが呼ばれるたびに呼び出されます"と言います。

典型的な使い方はもちろんイベントです。すべてのOnEventX デリゲートユーザーが定義したメソッドへ。

デリゲートはあなたのオブジェクトのuserに彼らの振る舞いをカスタマイズする能力を提供するのに役立ちます。ほとんどの場合、あなたは同じ目的を達成するために他の方法を使うことができます、そして私はあなたがデリゲートを作るためにあなたが決して強制になることができるとは思わない。状況によっては、物事を成し遂げることが最も簡単な方法です。

256
Benoit Vidis

デリゲートはメソッドへの参照です。オブジェクトは、パラメータとしてメソッド、コンストラクタなどに簡単に送信できますが、メソッドはもう少し複雑です。しかし、たまには、あるメソッドを別のメソッドのパラメータとして送信する必要があると感じるかもしれません。その場合は、デリゲートが必要になります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MyLibrary;

namespace DelegateApp {

  /// <summary>
  /// A class to define a person
  /// </summary>
  public class Person {
    public string Name { get; set; }
    public int Age { get; set; }
  }

  class Program {
    //Our delegate
    public delegate bool FilterDelegate(Person p);

    static void Main(string[] args) {

      //Create 4 Person objects
      Person p1 = new Person() { Name = "John", Age = 41 };
      Person p2 = new Person() { Name = "Jane", Age = 69 };
      Person p3 = new Person() { Name = "Jake", Age = 12 };
      Person p4 = new Person() { Name = "Jessie", Age = 25 };

      //Create a list of Person objects and fill it
      List<Person> people = new List<Person>() { p1, p2, p3, p4 };

      //Invoke DisplayPeople using appropriate delegate
      DisplayPeople("Children:", people, IsChild);
      DisplayPeople("Adults:", people, IsAdult);
      DisplayPeople("Seniors:", people, IsSenior);

      Console.Read();
    }

    /// <summary>
    /// A method to filter out the people you need
    /// </summary>
    /// <param name="people">A list of people</param>
    /// <param name="filter">A filter</param>
    /// <returns>A filtered list</returns>
    static void DisplayPeople(string title, List<Person> people, FilterDelegate filter) {
      Console.WriteLine(title);

      foreach (Person p in people) {
        if (filter(p)) {
          Console.WriteLine("{0}, {1} years old", p.Name, p.Age);
        }
      }

      Console.Write("\n\n");
    }

    //==========FILTERS===================
    static bool IsChild(Person p) {
      return p.Age < 18;
    }

    static bool IsAdult(Person p) {
      return p.Age >= 18;
    }

    static bool IsSenior(Person p) {
      return p.Age >= 65;
    }
  }
}
271
dhaval8087

ある区間で実数値の関数 f x )を積分する手続きを書きたいとしましょう[a、b]。これを行うために3点ガウス法を使用したいとします(もちろん、どれでも構いません)。

理想的には、以下のような機能が欲しいです。

// 'f' is the integrand we want to integrate over [a, b] with 'n' subintervals.
static double Gauss3(Integrand f, double a, double b, int n) {
  double res = 0;

  // compute result
  // ...

  return res;
}

そのため、任意のIntegrand f を渡すことができ、閉じた区間にわたってその定積分を得ることができます。

Integrandはどんな型にすべきですか?

代議員なし

そうですね、デリゲートがなければ、次のように宣言されたevalのように、単一のメソッドを持つある種のインターフェースが必要になります。

// Interface describing real-valued functions of one variable.
interface Integrand {
  double eval(double x);
}

それでは、次のように、このインターフェースを実装するクラス全体を作成する必要があります。

// Some function
class MyFunc1 : Integrand {
  public double eval(double x) {
    return /* some_result */ ;
  }
}

// Some other function
class MyFunc2 : Integrand {
  public double eval(double x) {
    return /* some_result */ ;
  }
}

// etc

それから、Gauss3メソッドでそれらを使用するには、次のようにそれを呼び出す必要があります。

double res1 = Gauss3(new MyFunc1(), -1, 1, 16);
double res2 = Gauss3(new MyFunc2(), 0, Math.PI, 16);

そしてGauss3は以下のようになります。

static double Gauss3(Integrand f, double a, double b, int n) {
  // Use the integrand passed in:
  f.eval(x);
}

だから私たちはGuass3で私たちの任意の関数を使うためだけにそれをする必要があります。

代議員と

public delegate double Integrand(double x);

これで、そのプロトタイプに準拠したいくつかの静的(またはそうでない)関数を定義できます。

class Program {
   public delegate double Integrand(double x);   
   // Define implementations to above delegate 
   // with similar input and output types
   static double MyFunc1(double x) { /* ... */ }
   static double MyFunc2(double x) { /* ... */ }
   // ... etc ...

   public static double Gauss3(Integrand f, ...) { 
      // Now just call the function naturally, no f.eval() stuff.
      double a = f(x); 
      // ...
   }

   // Let's use it
   static void Main() {
     // Just pass the function in naturally (well, its reference).
     double res = Gauss3(MyFunc1, a, b, n);
     double res = Gauss3(MyFunc2, a, b, n);    
   }
}

単純なタスクのためのインターフェイス、不格好な.evalのもの、オブジェクトのインスタンス化、使用法のような単純な関数ポインタなどは不要です。

もちろん、デリゲートは内部の単なる関数ポインタ以上のものですが、それは別の問題です(関数チェーンとイベント)。

141
Alex Budovski

あなたが受け渡したいコードのブロックを宣言したいとき、デリゲートは非常に役に立ちます。たとえば、一般的な再試行メカニズムを使用している場合などです。

擬似:

function Retry(Delegate func, int numberOfTimes)
    try
    {
       func.Invoke();
    }
    catch { if(numberOfTimes blabla) func.Invoke(); etc. etc. }

Transformアクションがあり、BeforeTransformが満たされているかどうかを知らなくてもTransform関数内で評価できるAfterTransformおよびBeginTransformアクションが必要な関数のように、コードブロックの評価を遅くする場合またはそれが何を変換する必要があるのか​​。

そしてもちろんイベントハンドラを作成するとき。今すぐコードを評価する必要はありませんが、必要なときだけにします。そのため、イベントが発生したときに呼び出すことができるデリゲートを登録します。

27
Jan Jongboom

私はこれらのことに頭を触れているので、あなたはすでに説明を持っているので、私は例を共有するつもりですが、現時点で私が見る一つの利点その他.

アプリケーションがXMLをダウンロードしてからXMLをデータベースに保存するとします。

私は私のソリューションを構築する2つのプロジェクトをここに持っています:FTPとSaveDatabase。

そのため、私たちのアプリケーションは、ダウンロードを探してファイルをダウンロードすることから始め、次にSaveDatabaseプロジェクトを呼び出します。

今、私たちのアプリケーションは、メタデータを含むファイルをアップロードすることによってファイルがデータベースに保存されたときにFTPサイトに通知する必要があります(理由は無視してください、それはFTPサイトの所有者からの要求です)。問題は、どの時点でどのようになっていますか? NotifyFtpComplete()という新しいメソッドが必要ですが、FTPまたはSaveDatabaseのどちらのプロジェクトにも保存する必要がありますか。論理的には、コードは私たちのFTPプロジェクトにあるはずです。しかし、これは、NotifyFtpCompleteをトリガーする必要があるか、保存が完了するまで待機してからデータベースに照会してそこにあることを確認する必要があることを意味します。 SaveDatabaseプロジェクトにNotifyFtpComplete()メソッドを直接呼び出すように指示する必要がありますが、できません。円周状の参照を取得し、NotifyFtpComplete()はプライベートメソッドです。なんて残念なことでしょう、これはうまくいったでしょう。ええ、できます。

アプリケーションのコードの間、メソッド間でパラメータを渡していましたが、それらのパラメータの1つがNotifyFtpCompleteメソッドだった場合はどうなりますか。うん、すべてのコードも含めてメソッドを渡します。これは、どのプロジェクトからでも、いつでもメソッドを実行できることを意味します。まあ、これはデリゲートです。つまり、NotifyFtpComplete()メソッドをSaveDatabase()クラスのパラメータとして渡すことができます。保存した時点で、単にデリゲートを実行します。

この実例が役に立つかどうかを確認してください(疑似コード)。また、アプリケーションはFTPクラスのBegin()メソッドで始まると仮定します。

class FTP
{
    public void Begin()
    {
        string filePath = DownloadFileFromFtpAndReturnPathName();

        SaveDatabase sd = new SaveDatabase();
        sd.Begin(filePath, NotifyFtpComplete());
    }

    private void NotifyFtpComplete()
    {
        //Code to send file to FTP site
    }
}


class SaveDatabase
{
    private void Begin(string filePath, delegateType NotifyJobComplete())
    {
        SaveToTheDatabase(filePath);

        //InvokeTheDelegate - here we can execute the NotifyJobComplete method at our preferred moment in the application, despite the method being private and belonging to a different class. 
        NotifyJobComplete.Invoke();
    }
}

それで、それを説明したので、C#を使用してこのコンソールアプリケーションで今それを実際にすることができます

using System;

namespace ConsoleApplication1
{
    //I've made this class private to demonstrate that the SaveToDatabase cannot have any knowledge of this Program class.
    class Program
    {
        static void Main(string[] args)
        {
            //Note, this NotifyDelegate type is defined in the SaveToDatabase project
            NotifyDelegate nofityDelegate = new NotifyDelegate(NotifyIfComplete);

            SaveToDatabase sd = new SaveToDatabase();            
            sd.Start(nofityDelegate);
            Console.ReadKey();
        }

        //this is the method which will be delegated - the only thing it has in common with the NofityDelegate is that it takes 0 parameters and that it returns void. However, it is these 2 which are essential. It is really important to notice that it writes a variable which, due to no constructor, has not yet been called (so _notice is not initialized yet). 
    private static void NotifyIfComplete()
    {
        Console.WriteLine(_notice);
    }

    private static string _notice = "Notified";
    }


    public class SaveToDatabase
    {
        public void Start(NotifyDelegate nd)
        {
            Console.WriteLine("Yes, I shouldn't write to the console from here, it's just to demonstrate the code executed.");
            Console.WriteLine("SaveToDatabase Complete");
            Console.WriteLine(" ");
            nd.Invoke();
        }
    }
    public delegate void NotifyDelegate();
}

コードを一通り調べて、_noticeが呼び出されたときとメソッド(デリゲート)がこのとき呼び出されたときに確認することをお勧めします。

ただし、最後に、デリゲート型を変更してパラメータを含めることで、より便利にすることができます。

using System.Text;

namespace ConsoleApplication1
{
    //I've made this class private to demonstrate that the SaveToDatabase cannot have any knowledge of this Program class.
    class Program
    {
        static void Main(string[] args)
        {
            SaveToDatabase sd = new SaveToDatabase();

//Please note, that although NotifyIfComplete() takes a string parameter, we do not declare it - all we want to do is tell C# where the method is so it can be referenced later - we will pass the paramater later.
            NotifyDelegateWithMessage notifyDelegateWithMessage = new NotifyDelegateWithMessage(NotifyIfComplete);

            sd.Start(notifyDelegateWithMessage );

            Console.ReadKey();
        }

        private static void NotifyIfComplete(string message)
        {
            Console.WriteLine(message);
        }
    }


    public class SaveToDatabase
    {
        public void Start(NotifyDelegateWithMessage nd)
        {
            //To simulate a saving fail or success, I'm just going to check the current time (well, the seconds) and store the value as variable.
            string message = string.Empty;
            if (DateTime.Now.Second > 30)
                message = "Saved";
            else
                message = "Failed";

            //It is at this point we pass the parameter to our method.
            nd.Invoke(message);
        }
    }

    public delegate void NotifyDelegateWithMessage(string message);
}
21
Dave

参加者の概要

代議員は以下の性質を持っています:

  • デリゲートはC++関数ポインタに似ていますが、型保証されています。
  • デリゲートを使用すると、メソッドをパラメータとして渡すことができます。
  • デリゲートはコールバックメソッドを定義するために使用できます。
  • 参加者は一緒に連鎖することができます。たとえば、単一のイベントで複数のメソッドを呼び出すことができます。
  • メソッドはデリゲート署名と正確に一致する必要はありません。詳細は、共分散とコントラ分散を参照してください。
  • C#バージョン2.0では、個別に定義されたメソッドの代わりにコードブロックをパラメータとして渡すことができる匿名メソッドの概念が導入されました。
21

私はデリゲートを Anonymous Interfaces と見なします。多くの場合、単一のメソッドを持つインターフェースが必要なときはいつでも使用できますが、そのインターフェースを定義することによるオーバーヘッドは不要です。

9
Mark Seemann

デリゲートは、特定のシグネチャを持つメソッドを指すために使用される単純なクラスで、基本的に型保証関数ポインタになります。デリゲートの目的は、構造化された方法で、完了した後に別のメソッドへのコールバックを容易にすることです。

この機能を実行するための広範なコードセットを作成することは可能かもしれませんが、あなたも必要ではありません。あなたは代理人を使うことができます。

デリゲートを作成するのは簡単です。 "delegate"キーワードを使用して、クラスをデリゲートとして識別します。それからその型のシグネチャを指定します。

3
Pankaj