W przeciwieństwie do języka Java, dlaczego C # domyślnie traktuje metody jako funkcje niewirtualne? Czy bardziej prawdopodobne jest, że będzie to problem z wydajnością, a nie inne możliwe wyniki?
Przypomina mi się przeczytanie akapitu Andersa Hejlsberga o kilku zaletach, jakie przynosi istniejąca architektura. Ale co z efektami ubocznymi? Czy to naprawdę dobry kompromis, aby domyślnie mieć metody niewirtualne?
c#
java
.net
virtual-functions
Burcu Dogan
źródło
źródło
this
odwołanie ma wartość null. Więcej informacji znajdziesz tutaj .Odpowiedzi:
Klasy powinny być zaprojektowane do dziedziczenia, aby móc z niego skorzystać. Posiadanie metod
virtual
domyślnie oznacza, że każdą funkcję w klasie można odłączyć i zastąpić inną, co nie jest dobrą rzeczą. Wiele osób uważa nawet, że zajęcia powinny byćsealed
domyślne.virtual
metody mogą mieć również niewielki wpływ na wydajność. Jednak prawdopodobnie nie jest to główny powód.źródło
callvirt
prostącall
w kodzie IL (lub nawet dalej w dół, gdy JITting). Java HotSpot robi to samo. Reszta odpowiedzi jest trafna.Dziwię się, że wydaje się, że istnieje taki konsensus, że domyślnie niewirtualny jest właściwym sposobem robienia rzeczy. Zejdę po drugiej - myślę pragmatycznie - stronie ogrodzenia.
Większość uzasadnień brzmi dla mnie jak stary argument „Jeśli damy ci siłę, możesz się skrzywdzić”. Od programistów ?!
Wydaje mi się, że programista, który nie wiedział wystarczająco dużo (lub nie miał wystarczająco dużo czasu), aby zaprojektować swoją bibliotekę pod kątem dziedziczenia i / lub rozszerzalności, jest koderem, który stworzył dokładnie taką bibliotekę, którą prawdopodobnie będę musiał naprawić lub ulepszyć - dokładnie biblioteka, w której najbardziej przydatna byłaby możliwość nadpisania.
Ile razy musiałem napisać brzydki, desperacki kod obejścia (lub zrezygnować z użycia i wprowadzić własne alternatywne rozwiązanie), ponieważ nie mogę nadpisać daleko, znacznie przewyższa liczbę razy, kiedy byłem kiedykolwiek ugryziony ( np. w Javie), nadpisując, gdy projektant mógł nie rozważyć, że mogę.
Domyślnie niewirtualny utrudnia mi życie.
AKTUALIZACJA: Wskazano [całkiem poprawnie], że tak naprawdę nie odpowiedziałem na pytanie. A więc - i przepraszam za spóźnienie ....
Chciałem móc napisać coś zwięzłego, np. „C # domyślnie implementuje metody jako niewirtualne, ponieważ podjęto złą decyzję, która ceniła programy bardziej niż programiści”. (Myślę, że można to nieco uzasadnić na podstawie niektórych innych odpowiedzi na to pytanie - takich jak wydajność (przedwczesna optymalizacja, ktoś?) Lub gwarantowanie zachowania klas).
Jednak zdaję sobie sprawę, że przedstawiłbym tylko swoją opinię, a nie ostateczną odpowiedź, której pragnie Stack Overflow. Z pewnością, pomyślałem, na najwyższym poziomie ostateczna (ale nieprzydatna) odpowiedź brzmi:
Domyślnie nie są wirtualne, ponieważ projektanci języka musieli podjąć decyzję i właśnie to wybrali.
Teraz domyślam się, że z dokładnego powodu, dla którego podjęli tę decyzję, nigdy ... och, czekaj! Zapis rozmowy!
Wydawałoby się więc, że odpowiedzi i komentarze tutaj dotyczące niebezpieczeństw związanych z zastępowaniem interfejsów API i potrzeby jawnego projektowania dziedziczenia są na właściwej drodze, ale wszystkim brakuje ważnego aspektu czasowego: głównym zmartwieniem Andersa było utrzymanie niejawnych klas lub API kontrakt między wersjami . Myślę, że bardziej interesuje go pozwolenie platformie .Net / C # na zmianę pod kodem, niż zmiana kodu użytkownika na platformie. (A jego „pragmatyczny” punkt widzenia jest dokładnym przeciwieństwem mojego, ponieważ patrzy z drugiej strony).
(Ale czy nie mogli po prostu wybrać domyślnie wirtualnego, a następnie posypać „ostatecznym” kodem? Może to nie to samo ... a Anders jest wyraźnie mądrzejszy ode mnie, więc pozwolę mu kłamać.)
źródło
virtual
… dodatek programu Visual Studio?Ponieważ zbyt łatwo jest zapomnieć, że metoda może zostać zastąpiona, a nie zaprojektowana pod tym kątem. C # sprawia, że myślisz, zanim zrobisz to wirtualnie. Myślę, że to świetna decyzja projektowa. Niektórzy ludzie (na przykład Jon Skeet) powiedzieli nawet, że klasy powinny być domyślnie zapieczętowane.
źródło
Podsumowując to, co powiedzieli inni, jest kilka powodów:
1 - W języku C # jest wiele elementów składni i semantyki, które pochodzą bezpośrednio z C ++. Fakt, że metody domyślnie nie były wirtualne w C ++, wpłynął na C #.
2- Posiadanie każdej metody jako domyślnej wirtualnej jest problemem wydajnościowym, ponieważ każde wywołanie metody musi używać wirtualnej tabeli obiektu. Co więcej, to mocno ogranicza zdolność kompilatora Just-In-Time do wbudowywania metod i wykonywania innych rodzajów optymalizacji.
3- Co najważniejsze, jeśli metody domyślnie nie są wirtualne, możesz zagwarantować zachowanie swoich klas. Gdy są one domyślnie wirtualne, na przykład w Javie, nie można nawet zagwarantować, że prosta metoda pobierająca będzie działać zgodnie z przeznaczeniem, ponieważ można ją przesłonić w celu wykonania czegokolwiek w klasie pochodnej (oczywiście można i należy metoda i / lub finał klasy).
Można by się zastanawiać, jak wspomniał Zifre, dlaczego język C # nie poszedł o krok dalej i domyślnie zapieczętował klasy. To część całej debaty o problemach dziedziczenia implementacji, co jest bardzo interesującym tematem.
źródło
C # jest pod wpływem języka C ++ (i nie tylko). C ++ domyślnie nie włącza dynamicznego wysyłania (funkcji wirtualnych). Jednym (dobrym?) Argumentem przemawiającym za tym jest pytanie: „Jak często implementujesz klasy, które są członkami hierarchii klas?”. Innym powodem, dla którego należy unikać domyślnego włączania dynamicznego wysyłania, jest zużycie pamięci. Klasa bez wskaźnika wirtualnego (vpointer) wskazującego na wirtualną tabelę jest oczywiście mniejsza niż odpowiadająca jej klasa z włączonym późnym wiązaniem.
Kwestia wydajności nie jest łatwa do powiedzenia „tak” lub „nie”. Powodem tego jest kompilacja Just In Time (JIT), która jest optymalizacją czasu wykonywania w języku C #.
Kolejne, podobne pytanie dotyczące „ szybkości połączeń wirtualnych ”
źródło
Prosty powód to koszt projektu i utrzymania, oprócz kosztów wydajności. Metoda wirtualna ma dodatkowy koszt w porównaniu z metodą niewirtualną, ponieważ projektant klasy musi zaplanować, co się stanie, gdy metoda zostanie zastąpiona przez inną klasę. Ma to duży wpływ, jeśli spodziewasz się, że określona metoda zaktualizuje stan wewnętrzny lub będzie miało określone zachowanie. Teraz musisz zaplanować, co się stanie, gdy klasa pochodna zmieni to zachowanie. W takiej sytuacji znacznie trudniej jest napisać niezawodny kod.
Dzięki metodzie niewirtualnej masz pełną kontrolę. Wszystko, co idzie nie tak, jest winą oryginalnego autora. Kod jest znacznie łatwiejszy do zrozumienia.
źródło
Gdyby wszystkie metody C # były wirtualne, to vtbl byłby znacznie większy.
Obiekty C # mają tylko metody wirtualne, jeśli klasa ma zdefiniowane metody wirtualne. Prawdą jest, że wszystkie obiekty mają informacje o typie, które zawierają odpowiednik vtbl, ale jeśli nie zdefiniowano żadnych metod wirtualnych, będą obecne tylko podstawowe metody Object.
@Tom Hawtin: Prawdopodobnie dokładniej jest powiedzieć, że C ++, C # i Java pochodzą z rodziny języków C :)
źródło
Pochodząc z tła perla, myślę, że C # przypieczętował zagładę każdego programisty, który mógł chcieć rozszerzyć i zmodyfikować zachowanie klasy bazowej za pomocą metody niewirtualnej bez zmuszania wszystkich użytkowników nowej klasy do zdawania sobie sprawy z potencjalnie za kulisami Detale.
Rozważmy metodę Add klasy List. Co by się stało, gdyby programista chciał zaktualizować jedną z kilku potencjalnych baz danych za każdym razem, gdy dana lista jest dodawana do? Gdyby opcja „Add” była domyślnie wirtualna, programista mógłby opracować klasę „BackedList”, która zastąpiłaby metodę „Add” bez wymuszania na całym kodzie klienta, że jest to „BackedList” zamiast zwykłej „Listy”. Ze względów praktycznych „BackedList” można traktować jako kolejną „Listę” z kodu klienta.
Ma to sens z punktu widzenia dużej klasy głównej, która może zapewniać dostęp do jednego lub więcej składników listy, które same są obsługiwane przez jeden lub więcej schematów w bazie danych. Biorąc pod uwagę, że metody C # nie są domyślnie wirtualne, lista dostarczona przez główną klasę nie może być prostym IEnumerable lub ICollection ani nawet wystąpieniem List, ale zamiast tego musi być anonsowana klientowi jako „BackedList”, aby zapewnić, że nowa wersja operacji „Dodaj” jest wywoływana w celu zaktualizowania poprawnego schematu.
źródło
B
jestA
. JeśliB
wymaga czegoś innego,A
to nie jestA
. Uważam, że nadrzędność jako umiejętność w samym języku jest wadą konstrukcyjną. Jeśli potrzebujesz innejAdd
metody, twoja klasa kolekcji nie jestList
. Próba stwierdzenia, że to udawanie. Właściwe podejście to kompozycja (a nie udawanie). To prawda, że cały framework jest zbudowany na nadrzędnych możliwościach, ale po prostu mi się to nie podoba.Z pewnością nie jest to kwestia wydajności. Interpreter Javy firmy Sun używa tego samego kodu do wysyłania (
invokevirtual
kodu bajtowego), a HotSpot generuje dokładnie ten sam kod niezależnie od tego,final
czy nie. Uważam, że wszystkie obiekty C # (ale nie struktury) mają metody wirtualne, więc zawsze będziesz potrzebowaćvtbl
identyfikacji klasy / runtime. C # to dialekt „języków podobnych do języka Java”. Sugerowanie, że pochodzi z C ++, nie jest do końca uczciwe.Istnieje pomysł, że powinieneś „zaprojektować do dziedziczenia albo tego zabronić”. To brzmi jak świetny pomysł, aż do momentu, gdy masz poważne uzasadnienie biznesowe, które trzeba szybko naprawić. Być może dziedziczenie po kodzie, nad którym nie masz kontroli.
źródło
final
skutecznymifinal
jest banalna. Warto również zauważyć, że może wykonywać bimorficzne inlining, czyli metody inline z dwiema różnymi implementacjami.