kiedy i dlaczego używać delegatów? [duplikat]

To pytanie ma już odpowiedź tutaj:

Jestem stosunkowo nowy w C# i zastanawiam się kiedy właściwie używać delegatów. są one powszechnie używane w deklaracji zdarzeń, ale kiedy powinienem ich używać w moim własnym kodzie i dlaczego są przydatne? dlaczego nie użyć czegoś else?

Zastanawiam się również kiedy muszę korzystać z delegatów i nie mam innej alternatywy .

Dziękuję za pomoc!

EDIT: chyba znalazłem niezbędne użycie delegatów tutaj

Author: iChaib, 2010-01-07

8 answers

Zgadzam się ze wszystkim, co już zostało powiedziane, po prostu staram się umieścić na nim inne słowa.

Delegat może być postrzegany jako symbol zastępczy dla niektórych metod.

Definiując delegata, mówisz do użytkownika swojej klasy: "prosimy o przypisanie dowolnej metody, która pasuje do tego podpisu, do delegata i będzie ona wywoływana za każdym razem, gdy mój delegat zostanie wywołany ".

Typowe użycie to oczywiście zdarzenia. Wszystkie oneventx delegują do metod użytkownik definiuje.

Delegaty są przydatne, aby zaoferować użytkownikowi Twoich obiektów możliwość dostosowania ich zachowania. Przez większość czasu możesz użyć innych sposobów, aby osiągnąć ten sam cel i nie wierzę, że kiedykolwiek możesz być zmuszony do tworzenia delegatów. Jest to po prostu najprostszy sposób w niektórych sytuacjach, aby to zrobić.

 232
Author: Benoit Vidis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-18 20:18:34

Delegat jest odniesieniem do metody. Podczas gdy obiekty mogą być łatwo wysyłane jako parametry do metod, konstruktorów lub czegokolwiek innego, metody są nieco trudniejsze. Ale od czasu do czasu możesz odczuwać potrzebę wysłania metody jako parametru do innej metody i wtedy będziesz potrzebować delegatów.

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;
    }
  }
}
 256
Author: dhaval8087,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-04 05:46:38

Powiedz, że chcesz napisać procedurę integrującą jakąś funkcję o wartości rzeczywistej f (x ) w pewnym przedziale [a, b]. Powiedzmy, że chcemy użyć 3-punktowej metody Gaussa, aby to zrobić (każdy zrobi, oczywiście).

Idealnie chcemy jakąś funkcję, która wygląda następująco:

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

Więc możemy przejść w dowolnym Integrand, f , i uzyskać jej całkę określoną w przedziale zamkniętym.

Jaki powinien być Typ Integrand?

Bez Delegatów

Dobrze, bez delegatów, potrzebowalibyśmy pewnego rodzaju interfejsu z jedną metodą, powiedzmy eval zadeklarowaną w następujący sposób:

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

Wtedy musielibyśmy utworzyć całą masę klas implementujących ten interfejs, w następujący sposób:

// 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
Aby użyć ich w naszej metodzie Gauss3, musimy wywołać ją w następujący sposób:]}
double res1 = Gauss3(new MyFunc1(), -1, 1, 16);
double res2 = Gauss3(new MyFunc2(), 0, Math.PI, 16);

I Gauss3 musi wyglądać tak:

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

Więc musimy zrobić to wszystko tylko po to, aby użyć naszych arbitralnych funkcji w Guass3.

Z Delegaci

public delegate double Integrand(double x);

Teraz możemy zdefiniować pewne statyczne (lub nie) funkcje przylegające do tego prototypu:

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

Brak interfejsów, brak klekotania .eval rzeczy, bez obiekt instantiation, po prostu prosty wskaźnik funkcji jak użycie, dla prostego zadania.

Oczywiście delegaty są czymś więcej niż tylko wskaźnikami funkcji pod maską, ale jest to osobna kwestia (łączenie funkcji i zdarzenia).

 138
Author: Alex Budovski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-15 02:04:50

Delegaci są niezwykle przydatni, gdy chcą zadeklarować blok kodu, który chcesz przekazać. Na przykład przy użyciu ogólnego mechanizmu retry.

Pseudo:

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

Lub gdy chcesz wykonać późną ocenę bloków kodu, jak funkcja, w której masz jakąś akcję Transform i chcesz mieć akcję BeforeTransform i AfterTransform, które możesz ocenić w swojej funkcji przekształcania, bez konieczności wiedzieć, czy BeginTransform jest wypełniona, lub co ma zostać przekształcone.

I z kurs podczas tworzenia programów obsługi zdarzeń. Nie chcesz teraz oceniać kodu, ale tylko wtedy, gdy jest to konieczne, więc rejestrujesz delegata, który można wywołać, gdy wystąpi zdarzenie.

 26
Author: Jan Jongboom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-07 09:56:42

Przegląd Delegatów

delegaci mają następujące właściwości:

  • Delegaty są podobne do wskaźników funkcji C++, ale są bezpieczne dla typu.
  • Delegaty umożliwiają przekazywanie metod jako parametrów.
  • Delegaty mogą być używane do definiowania metod wywołania zwrotnego.
  • Delegaty mogą być połączone łańcuchowo; na przykład można wywołać wiele metod w jednym zdarzeniu.
  • metody nie muszą dokładnie pasować do podpisu delegata. Aby uzyskać więcej informacji, zobacz kowariancję i kontra wariancję.
  • C# w wersji 2.0 wprowadza pojęcie anonimowych metod, które pozwalają na przekazywanie bloków kodu jako parametrów zamiast oddzielnie zdefiniowanej metody.
 21
Author: Lukas Šalkauskas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-13 00:06:15

Po prostu zająłem się tymi, więc podzielę się przykładem, ponieważ już masz opisy, ale w tej chwili jedną z zalet, jakie widzę, jest obejście okrągłych ostrzeżeń w stylu odniesienia, gdzie nie można mieć 2 projektów odwołujących się do siebie.

Załóżmy, że aplikacja pobiera XML, a następnie zapisuje XML do bazy danych.

Mam tutaj 2 projekty, które budują moje rozwiązanie: FTP i SaveDatabase.

Więc nasza aplikacja zaczyna się od szukania wszelkich pobrań i pobierając plik(y) następnie wywołuje projekt SaveDatabase.

Teraz nasza aplikacja musi powiadomić stronę FTP, gdy plik zostanie zapisany w bazie danych, przesyłając plik z metadanymi (zignoruj dlaczego, jest to żądanie od właściciela strony FTP). Problem polega na tym, w jakim momencie i w jaki sposób? Potrzebujemy nowej metody o nazwie NotifyFtpComplete (), ale w którym z naszych projektów też powinna być zapisana-FTP czy SaveDatabase? Logicznie rzecz biorąc, kod powinien żyć w naszym projekcie FTP. Ale to oznaczałoby nasz NotifyFtpComplete będzie musiał zostać uruchomiony lub będzie musiał poczekać, aż zapisanie zostanie zakończone, a następnie odpytać bazę danych, aby upewnić się, że jest tam. To, co musimy zrobić, to powiedzieć naszemu projektowi SaveDatabase, aby wywołał metodę NotifyFtpComplete() direct, ale nie możemy; otrzymamy referencję ciruclar i NotifyFtpComplete() jest metodą prywatną. Jaka szkoda, to by zadziałało. Może.

W kodzie naszej aplikacji przekazalibyśmy parametry między metodami, ale co jeśli jednym z tych parametrów była metoda NotifyFtpComplete. Tak, przekazujemy metodę, z całym kodem w środku, jak również. Oznaczałoby to, że możemy wykonać metodę w dowolnym momencie, z dowolnego projektu. Tym właśnie jest delegat. Oznacza to, że możemy przekazać metodę NotifyFtpComplete () jako parametr do Naszej Klasy SaveDatabase (). W momencie zapisywania, po prostu wykonuje delegata.

Zobacz, Czy ten prymitywny przykład pomaga (pseudo kod). Zakładamy również, że aplikacja uruchomi się z metodą Begin() klasy 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();
    }
}

Więc, z tym wyjaśnieniem, możemy zrobić to naprawdę teraz z tej aplikacji konsolowej za pomocą 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();
}

Proponuję przejść przez kod i zobaczyć, kiedy _notice jest wywoływana i kiedy metoda (delegat) jest wywoływana, mam nadzieję, że to wszystko wyjaśni.

Wreszcie możemy uczynić go bardziej użytecznym, zmieniając typ delegata na parametr.

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 nofityDelegateWithMessage = new NotifyDelegateWithMessage(NotifyIfComplete);

            sd.Start(nofityDelegateWithMessage);

            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);
}
 20
Author: Dave Rook,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-22 08:23:19

Uważam delegatów za anonimowe Interfejsy . W wielu przypadkach można ich używać, gdy potrzebujesz interfejsu z jedną metodą, ale nie chcesz, aby narzut definiujący ten interfejs.

 9
Author: Mark Seemann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-07 09:55:08

Delegat jest prostą klasą, która służy do wskazywania metod z określonym podpisem, stając się zasadniczo wskaźnikiem funkcji bezpiecznych dla typu. Celem delegata jest ułatwienie wywołania z powrotem do innej metody (lub metod), po zakończeniu jednej z nich, w zorganizowany sposób.

Chociaż możliwe jest stworzenie obszernego zestawu kodu do wykonywania tej funkcji, nie potrzebujesz też. Możesz użyć delegata.

Tworzenie delegata jest łatwe do zrobienia. Określ klasę jako a deleguj za pomocą słowa kluczowego "deleguj". Następnie określ podpis typu.

 3
Author: Pankaj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-07 12:51:00