Delegowanie kontra interfejsy - Czy są jeszcze jakieś wyjaśnienia?

23

Po przeczytaniu article- kiedy używać delegatów zamiast interfejsów (C # Programming Guide) , Potrzebuję pomocy ze zrozumieniem podane poniżej punkty, które okazały się być nie tak oczywiste (dla mnie). Dostępne są jakieś przykłady lub szczegółowe wyjaśnienia?

Użyj delegata, gdy:

  • Zastosowano wzorzec projektowy eventing.
  • Pożądane jest kapsułkowanie metody statycznej.
  • Pożądana jest łatwa kompozycja.
  • Klasa może wymagać więcej niż jednej implementacji metody.

Użyj interfejsu, gdy:

  • Istnieje grupa powiązanych metod, które można wywołać.
  • Klasa potrzebuje tylko jednej implementacji metody.

Moje pytania to

  1. Co rozumieją przez wzorzec projektowania eventing?
  2. Jak kompozycja okazuje się łatwa, jeśli używa się delegata?
  3. jeśli istnieje grupa powiązanych metod, które można wywołać, należy użyć interfejsu - jakie to ma korzyści?
  4. jeśli klasa potrzebuje tylko jednej implementacji metody, użyj interfejsu - jak jest to uzasadnione pod względem korzyści?
WinW
źródło

Odpowiedzi:

11

Co rozumieją przez wzorzec projektowania eventing?

Najprawdopodobniej odnoszą się do implementacji wzorca obserwatora, który jest konstrukcją języka podstawowego w języku C #, ujawnioną jako „ zdarzenia ”. Odsłuchiwanie wydarzeń jest możliwe poprzez podpięcie do nich delegata. Jak zauważył Yam Marcovic, EventHandlerjest to konwencjonalny podstawowy typ delegata dla zdarzeń, ale można użyć dowolnego typu delegata.

Jak kompozycja okazuje się łatwa, jeśli używa się delegata?

Prawdopodobnie dotyczy to tylko elastyczności oferty delegatów. Możesz łatwo „skomponować” określone zachowanie. Przy pomocy lambdas składnia do tego jest również bardzo zwięzła. Rozważ następujący przykład.

class Bunny
{
    Func<bool> _canHop;

    public Bunny( Func<bool> canHop )
    {
        _canHop = canHop;
    }

    public void Hop()
    {
        if ( _canHop() )  Console.WriteLine( "Hop!" );
    }
}

Bunny captiveBunny = new Bunny( () => IsBunnyReleased );
Bunny lazyBunny = new Bunny( () => !IsLazyDay );
Bunny captiveLazyBunny = new Bunny( () => IsBunnyReleased && !IsLazyDay );

Wykonanie czegoś podobnego z interfejsami wymagałoby albo użycia wzorca strategii, albo (abstrakcyjnej) Bunnyklasy bazowej, z której można rozszerzyć bardziej szczegółowe króliczki.

jeśli istnieje grupa powiązanych metod, które można wywołać, należy użyć interfejsu - jakie to ma korzyści?

Znowu użyję króliczków, aby pokazać, jak byłoby łatwiej.

interface IAnimal
{
    void Jump();
    void Eat();
    void Poo();
}

class Bunny : IAnimal { ... }
class Chick : IAnimal { ... }

// Using the interface.
IAnimal bunny = new Bunny();
bunny.Jump();  bunny.Eat();  bunny.Poo();
IAnimal chick = new Chick();
chick.Jump();  chick.Eat();  chick.Poo();

// Without the interface.
Action bunnyJump = () => bunny.Jump();
Action bunnyEat = () => bunny.Eat();
Action bunnyPoo = () => bunny.Poo();
bunnyJump(); bunnyEat(); bunnyPoo();
Action chickJump = () => chick.Jump();
Action chickEat = () => chick.Eat();
...

jeśli klasa potrzebuje tylko jednej implementacji metody, użyj interfejsu - jak jest to uzasadnione pod względem korzyści?

W tym celu ponownie rozważ pierwszy przykład z króliczkiem. Jeśli kiedykolwiek potrzebna jest tylko jedna implementacja - nie jest wymagana żadna kompozycja niestandardowa - możesz ujawnić to zachowanie jako interfejs. Nigdy nie będziesz musiał budować lambdas, możesz po prostu użyć interfejsu.

Wniosek

Delegaci oferują znacznie większą elastyczność, a interfejsy pomagają w zawarciu silnych umów. Dlatego znajduję ostatni wspomniany punkt: „Klasa może wymagać więcej niż jednej implementacji metody”. , zdecydowanie najbardziej odpowiedni.

Dodatkowym powodem używania delegatów jest to, że chcesz odsłonić tylko część klasy, z której nie można dostosować pliku źródłowego.

Jako przykład takiego scenariusza (maksymalna elastyczność, nie trzeba modyfikować źródeł), rozważ tę implementację algorytmu wyszukiwania binarnego dla każdego możliwego zbioru, przekazując tylko dwóch delegatów.

Steven Jeuris
źródło
12

Delegat jest czymś w rodzaju interfejsu dla pojedynczej sygnatury metody, który nie musi być jawnie implementowany jak zwykły interfejs. Możesz zbudować go w biegu.

Interfejs to po prostu struktura językowa reprezentująca pewną umowę - „Gwarantuję, że udostępnię następujące metody i właściwości”.

Ponadto nie do końca zgadzam się z tym, że delegaci są przede wszystkim użyteczni, gdy są stosowane jako rozwiązanie wzorca obserwatora / subskrybenta. Jest to jednak eleganckie rozwiązanie „problemu gadatliwości języka Java”.

Na twoje pytania:

1 i 2)

Jeśli chcesz utworzyć system zdarzeń w Javie, zwykle używasz interfejsu do propagowania zdarzenia, na przykład:

interface KeyboardListener
{
    void KeyDown(int key);
    void KeyUp(int key)
    void KeyPress(int key);
    .... and so on
}

Oznacza to, że twoja klasa będzie musiała jawnie zaimplementować wszystkie te metody i udostępnić kody pośredniczące dla wszystkich z nich, nawet jeśli po prostu chcesz je zaimplementować KeyPress(int key).

W języku C # zdarzenia te będą reprezentowane jako listy uczestników, ukryte przez słowo kluczowe „event” w języku c #, po jednym dla każdego pojedynczego zdarzenia. Oznacza to, że możesz łatwo zasubskrybować to, co chcesz, bez obciążania swojej klasy publicznymi metodami „klucza” itp.

+1 za punkty 3-5.

Dodatkowo:

Delegaci są bardzo przydatni, gdy chcesz na przykład udostępnić funkcję „mapy”, która przenosi listę i projektuje każdy element na nową listę z taką samą liczbą elementów, ale w jakiś sposób inny. Zasadniczo IEnumerable.Select (...).

IEnumerable.Select przyjmuje Func<TSource, TDest>, który jest delegatem opakowującym funkcję, która pobiera element TSource i przekształca ten element w element TDest.

W Javie należy to zaimplementować za pomocą interfejsu. Często nie ma naturalnego miejsca na wdrożenie takiego interfejsu. Jeśli klasa zawiera listę, którą chce w jakiś sposób przekształcić, to nie jest bardzo naturalne, że implementuje interfejs „ListTransformer”, zwłaszcza że mogą istnieć dwie różne listy, które należy przekształcić na różne sposoby.

Oczywiście możesz użyć anonimowych klas, które są podobną koncepcją (w java).

Max
źródło
Zastanów się nad edytowaniem swojego postu, aby faktycznie odpowiedzieć na jego pytania
Yam Marcovic
@YMMarcovic Masz rację, trochę pokłóciłem się swobodnie, zamiast bezpośrednio odpowiadać na jego pytania. Mimo to myślę, że to trochę wyjaśnia mechanikę. Ponadto jego pytania były nieco mniej dobrze sformułowane, gdy odpowiedziałem na pytanie. : p
Maks.
Naturalnie. Zdarza mi się również i ma swoją wartość jako taką. Dlatego tylko to zasugerowałem , ale nie narzekałem. :)
Yam Marcovic
3

1) Wzory eventingowe, cóż, klasycznym jest wzorzec Observer, oto dobry link Microsoft Talk Observer

Artykuł, do którego linkujesz, nie jest zbyt dobrze napisany imo i sprawia, że ​​sprawy są bardziej skomplikowane niż to konieczne. Statyczny biznes ma zdrowy rozsądek, nie można zdefiniować interfejsu z elementami statycznymi, więc jeśli chcesz polimorficznego zachowania w metodzie statycznej, użyłbyś delegata.

Powyższy link mówi o delegatach i dlaczego warto je komponować, aby pomóc w zapytaniu.

Ian
źródło
3

Przede wszystkim dobre pytanie. Podziwiam twój nacisk na użyteczność, a nie ślepe przyjmowanie „najlepszych praktyk”. +1 za to.

Przeczytałem już ten przewodnik. Musisz coś o tym pamiętać - to tylko przewodnik, głównie dla początkujących C #, którzy wiedzą, jak programować, ale nie są tak obeznani z robieniem rzeczy w C #. Jest to nie tyle strona reguł, ile strona, która opisuje, w jaki sposób rzeczy są już zwykle wykonywane. A ponieważ są już wszędzie tak robione, dobrym pomysłem może być zachowanie spójności.

Przejdę do sedna, odpowiadając na twoje pytania.

Po pierwsze zakładam, że wiesz już, czym jest interfejs. Jeśli chodzi o delegata, wystarczy powiedzieć, że jest to struktura zawierająca typowany wskaźnik do metody wraz z opcjonalnym wskaźnikiem do obiektu reprezentującego thisargument dla tej metody. W przypadku metod statycznych ten ostatni wskaźnik jest zerowy.
Istnieją również delegaci multiemisji, którzy są tak samo jak delegaci, ale mogą mieć przypisane kilka z tych struktur (co oznacza pojedyncze wywołanie Invoke na delegacie multiemisji wywołuje wszystkie metody z przypisanej listy wywołań).

Co rozumieją przez wzorzec projektowania eventing?

Oznaczają one używanie zdarzeń w języku C # (który zawiera specjalne słowa kluczowe do zaawansowanego wdrażania tego niezwykle użytecznego wzorca). Wydarzenia w C # są napędzane przez delegatów multiemisji.

Podczas definiowania zdarzenia, takiego jak w tym przykładzie:

class MyClass {
  // Note: EventHandler is just a multicast delegate,
  // that returns void and accepts (object sender, EventArgs e)!
  public event EventHandler MyEvent;

  public void DoSomethingThatTriggersMyEvent() {
    // ... some code
    var handler = MyEvent;
    if (handler != null)
      handler(this, EventArgs.Empty);
    // ... some other code
  }
}

Kompilator faktycznie przekształca to w następujący kod:

class MyClass {
  private EventHandler MyEvent = null;

  public void add_MyEvent(EventHandler value) {
    MyEvent += value;
  }

  public void remove_MyEvent(EventHandler value) {
    MyEvent -= value;
  }

  public void DoSomethingThatTriggersMyEvent() {
    // ... some code
    var handler = MyEvent;
    if (handler != null)
      handler(this, EventArgs.Empty);
    // ... some other code
  }
}

Następnie subskrybujesz wydarzenie, robiąc to

MyClass instance = new MyClass();
instance.MyEvent += SomeMethodInMyClass;

Który kompiluje do

MyClass instance = new MyClass();
instance.add_MyEvent(new EventHandler(SomeMethodInMyClass));

To się dzieje w C # (lub .NET w ogóle).

Jak kompozycja okazuje się łatwa, jeśli używa się delegata?

Można to łatwo wykazać:

Załóżmy, że masz klasę, która zależy od zestawu działań, które zostaną do niej przekazane. Możesz zamknąć te działania w interfejsie:

interface RequiredMethods {
  void DoX();
  int DoY();
};

I każdy, kto chciałby przekazać działania twojej klasie, musiałby najpierw wdrożyć ten interfejs. Lub możesz ułatwić ich życie , zależnie od następujących klas:

sealed class RequiredMethods {
  public Action DoX;
  public Func<int> DoY();
}

W ten sposób osoby dzwoniące muszą jedynie utworzyć instancję RequiredMethods i powiązać metody z delegatami w czasie wykonywania. To jest zwykle łatwiejsze.

Ten sposób robienia rzeczy jest niezwykle korzystny w odpowiednich okolicznościach. Pomyśl o tym - po co polegać na interfejsie, skoro naprawdę zależy Ci na tym, aby przekazać Ci implementację?

Korzyści z używania interfejsów, gdy istnieje grupa powiązanych metod

Korzystne jest używanie interfejsów, ponieważ interfejsy zwykle wymagają wyraźnych implementacji czasu kompilacji. Oznacza to, że tworzysz nową klasę.
A jeśli masz grupę powiązanych metod w jednym pakiecie, korzystne jest, aby ten pakiet mógł być ponownie wykorzystany przez inne części kodu. Jeśli więc mogą po prostu utworzyć instancję klasy zamiast budować zestaw delegatów, jest to łatwiejsze.

Korzyści z używania interfejsów, jeśli klasa wymaga tylko jednej implementacji

Jak wspomniano wcześniej, interfejsy są wdrażane w czasie kompilacji - co oznacza, że ​​są one bardziej wydajne niż wywoływanie delegata (co jest poziomem pośrednim per se).

„Jedna implementacja” może oznaczać implementację, która istnieje w jednym dobrze zdefiniowanym miejscu.
W przeciwnym razie implementacja może pochodzić z dowolnego miejsca w programie, co akurat jest zgodne z podpisem metody. Pozwala to na większą elastyczność, ponieważ metody muszą jedynie być zgodne z oczekiwanym podpisem, a nie należeć do klasy, która wyraźnie implementuje określony interfejs. Ale ta elastyczność może kosztować i faktycznie łamie zasadę substytucji Liskowa , ponieważ przez większość czasu potrzebujesz jawności, ponieważ minimalizuje ona ryzyko wypadków. Podobnie jak pisanie statyczne.

Termin może również odnosić się tutaj do delegatów multiemisji. Metody zadeklarowane przez interfejsy mogą być zaimplementowane tylko raz w klasie implementującej. Ale uczestnicy mogą gromadzić wiele metod, które będą wywoływane sekwencyjnie.

Podsumowując, wygląda na to, że przewodnik nie jest wystarczająco informacyjny i po prostu funkcjonuje tak, jak jest - przewodnikiem, a nie zbiorem reguł. Niektóre porady mogą brzmieć nieco sprzecznie. To Ty decydujesz, kiedy należy zastosować. Przewodnik wydaje nam się jedynie ogólną ścieżką.

Mam nadzieję, że odpowiedziano na twoje pytania. I znowu, podziękowania za pytanie.

Yam Marcovic
źródło
2

Jeśli nadal mam pamięć .NET, delegat jest w zasadzie funkcją ptr lub funktorem. Dodaje warstwę pośrednią do wywołania funkcji, dzięki czemu funkcje można zastąpić bez konieczności zmiany kodu wywołującego. Jest to to samo, co interfejs, z wyjątkiem tego, że interfejs pakuje wiele funkcji razem, a implementator musi je zaimplementować razem.

Ogólnie rzecz biorąc, wzorzec zdarzeń to taki, w którym coś reaguje na zdarzenia z dowolnego miejsca (takie jak komunikaty systemu Windows). Zbiór wydarzeń jest zwykle otwarty, mogą występować w dowolnej kolejności i niekoniecznie są ze sobą powiązane. Delegaci pracują w tym dobrze, ponieważ każde zdarzenie może wywołać pojedynczą funkcję bez potrzeby odwoływania się do szeregu obiektów implementujących, które mogą również zawierać wiele nieistotnych funkcji. Ponadto (w tym przypadku moja pamięć .NET jest niejasna), myślę, że do wydarzenia można dołączyć wielu delegatów.

Komponowanie, choć nie znam tego pojęcia, w zasadzie polega na zaprojektowaniu jednego obiektu tak, aby zawierał wiele pod-części lub zagregowanych elementów potomnych, do których praca jest przekazywana. Delegaci pozwalają mieszać i dopasowywać dzieci w bardziej doraźny sposób, w którym interfejs może być przesadny lub powodować nadmierne sprzężenie oraz towarzyszącą mu sztywność i kruchość.

Zaletą interfejsu dla powiązanych metod jest to, że metody mogą współużytkować stan obiektu implementującego. Funkcje delegowania nie mogą tak wspaniale współdzielić ani nawet zawierać stanu.

Jeśli klasa wymaga pojedynczej implementacji, interfejs jest bardziej odpowiedni, ponieważ w dowolnej klasie implementującej całą kolekcję można wykonać tylko jedną implementację i uzyskać korzyści z klasy implementującej (stan, enkapsulacja itp.). Jeśli implementacja może ulec zmianie ze względu na stan środowiska wykonawczego, delegaci działają lepiej, ponieważ można je zamienić na inne implementacje bez wpływu na inne metody. Na przykład, jeśli są trzy delegaty, z których każdy ma dwie możliwe implementacje, potrzebujesz ośmiu różnych klas implementujących interfejs trzech metod, aby uwzględnić wszystkie możliwe kombinacje stanów.

Kylben
źródło
1

Wzorzec projektu „eventing” (lepiej znany jako wzorzec obserwatora) umożliwia dołączenie do delegata wielu metod tego samego podpisu. Naprawdę nie można tego zrobić za pomocą interfejsu.

Jednak wcale nie jestem przekonany, że składanie jest łatwiejsze dla delegata niż interfejs. To bardzo dziwne stwierdzenie. Zastanawiam się, czy ma na myśli, ponieważ możesz dołączyć anonimowe metody do delegata.

pdr
źródło
1

Największe wyjaśnienie, jakie mogę zaoferować:

  1. Delegat definiuje podpis funkcji - które parametry przyjmie odpowiednia funkcja i co zwróci.
  2. Interfejs obsługuje cały zestaw funkcji, zdarzeń, właściwości i pól.

Więc:

  1. Jeśli chcesz uogólnić niektóre funkcje z tym samym podpisem - skorzystaj z delegata.
  2. Jeśli chcesz uogólnić pewne zachowanie lub jakość klasy - użyj interfejsu.
John Fisher
źródło
1

Twoje pytanie dotyczące wydarzeń zostało już dobrze ujęte. Prawdą jest również to, że interfejs może definiować wiele metod (ale tak naprawdę nie musi), podczas gdy typ funkcji zawsze nakłada ograniczenia na pojedynczą funkcję.

Prawdziwa różnica polega jednak na:

  • wartości funkcji są dopasowywane do typów funkcji przez podtypowanie strukturalne (tj. domyślna zgodność z wymaganą strukturą). Oznacza to, że jeśli wartość funkcji ma podpis zgodny z typem funkcji, jest to poprawna wartość dla tego typu.
  • instancje są dopasowane do interfejsów przez nominalne podtypy (tj. jawne użycie nazwy). Oznacza to, że jeśli instancja należy do klasy, która jawnie implementuje dany interfejs, instancja jest prawidłową wartością interfejsu. Jeśli jednak obiekt ma tylko wszystkie elementy wymagane przez interfejsy, a wszystkie elementy mają zgodne podpisy, ale nie implementuje jawnie interfejsu, nie jest to poprawna wartość interfejsu.

Jasne, ale co to znaczy?

Weźmy ten przykład (kod jest w haXe, ponieważ mój C # nie jest zbyt dobry):

class Collection<T> {
    /* a lot of code we don't care about now */
    public function filter(predicate:T->Bool):Collection<T> { 
         //build a new collection with all elements e, such that predicate(e) == true
    }
    public function remove(e:T):Bool {
         //removes an element from this collection, if contained and returns true, false otherwise
    }
}

Teraz metoda filtrowania ułatwia wygodne przekazywanie tylko niewielkiej logiki, która nie jest świadoma wewnętrznej organizacji kolekcji, podczas gdy kolekcja nie zależy od nadanej jej logiki. Świetny. Z wyjątkiem jednego problemu:
Zbiór nie zależą od logiki. Kolekcja nieodłącznie przyjmuje założenie, że przekazana funkcja ma na celu przetestowanie wartości względem warunku i zwrócenie sukcesu testu. Zauważ, że nie wszystkie funkcje, które przyjmują jedną wartość jako argument i zwracają wartość logiczną, są w rzeczywistości zwykłymi predykatami. Na przykład metoda usuwania naszej kolekcji jest taką funkcją.
Załóżmy, że zadzwoniliśmy c.filter(c.remove). Rezultatem byłaby kolekcja ze wszystkimi elementami cwhilecsam staje się pusty. Jest to niefortunne, ponieważ naturalnie spodziewalibyśmy csię, że będzie niezmienny.

Przykład jest bardzo skonstruowany. Ale kluczowym problemem jest to, że wywołanie kodu c.filterz pewną wartością funkcji jako argumentu nie ma możliwości dowiedzenia się, czy ten argument jest odpowiedni (tj. Ostatecznie zachowa niezmiennik). Kod, który utworzył wartość funkcji może, ale nie musi wiedzieć, że będzie interpretowany jako predykat.

Teraz zmieńmy rzeczy:

interface Predicate<T> {
    function test(value:T):Bool;
}
class Collection<T> {
    /* a lot of code we don't care about now */
    public function filter(predicate:Predicate<T>):Collection<T> { 
         //build a new collection with all elements e, such that predicate.test(e) == true
    }
    public function remove(e:T):Bool {
         //removes an element from this collection, if contained and returns true, false otherwise
    }
}

Co się zmieniło? To, co się zmieniło, polega na tym, że niezależnie od tego, jaką wartość się teraz otrzymuje filter, wyraźnie podpisano umowę o predykacie. Oczywiście złośliwi lub wyjątkowo głupi programiści tworzą implementacje interfejsu, które nie są wolne od skutków ubocznych, a zatem nie są predykatami.
Ale to, czego już nie może się wydarzyć, to to, że ktoś wiąże logiczną jednostkę danych / kodu, która błędnie zostaje zinterpretowana jako predykat ze względu na jego zewnętrzną strukturę.

Aby sformułować to, co zostało powiedziane powyżej, w kilku słowach:

  • interfejsy oznaczają używanie nominalnego pisania, a tym samym wyraźne relacje
  • delegaci oznaczają stosowanie strukturalnego pisania, a tym samym niejawne relacje

Zaletą wyraźnych relacji jest to, że możesz być ich pewien. Wadą jest to, że wymagają narzutu jawności. I odwrotnie, wadą ukrytych relacji (w naszym przypadku sygnatura jednej funkcji pasująca do pożądanej sygnatury) jest to, że tak naprawdę nie możesz być w 100% pewien, że możesz używać tego w ten sposób. Zaletą jest to, że możesz nawiązywać relacje bez wszystkich kosztów ogólnych. Możesz po prostu szybko poskładać rzeczy, ponieważ pozwala na to ich struktura. To właśnie oznacza łatwa kompozycja.
To trochę jak LEGO: Państwo może po prostu wtyczką LEGO Star Wars na postać z LEGO statek piracki, po prostu dlatego, że struktura zewnętrzna na to pozwala. Teraz możesz poczuć, że jest to okropnie złe lub może być dokładnie tym, czego chcesz. Nikt cię nie powstrzyma.

back2dos
źródło
1
Warto wspomnieć, że „jawnie implementuje interfejs” (pod „nominalnym podtypem”) nie jest tym samym, co jawna lub niejawna implementacja elementów interfejsu. W języku C # klasy muszą zawsze jawnie deklarować interfejsy, które implementują, tak jak mówisz. Jednak elementy interfejsu mogą być implementowane jawnie (np. int IList.Count { get { ... } }) Lub niejawnie ( public int Count { get { ... } }). To rozróżnienie nie ma znaczenia w tej dyskusji, ale zasługuje na wzmiankę, aby uniknąć mylących czytelników.
phoog
@ phoog: Tak, dziękuję za poprawkę. Nie wiedziałem nawet, że pierwszy był możliwy. Ale tak, sposób, w jaki język faktycznie wymusza implementację interfejsu, jest bardzo zróżnicowany. Na przykład w Objective-C ostrzeże cię tylko wtedy, gdy nie zostanie zaimplementowany.
back2dos 24.10.11
1
  1. Wzorzec projektu eventing implikuje strukturę, która obejmuje mechanizm publikowania i subskrybowania. Zdarzenia są publikowane przez źródło, każdy subskrybent otrzymuje własną kopię opublikowanego elementu i jest odpowiedzialny za swoje działania. Jest to luźno powiązany mechanizm, ponieważ wydawca nie musi nawet wiedzieć, że są tam subskrybenci. Nie mówiąc już o tym, że subskrybenci nie są obowiązkowi (nie może być żaden)
  2. kompozycja jest „łatwiejsza”, jeśli używany jest delegat w porównaniu do interfejsu. Interfejsy definiują instancję klasy jako obiekt „rodzaju procedury obsługi”, w którym jako instancja konstrukcji kompozycji wydaje się „mieć procedurę obsługi”, dlatego wygląd instancji jest mniej ograniczony przez użycie delegata i staje się bardziej elastyczny.
  3. Z poniższego komentarza wynika, że ​​muszę poprawić swój post, zobacz to w celach informacyjnych: http://bytes.com/topic/c-sharp/answers/252309-interface-vs-delegate
Carlo Kuip
źródło
1
3. Dlaczego delegaci mieliby wymagać większej obsady i dlaczego ściślejsze połączenie jest korzystne? 4. Nie musisz używać zdarzeń, aby używać delegatów, a w przypadku delegatów jest to tak jak trzymanie metody - wiesz, że jest to typ zwracany i typy argumentów. Ponadto, dlaczego metoda zadeklarowana w interfejsie miałaby łatwiej obsługiwać błędy niż metoda dołączona do delegata?
Yam Marcovic,
W przypadku tylko specyfikacji delegata masz rację. Jednak w przypadku obsługi zdarzeń (przynajmniej takie jest moje odniesienie) zawsze musisz odziedziczyć pewien rodzaj eventArgs, aby działał jako kontener dla niestandardowych typów, więc zamiast być w stanie bezpiecznie przetestować wartość, którą zawsze będziesz przed rozpakowaniem wartości musisz rozpakować swój typ.
Carlo Kuip,
Powodem, dla którego uważam, że metoda zdefiniowana w interfejsie ułatwiłaby obsługę błędów, jest to, że kompilator może sprawdzać typy wyjątków zgłaszane przez tę metodę. Wiem, że nie są to przekonujące dowody, ale może należy je uznać za preferencję?
Carlo Kuip,
1
Po pierwsze rozmawialiśmy o delegatach vs interfejsach, a nie zdarzeniach vs interfejsach. Po drugie, samodzielne tworzenie zdarzenia na podstawie delegata EventHandler jest tylko konwencją i w żadnym wypadku nie jest obowiązkowe. Ale znowu, mówienie o wydarzeniach tutaj trochę mija się z celem. Jeśli chodzi o twój drugi komentarz - wyjątki nie są sprawdzane w C #. Czujesz się zagubiony w Javie (która, nawiasem mówiąc, nawet nie ma delegatów).
Yam Marcovic,
Znaleziono wątek na ten temat wyjaśniający ważne różnice: bytes.com/topic/c-sharp/answers/252309-interface-vs-delegate
Carlo Kuip