Przeglądałem ten post na blogu i miałem następujące pytania:
- Dlaczego potrzebujemy
new
słowa kluczowego, czy tylko po to, aby określić, że metoda klasy bazowej jest ukrywana. Mam na myśli, dlaczego tego potrzebujemy? Jeśli nie używamyoverride
słowa kluczowego, czy nie ukrywamy metody klasy bazowej? - Dlaczego domyślnie w C # ukrywa się i nie zastępuje? Dlaczego projektanci zaimplementowali to w ten sposób?
c#
overriding
Piaskownica
źródło
źródło
Odpowiedzi:
Dobre pytania. Pozwól mi je powtórzyć.
Odpowiem na to pytanie na przykładzie. Masz interfejs z CLR v1:
interface IEnumerable { IEnumerator GetEnumerator(); }
Wspaniały. Teraz w CLR v2 masz generyczne i myślisz „stary, gdybyśmy tylko mieli generyczne w v1, zrobiłbym to jako ogólny interfejs. Ale nie zrobiłem. Powinienem stworzyć coś kompatybilnego z tym teraz, co jest ogólne, więc Korzystam z zalet leków generycznych bez utraty wstecznej zgodności z kodem, który oczekuje od IEnumerable. ”
interface IEnumerable<T> : IEnumerable { IEnumerator<T> .... uh oh
Co zamierzasz wywołać metodę GetEnumerator
IEnumerable<T>
? Pamiętaj, chcesz , aby ukrywał GetEnumerator w nieogólnym interfejsie podstawowym. Ty nigdy chcesz, aby ta rzecz została wywołana, chyba że jesteś wyraźnie w sytuacji kompatybilności wstecznej.Samo to uzasadnia ukrywanie metody. Więcej przemyśleń na temat uzasadnień ukrywania metod w moim artykule na ten temat .
Ponieważ chcemy zwrócić Twoją uwagę, że coś ukrywasz i możesz to zrobić przypadkowo. Pamiętaj, że możesz przypadkowo ukryć coś z powodu edycji klasy bazowej wykonanej przez kogoś innego, a nie przez edycję klasy pochodnej.
Ten sam powód. Być może przypadkowo coś ukrywasz, ponieważ właśnie wybrałeś nową wersję klasy bazowej. Dzieje się to cały czas. FooCorp tworzy klasę bazową B. BarCorp tworzy klasę pochodną D za pomocą metody Bar, ponieważ ich klienci lubią tę metodę. FooCorp widzi to i mówi hej, to dobry pomysł, możemy umieścić tę funkcjonalność w klasie bazowej. Robią to i wysyłają nową wersję Foo.DLL, a kiedy BarCorp odbierze nową wersję, byłoby miło, gdyby powiedziano im, że ich metoda ukrywa teraz metodę klasy bazowej.
Chcemy, aby ta sytuacja była ostrzeżeniem, a nie błędem, ponieważ popełnienie błędu oznacza, że jest to kolejna forma kruchego problemu z klasą bazową . Język C # został starannie zaprojektowany, aby gdy ktoś wprowadził zmianę w klasie bazowej, wpływ na kod, który używa klasy pochodnej, został zminimalizowany.
Ponieważ wirtualne nadpisanie jest niebezpieczne . Wirtualne przesłonięcie umożliwia klasom pochodnym zmianę zachowania kodu, który został skompilowany w celu użycia klas podstawowych. Robienie czegoś niebezpiecznego, jak dokonywanie zmian, powinno być czymś, co robisz świadomie i celowo , a nie przez przypadek.
źródło
Jeśli metoda w klasie pochodnej jest poprzedzona słowem kluczowym new, metoda jest definiowana jako niezależna od metody w klasie bazowej
Jeśli jednak nie określisz ani new, ani zastąpień, wynikowe dane wyjściowe będą takie same, jak w przypadku określenia new, ale otrzymasz ostrzeżenie kompilatora (ponieważ możesz nie być świadomy, że ukrywasz metodę w metodzie klasy bazowej, a nawet mogłeś chcieć go zastąpić i po prostu zapomnieć o dołączeniu słowa kluczowego).
Pomaga więc uniknąć błędów i wyraźnie pokazuje, co chcesz zrobić, a także tworzy bardziej czytelny kod, dzięki czemu można go łatwo zrozumieć.
źródło
Warto zauważyć, że jedyny efekt
new
w tym kontekście jest zniesienie ostrzeżenia. Nie ma zmiany w semantyce.Zatem jedna odpowiedź brzmi: musimy
new
zasygnalizować kompilatorowi, że ukrywanie jest celowe i pozbyć się ostrzeżenia.Kolejne pytanie brzmi: jeśli nie możesz / nie możesz zastąpić metody, dlaczego miałbyś wprowadzić inną metodę o tej samej nazwie? Ponieważ ukrywanie jest w istocie konfliktem nazw. I oczywiście w większości przypadków można by tego uniknąć.
Jedynym dobrym powodem, dla którego przychodzi mi do głowy celowe ukrywanie się, jest wymuszenie nazwy przez interfejs.
źródło
W języku C # elementy członkowskie są domyślnie zapieczętowane, co oznacza, że nie można ich zastąpić (chyba że są oznaczone słowami kluczowymi
virtual
lubabstract
), a to ze względu na wydajność . Nowy modyfikator stosuje się wyraźnie ukryć odziedziczony członka.źródło
Jeśli zastępowanie było domyślne bez określania
override
słowa kluczowego, możesz przypadkowo przesłonić jakąś metodę swojej bazy tylko z powodu równości nazw.Strategia kompilatora .Net polega na emitowaniu ostrzeżeń, jeśli coś może pójść nie tak, aby być bezpiecznym, więc w tym przypadku, gdyby zastępowanie było domyślne, musiałoby być ostrzeżenie dla każdej zastępowanej metody - coś w rodzaju `` ostrzeżenie: sprawdź, czy naprawdę chcesz przekroczyć'.
źródło
Moje przypuszczenie wynikałoby głównie z dziedziczenia wielu interfejsów. Używając dyskretnych interfejsów, byłoby bardzo możliwe, że dwa różne interfejsy używają tej samej sygnatury metody. Zezwolenie na użycie
new
słowa kluczowego umożliwiłoby utworzenie tych różnych implementacji z jedną klasą, zamiast konieczności tworzenia dwóch odrębnych klas.Zaktualizowano ... Eric dał mi pomysł, jak ulepszyć ten przykład.
public interface IAction1 { int DoWork(); } public interface IAction2 { string DoWork(); } public class MyBase : IAction1 { public int DoWork() { return 0; } } public class MyClass : MyBase, IAction2 { public new string DoWork() { return "Hi"; } } class Program { static void Main(string[] args) { var myClass = new MyClass(); var ret0 = myClass.DoWork(); //Hi var ret1 = ((IAction1)myClass).DoWork(); //0 var ret2 = ((IAction2)myClass).DoWork(); //Hi var ret3 = ((MyBase)myClass).DoWork(); //0 var ret4 = ((MyClass)myClass).DoWork(); //Hi } }
źródło
new
kluczowe pozwala również zmienić podstawę dziedziczenia dla wszystkich dzieci z tego miejsca.Jak zauważono, ukrywanie metody / właściwości umożliwia zmianę rzeczy w metodzie lub właściwości, których w innym przypadku nie można by łatwo zmienić. Jedną z sytuacji, w których może to być przydatne, jest zezwolenie dziedziczonej klasie na posiadanie właściwości do odczytu i zapisu, które są tylko do odczytu w klasie bazowej. Na przykład załóżmy, że klasa bazowa ma kilka właściwości tylko do odczytu o nazwie Wartość1-Wartość40 (oczywiście prawdziwa klasa używałaby lepszych nazw). Zapieczętowany element potomny tej klasy ma konstruktora, który pobiera obiekt klasy bazowej i kopiuje stamtąd wartości; klasa nie pozwala na ich późniejszą zmianę. Inny, dziedziczony potomek deklaruje właściwości do odczytu i zapisu o nazwie Wartość1-Wartość40, które po odczytaniu zachowują się tak samo jak wersje klasy bazowej, ale po zapisaniu pozwalają na zapisanie wartości.
Jedną irytacją związaną z tym podejściem - być może ktoś może mi pomóc - jest to, że nie znam sposobu, aby zarówno przesłonić, jak i zastąpić określoną właściwość w ramach tej samej klasy. Czy któryś z języków CLR na to pozwala (używam VB 2005)? Byłoby użyteczne, gdyby obiekt klasy bazowej i jego właściwości były abstrakcyjne, ale wymagałoby to od klasy pośredniej zastąpienia właściwości Value1 do Value40, zanim klasa potomna będzie mogła je przesłonić.
źródło