Dlaczego „void” nie jest dozwolone jako typ ogólny w C #

53

Jakie były decyzje projektowe, które przemawiały za tym, że voidnie można ich zbudować i że nie można ich uznać za rodzaj ogólny? W końcu jest to po prostu specjalny pusty structi uniknąłby całkowitej PITA posiadania odrębnych Funci Actiondelegatów.

(C ++ pozwala na jawne voidzwroty i pozwala voidjako parametr szablonu)

Thomas Owens
źródło
13
@Oded Biorąc pod uwagę, że Eric Lippert ma konto i uczestniczy w programistach , myślę, że właśnie to zrobił.
Thomas Owens
2
@BenVoigt Niekoniecznie wymaga wiedzy pojedynczej osoby, chociaż jest tu jedna osoba, która (łatwo zakładam, że) może udzielić jednej właściwej odpowiedzi. Każdy, kto zna specyfikację, może prawdopodobnie odpowiedzieć na pytanie, zwłaszcza jeśli ma wiedzę na temat projektowania języka programowania. Jest to również interesujące pytanie dotyczące projektowania / implementacji języka, które jest na ten temat.
Thomas Owens
6
@BenVoigt: Dlaczego tak bardzo chcesz mieć idealnie ważne i interesujące pytanie zamknięte bez uzasadnionego powodu, inne, aby zaspokoić swoją własną pedanterię? Przeszukałem i nie mogłem znaleźć odpowiedzi; może ktoś tutaj przeczytał post na blogu, może ludzie, którzy podjęli decyzję, chcą odpowiedzieć na pytanie, może ktoś porozmawiał z osobami, które podjęły decyzję w tej sprawie i same zadały pytanie i mogą przekazać odpowiedź. Przestałem publikować posty na tej stronie i SO, ponieważ ludzie wydają się bardziej zainteresowani zamknięciem pytania niż udzieleniem odpowiedzi.
4
Mógłbym tutaj zwrócić uwagę na „najważniejsze pytania” i odpowiedzi, a zwłaszcza na SO mają formę „co oznacza ta składnia?” Te pytania prawie nigdy się nie zamykają, ponieważ każdy może nakładać i przedstawiać definicje stałych poinerów do stałych obiektów. Bardziej interesujące pytania, które leżą na uboczu, zostają wyrzucone ze strony z jakiegoś pedantycznego powodu: poważnie, to gdzieś tylko kilka kilobajtów w hurtowni danych. Czy wszyscy możemy po prostu zrelaksować się i zaakceptować naukę, a nie narzucać arbitralnie interpretowane reguły, które służą bardzo mało celowi?
2
@ThomasOwens: Jestem w pewnym sensie bardziej aktywny na tej stronie niż na SO. Na tej stronie zamieszczam odpowiedź na około jedno pytanie na każde 300. Na SO przesyłam odpowiedź na około jedno pytanie na każde 1500. Różnica w samej liczbie odpowiedzi jest spowodowana znacznie większą liczbą możliwości odpowiadanie na pytania C # na SO.
Eric Lippert,

Odpowiedzi:

69

Podstawowym problemem związanym z „void” jest to, że nie oznacza to tego samego, co każdy inny typ zwrotu. „void” oznacza „jeśli ta metoda zwróci, to nie zwróci żadnej wartości”. Nie jest zerem; null jest wartością. Nie zwraca żadnej wartości.

To naprawdę psuje system typów. System typów jest zasadniczo systemem do dokonywania logicznych dedukcji dotyczących tego, jakie operacje są poprawne dla poszczególnych wartości; nieważna metoda zwracająca wartość nie zwraca wartości, więc pytanie „jakie operacje są poprawne w tej sprawie?” nie ma żadnego sensu. Nie ma „rzeczy”, aby istniała operacja, ważna lub nieprawidłowa.

Co więcej, to zepsuło środowisko uruchomieniowe coś gwałtownego. Środowisko wykonawcze .NET jest implementacją wirtualnego systemu wykonawczego, który jest określony jako maszyna stosowa. Oznacza to maszynę wirtualną, w której wszystkie operacje są scharakteryzowane pod względem ich wpływu na stos oceny. (Oczywiście w praktyce maszyna zostanie zaimplementowana na maszynie ze stosem i rejestrami, ale wirtualny system wykonawczy przyjmuje tylko stos.) Efekt wywołania metody void jest zasadniczoinny niż efekt wywołania metody innej niż void; metoda nie void zawsze kładzie coś na stosie, co może wymagać oderwania. Metoda void nigdy nie kładzie czegoś na stosie. Dlatego kompilator nie może traktować metod void i non-void tak samo w przypadku, gdy zwracana wartość metody jest ignorowana; jeśli metoda jest nieważna, nie ma wartości zwracanej, więc nie może być pop.

Z tych wszystkich powodów „void” nie jest typem, który można utworzyć; nie ma żadnych wartości , o to właśnie chodzi. Nie można go przekształcić w obiekt, a metody zwracania pustek nigdy nie można nigdy traktować polimorficznie za pomocą metody nie zwracającej pustki, ponieważ powoduje to uszkodzenie stosu!

Dlatego void nie może być użyty jako argument typu, co jest wstydem, jak zauważasz. Byłoby to bardzo wygodne.

Z perspektywy czasu byłoby lepiej dla wszystkich zainteresowanych, gdyby zamiast niczego metoda zwracania pustek automatycznie zwracała „Unit”, magiczny typ odniesienia singletonu. Wiedziałbyś wtedy, że każde wywołanie metody umieszcza coś na stosie , wiedziałbyś, że każde wywołanie metody zwraca coś, co można przypisać do zmiennej typu obiektu , i oczywiście Unit może być użyty jako argument typu , więc byłoby nie trzeba mieć osobnych typów delegatów Action i Func. Niestety, nie w tym świecie się znajdujemy.

Aby uzyskać więcej przemyśleń w tym duchu, zobacz:

Eric Lippert
źródło
C ++ obsługuje go bez konieczności używania magicznego typu singleton. Ale zakładam, że ma to związek z C ++ wykorzystującym zupełnie inny styl „ogólny” niż C #.
Winston Ewert,
4
@WinstonEwert - Jestem prawie pewien, że C ++ w ogóle nie obsługuje generycznych, ale ma szablony, które są zasadniczo różne. Rozumiem, że przy użyciu szablonów kompilator wykonuje wyszukiwanie globalne i zastępuje je parametrami typu, ale w przypadku typów ogólnych system typów tworzy odpowiedni typ. Myślę też, że dlatego błędy szablonu C ++ były tak tępe. Jestem pewien, że Eric mnie poprawi, jeśli powiem coś nie tak
Adam Rackis,
1
Myślę, że wszystkie struktury są obsługiwane w czasie kompilacji, aby uzyskać odpowiednie umieszczenie na stosie oraz w innych miejscach, w których są wstawiane. Dlatego Voidpowinno być ok, aby było przekazywane jako nieskończona ilość argumentów wewnątrz 0 bajtów stosu. Gdy jakakolwiek struktura wymaga konwersji objectlub wywołania metody (w celu uzyskania this), jest umieszczana w ramce z umieszczaniem na stercie z Typeodniesieniem wstawionym przed polami pamięci (dla wielu implementacji CLR), a zatem może być poprawnie obsługiwana. Jak dla mnie typ to tylko grupowanie obiektów, które można rozróżnić za pomocą niektórych cech (pól). Voidjest tylko jednym przedmiotem.
tylko
1
@ OlivierJacot-Descombes: Gdyby voidbył pustym typem wartości, wówczas instrukcja ta przełożyłaby się na zero instrukcji kodu maszynowego. Niezbyt przydatne dla typu zakodowanego na stałe Void, ale przydatne w przypadkach, gdy typ ma wiele ogólnych parametrów, ale niektóre nie są potrzebne w niektórych przypadkach.
supercat
2
@EricLippert: Prawidłowa obsługa zwracanych wartości typu wartości wymagałaby możliwości usunięcia zmiennej liczby słów ze stosu. Czy wystąpiłaby jakaś podstawowa trudność z dopuszczeniem, aby liczba słów wynosiła zero? Jeśli dzwoniący jest odpowiedzialny za przydzielenie miejsca i przekazanie byref, system typów musiałby rozpoznać, że void byref nie ma znaczenia i być może nie było to warte wysiłku, ale nie widzę żadnego „podstawowego” problemu .
supercat,
9

Od: http://connect.microsoft.com/VisualStudio/feedback/details/94226/allow-void-to-be-parameter-of-generic-class-if-not-in-c-then-just-in- środowisko uruchomieniowe

W rzeczywistości jest to zgodne z projektem - nie zezwalamy na żadne wystąpienie typu void (wraz z wieloma innymi typami w środowisku wykonawczym). Nie możesz również utworzyćInstance void lub void []. Zatkaliśmy tego typu otwory dla bezpieczeństwa.

Nie mogę z mojego życia zobaczyć, jak pustka jest dziurą w zabezpieczeniach, ale ... tak to mówi.

Winston Ewert
źródło