Когда и зачем использовать делегатов?

Я относительно новичок в С#, и мне интересно, когда использовать делегатов надлежащим образом. они широко используются в объявлении событий, но когда я должен использовать их в своем собственном коде и почему они полезны? почему бы не использовать что-то еще?

Мне также интересно, когда я должен использовать делегатов, и у меня нет другой альтернативы.

Спасибо вам за помощь!

РЕДАКТИРОВАТЬ: Я думаю, что я нашел необходимое использование делегатов здесь

Ответ 1

Я согласен со всем, что уже сказано, просто пытаясь наложить на него некоторые другие слова.

Делегат можно рассматривать как местозаполнитель для какого-либо метода (ов).

Определив делегата, вы говорите пользователю своего класса: "Не стесняйтесь назначать, любой метод, соответствующий этой подписи, делегату, и он будет вызываться каждый раз, когда мой делегат называется".

Типичное использование - это, конечно, события. Все делегаты OnEventX определяют методы, которые пользователь определяет.

Делегаты могут предложить пользовательскому объекту возможность настраивать свое поведение. В большинстве случаев вы можете использовать другие способы достижения той же цели, и я не верю, что вы могли бы быть принудительно создавать делегаты. Это просто самый простой способ в некоторых ситуациях сделать это.

Ответ 2

Делегат - это ссылка на метод. В то время как объекты могут быть легко отправлены в качестве параметров в методы, конструктор или что-то еще, методы немного сложнее. Но время от времени вы можете чувствовать необходимость посылать метод в качестве параметра другому методу, и это когда вам понадобятся делегаты.

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

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;
    }
  }
}

Выход:

Children:
Jake, 12 years old


Adults:
John, 41 years old
Jane, 69 years old
Jessie, 25 years old


Seniors:
Jane, 69 years old

Ответ 3

Предположим, вы хотите написать процедуру для интегрирования некоторой вещественной функции 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 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);    
   }
}

Никаких интерфейсов, никаких неуклюжих, неизмененных объектов, без создания объектов, просто простого указателя функций, такого как использование, для простой задачи.

Конечно, делегаты - это больше, чем просто указатели на объекты под капотом, но это отдельная проблема (цепочка функций и события).

Ответ 4

Делегаты чрезвычайно полезны, когда хотят объявить блок кода, который вы хотите передать. Например, при использовании общего механизма повторных попыток.

Псевдо:

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

Или, если вы хотите выполнить позднюю оценку блоков кода, например функцию, в которой у вас есть действие Transform, и хотите иметь действие BeforeTransform и AfterTransform, которое вы можете оценить в своей функции Transform, без необходимости знать, заполняется ли BeginTransform или что он должен преобразовать.

И, конечно, при создании обработчиков событий. Вы не хотите оценивать код сейчас, но только при необходимости, поэтому вы регистрируете делегат, который может быть вызван при возникновении события.

Ответ 5

Обзор делегатов

Делегаты обладают следующими свойствами:

  • Делегаты похожи на указатели на функции С++, но безопасны по типу.
  • Делегаты позволяют передавать методы в качестве параметров.
  • Делегаты могут использоваться для определения методов обратного вызова.
  • Делегаты могут быть соединены вместе; например, несколько методов могут быть вызваны в одном событии.
  • Методам не нужно точно соответствовать подписи делегата. Дополнительные сведения см. В разделе Ковариация и отклонение Contra.
  • Версия С# версии 2.0 представляет концепцию анонимных методов, которые позволяют передавать блоки кода в качестве параметров вместо отдельно определенного метода.

Ответ 6

Я просто обдумываю их, и поэтому я поделюсь примером, так как у вас уже есть описания, но на данный момент я вижу одно преимущество - обойти предупреждения стиля Циркулярной ссылки, когда у вас не может быть двух проектов, ссылающихся на каждый другой.

Предположим, что приложение загружает XML, а затем сохраняет XML в базе данных.

У меня есть 2 проекта, которые создают мое решение: FTP и SaveDatabase.

Итак, наше приложение начинается с поиска каких-либо загрузок и скачивания файлов, а затем вызывает проект SaveDatabase.

Теперь нашему приложению необходимо уведомить FTP-сайт о сохранении файла в базе данных, загрузив файл с метаданными (игнорируйте почему, это запрос от владельца FTP-сайта). Вопрос в том, в какой момент и как? Нам нужен новый метод с именем NotifyFtpComplete(), но в каком из наших проектов он также должен быть сохранен - FTP или SaveDatabase? По логике, код должен жить в нашем проекте FTP. Но это будет означать, что наш NotifyFtpComplete должен быть запущен, или он должен будет дождаться завершения сохранения, а затем запросить базу данных, чтобы убедиться, что она там. Нам нужно сказать нашему проекту SaveDatabase вызвать метод NotifyFtpComplete() напрямую, но мы не можем; мы получили бы ссылку на Circular, и NotifyFtpComplete() является закрытым методом. Какой позор, это сработало бы. Ну, это может.

Во время нашего кода приложения мы передавали параметры между методами, но что, если одним из этих параметров был метод NotifyFtpComplete. Да, мы передаем метод со всем кодом внутри. Это будет означать, что мы можем выполнить метод в любой точке, из любого проекта. Ну, это то, что делегат. Это означает, что мы можем передать метод NotifyFtpComplete() в качестве параметра нашему классу SaveDatabase(). В момент сохранения, он просто выполняет делегат.

Посмотрите, поможет ли этот грубый пример (псевдокод). Мы также предполагаем, что приложение запускается с помощью метода Begin() класса FTP.

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();
    }
}

Итак, объяснив это, мы можем сделать это по-настоящему с помощью этого консольного приложения, используя С#

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)
        {
            /* I shouldn't write to the console from here, 
             * just for demonstration purposes
             */
            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 parameter later.
         */
            var 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);
}

Ответ 7

Я считаю делегатов анонимных интерфейсов. Во многих случаях вы можете использовать их каждый раз, когда вам нужен интерфейс с помощью одного метода, но вы не хотите накладных расходов на определение этого интерфейса.

Ответ 8

Делегат - это простой класс, который используется для указания методов с определенной сигнатурой, становясь по существу указателем на функцию типа. Цель делегата - облегчить обратный вызов другому методу (или методам) после того, как он был завершен, структурированным способом.

В то время как для выполнения этой функции можно создать обширный набор кода, вам тоже не понадобится. Вы можете использовать делегат.

Создание делегата легко сделать. Определите класс как делегата с ключевым словом "delegate". Затем укажите подпись типа.