Dlaczego zaimplementowano magiczne metody w C #?

16

W C # zacząłem widzieć wyskakujące wszystkie magiczne metody, bez tworzenia kopii zapasowej przez interfejs. Dlaczego został wybrany?

Pozwól mi wyjaśnić.

Poprzednio w języku C #, jeśli obiekt implementował IEnumerableinterfejs, byłby automatycznie iterowalny przez foreachpętlę. Ma to dla mnie sens, ponieważ jest to wspierane przez interfejs, a jeśli miałbym mieć własną Iteratorfunkcję wewnątrz iteracji klasy, mógłbym to zrobić bez obawy, że to magicznie oznaczałoby coś innego.

Teraz najwyraźniej (nie wiadomo kiedy) te interfejsy nie są już wymagane. Po prostu musi mieć odpowiednie konwersje nazw.

Innym przykładem jest spowodowanie, że każdy obiekt będzie oczekiwany dzięki metodzie o nazwie dokładnie, GetAwaiter która ma kilka specyficznych właściwości.

Dlaczego nie stworzyć interfejsu takiego jak oni IEnumerablelub INotifyPropertyChangedstatycznie poprzeć tę „magię”?

Więcej szczegółów na temat tego, co mam na myśli tutaj:

http://blog.nem.ec/2014/01/01/magic-methods-c-sharp/

Jakie są zalety i wady magicznych metod i czy jest gdziekolwiek w Internecie, gdzie mogę znaleźć informacje na temat przyczyn podjęcia tych decyzji?

Mathias Lykkegaard Lorenzen
źródło
4
Powinieneś zredagować swoją osobistą opinię, dlaczego „magia jest zła”, jeśli nie chcesz się zamknąć.
DougM
2
Jeśli chcesz wiedzieć, dlaczego Anders Hejlsberg to zrobił, musisz go zapytać. Mogę tylko powiedzieć, dlaczego tak postąpiłem, i chodzi o zgodność z poprzednimi wersjami. Metody rozszerzeń umożliwiają „fałszywe” dodawanie metod do istniejących typów, ale nie ma interfejsów rozszerzeń. Jeśli potrzebujesz interfejsu, powiedzmy, async/ await, to będzie on działał tylko z kodem, który został napisany po .NET 4.5 stał się wystarczająco rozpowszechniony, aby być realnym celem… co jest w zasadzie teraz. Ale czysto syntaktyczne tłumaczenie na wywołania metod pozwala mi dodać awaitfunkcjonalność do istniejących typów po fakcie.
Jörg W Mittag,
2
Zauważ, że jest to w zasadzie orientacja obiektowa: dopóki obiekt reaguje na odpowiednie komunikaty, jest uważany za właściwy typ.
Jörg W Mittag
7
Twoje „poprzednio” i „teraz” są zacofane - magiczna metoda została zaimplementowana jako podstawowa funkcjonalność foreachpętli na początku. Tam nigdy nie było wymagane dla obiektu w celu wdrożenia IEnumerabledo foreachdo pracy. To tylko konwencja.
Jesse C. Slicer,
2
@gbulmer to miejsce, w którym przypominam sobie pomysł z: blogs.msdn.com/b/ericlippert/archive/2011/06/30/… (i w mniejszym stopniu ericlippert.com/2013/07/22/… )
Jesse C. Slicer

Odpowiedzi:

16

Ogólnie „magiczne metody” stosuje się, gdy nie jest możliwe stworzenie interfejsu, który działałby w ten sam sposób.

Kiedy foreachpo raz pierwszy został wprowadzony w C # 1.0 (takie zachowanie z pewnością nie jest niczym nowym), musiał używać magicznych metod, ponieważ nie było żadnych leków generycznych. Dostępne opcje to:

  1. Użyj nietypowego IEnumerablei IEnumeratordziałającego z objects, co oznacza typy wartości boksu. Ponieważ iterowanie czegoś w rodzaju listy ints powinno być bardzo szybkie i na pewno nie powinno tworzyć wielu wartości w skrzyniach na śmieci, nie jest to dobry wybór.

  2. Poczekaj na leki generyczne. Oznaczałoby to prawdopodobnie opóźnienie .Net 1.0 (lub przynajmniej foreach) o więcej niż 3 lata.

  3. Użyj magicznych metod.

Wybrali więc opcję nr 3 i została z nami ze względu na kompatybilność wsteczną, chociaż od .Net 2.0, wymaganie też IEnumerable<T>by działało.


Inicjatory kolekcji mogą wyglądać inaczej dla każdego typu kolekcji. Porównaj List<T>:

public void Add(T item)

i Dictionary<TKey, TValue>:

public void Add(TKey key, TValue value)

Nie możesz mieć jednego interfejsu, który obsługiwałby tylko pierwszy formularz List<T>i tylko drugi dla Dictionary<TKey, TValue>.


Metody LINQ są zwykle implementowane jako metody rozszerzenia (tak, że może istnieć tylko jedna implementacja np. LINQ do Object dla wszystkich typów, które implementują IEnumerable<T>), co oznacza, że ​​nie można użyć interfejsu.


Dla awaitThe GetResult()sposób może powrócić albo voidlub pewnego rodzaju T. Ponownie, nie możesz mieć jednego interfejsu, który mógłby obsłużyć oba. Chociaż awaitjest częściowo oparty na interfejsie: oczekujący musi zaimplementować INotifyCompletioni może również zaimplementować ICriticalNotifyCompletion.

svick
źródło
1
Czy jesteś pewien, że „wybrali opcję nr 3” dla C # 1.0? Moim wspomnieniem jest to, że była to opcja 1. Moja pamięć to, C # twierdził, że ma znaczną przewagę nad Javą, ponieważ kompilator C # zrobił „auto-boxing” i „auto unboxing”. Twierdzono, że kod C # sprawił, że taki foreach działa znacznie lepiej niż Java częściowo z tego powodu.
żarówka
1
Dokumentacja dla foreachC # 1.2 (nic nie ma w MSDN dla C # 1.0) mówi, że wyrażenie w foreach„Ewaluuje do typu, który implementuje IEnumerablelub typu, który deklaruje GetEnumeratormetodę”. I nie jestem pewien, co masz na myśli przez to, rozpakowywanie w C # jest zawsze jednoznaczne.
svick,
1
@gbulmer A specyfikacja ECMA dla C # z grudnia 2001 r. (co musi oznaczać, że dotyczy C # 1.0) również mówi, że (§ 15.8.4): „Mówi się, że typ C jest typem kolekcji, jeśli implementuje interfejs System.IEnumerable lub wdraża wzorzec gromadzenia, spełniając wszystkie poniższe kryteria […] ”.
svick,
1
@svick foreach(T x in sequence)stosuje jawne rzutowania Tna elementy sekwencji. Więc jeśli sekwencja jest zwykła IEnumerablei Tjest typem wartości, rozpakuje się bez zapisania żadnego jawnego rzutowania w twoim kodzie. Jedna z bardziej brzydkich części C #.
CodesInChaos
@CodesInChaos „Automatyczne rozpakowywanie” brzmi dość ogólnie, nie zdawałem sobie sprawy, że odnosi się do tej konkretnej funkcji. (Chyba powinienem, skoro o tym rozmawialiśmy foreach.)
svick