Downcasting oznacza rzutowanie z klasy podstawowej (lub interfejsu) do podklasy lub klasy liścia.
Przykładem downcast może być rzutowanie z System.Object
innego typu.
Downcasting jest niepopularny, może pachnie kodem: doktryna obiektowa preferuje na przykład definiowanie i wywoływanie metod wirtualnych lub abstrakcyjnych zamiast downcastingu.
- Jakie, jeśli w ogóle, są dobre i właściwe przypadki użycia dla downcastingu? To znaczy, w jakich okolicznościach należy pisać kod, który jest w dół?
- Jeśli twoja odpowiedź brzmi „brak”, to dlaczego język obsługuje downcasting?
c#
object-oriented
ChrisW
źródło
źródło
ToString
); jego drugim przykładem są „kontenery Java”, ponieważ Java nie obsługuje generics (co robi C #). stackoverflow.com/questions/1524197/downcast-and-upcast mówi, co to jest downcasting , ale bez przykładów, kiedy jest to właściwe .before Java had support for generics
nie jestbecause Java doesn't support generics
. I amon prawie dał ci odpowiedź - kiedy system typów, z którym pracujesz, jest zbyt restrykcyjny i nie jesteś w stanie go zmienić.Odpowiedzi:
Oto niektóre właściwe zastosowania downcastingu.
I szacunkiem stanowczo nie zgadzają się z innymi, którzy mówią tu zastosowanie downcasts jest zdecydowanie zapach kod, ponieważ wierzę, nie ma innego rozsądny sposób na rozwiązanie odpowiednie problemy.
Equals
:Clone
:Stream.EndRead/Write
:Jeśli chcesz powiedzieć, że są to zapachy kodowe, musisz zapewnić im lepsze rozwiązania (nawet jeśli można ich uniknąć z powodu niedogodności). Nie wierzę, że istnieją lepsze rozwiązania.
źródło
object.Equals
pasuje do tego opisu całkiem dobrze (postaraj się poprawnie dostarczyć kontrakt równy w nadklasie, który pozwala na prawidłowe wdrożenie podklas - to absolutnie nietrywialne). To, że jako programiści aplikacji nie możemy tego rozwiązać (w niektórych przypadkach możemy tego uniknąć) to inny temat.Object.Equals is horrid. Equality computation in C# is completely messed up
(komentarz Erica do jego odpowiedzi). To zabawne, że nie chcesz ponownie rozważać swojej opinii, nawet jeśli jeden z głównych projektantów samego języka mówi ci, że się mylisz.Nie zgadzam się. Downcasting jest niezwykle popularny ; ogromna liczba programów w świecie rzeczywistym zawiera co najmniej jeden problem. I to może nie jest zapach kodu. To zdecydowanie zapach kodu. Dlatego operacja downcastingu musi być zamanifestowana w tekście programu . Dzięki temu łatwiej jest zauważyć zapach i poświęcić mu uwagę na przeglądanie kodu.
W każdych okolicznościach, gdy:
Jeśli możesz tanio refaktoryzować program, tak aby kompilator mógł wydedukować typ środowiska wykonawczego, lub refaktoryzować program, aby nie trzeba było możliwości bardziej pochodnego typu, zrób to. Do języka dodano downcasty w tych okolicznościach, w których refaktoryzacja programu jest trudna i droga .
C # został wymyślony przez pragmatycznych programistów, którzy mają zadania do wykonania, dla pragmatycznych programistów, którzy mają zadania do wykonania. Projektanci C # nie są purystami OO. A system typu C # nie jest idealny; z założenia nie docenia ograniczeń, które można nałożyć na typ środowiska wykonawczego zmiennej.
Również downcasting jest bardzo bezpieczny w C #. Mamy silną gwarancję, że downcast zostanie zweryfikowany w czasie wykonywania, a jeśli nie będzie można go zweryfikować, program wykona właściwą czynność i ulegnie awarii. To jest cudowne; oznacza to, że jeśli twoje 100% poprawne zrozumienie semantyki typów okaże się 99,99% poprawne, to twój program działa przez 99,99% czasu i zawiesza resztę czasu, zamiast zachowywać się nieprzewidywalnie i uszkadzać dane użytkownika 0,01% czasu .
ĆWICZENIE: Istnieje co najmniej jeden sposób na utworzenie downcastu w języku C # bez użycia jawnego operatora rzutowania. Czy możesz wymyślić takie scenariusze? Ponieważ są to również potencjalne zapachy kodu, jakie według Pana czynniki projektowe przyczyniły się do zaprojektowania funkcji, która może spowodować awarię w dół, bez manifestu w kodzie?
źródło
Object.Equals
jest okropny . Obliczenia równości w języku C # są całkowicie pomieszane , zdecydowanie zbyt pomieszane, aby wyjaśnić w komentarzu. Jest tyle sposobów na reprezentowanie równości; bardzo łatwo jest uczynić je niespójnymi; jest to bardzo łatwe, w rzeczywistości wymaga naruszenia właściwości wymaganych od operatora równości (symetrii, zwrotności i przechodniości). Gdybym dzisiaj projektował system typów od zera, nie byłobyEquals
(GetHashcode
lubToString
)Object
w ogóle.Procedury obsługi zdarzeń zwykle mają podpis
MethodName(object sender, EventArgs e)
. W niektórych przypadkach można obsłużyć zdarzenie bez względu na typsender
, a nawet w ogóle go nie używaćsender
, ale w innychsender
należy obsłużyć bardziej konkretny typ, aby obsłużyć zdarzenie.Na przykład możesz mieć dwa
TextBox
s i chcesz użyć jednego delegata do obsługi zdarzenia z każdego z nich. Następnie musisz rzutowaćsender
naTextBox
, aby uzyskać dostęp do niezbędnych właściwości do obsługi zdarzenia:Tak, w tym przykładzie możesz utworzyć własną podklasę,
TextBox
która zrobiła to wewnętrznie. Jednak nie zawsze jest to praktyczne lub możliwe.źródło
TextChanged
Wydarzenie jest członkiemControl
- Zastanawiam się, dlaczegosender
nie jest typuControl
zamiast typuobject
? Zastanawiam się także, czy (teoretycznie) środowisko mogło przedefiniować zdarzenie dla każdej podklasy (tak, że np.TextBox
Może mieć nową wersję zdarzenia, używającTextBox
jako typu nadawcy) ... które może (lub nie) wymagać downcastingu ( doTextBox
) wewnątrzTextBox
implementacji (w celu zaimplementowania nowego typu zdarzenia), ale uniknęłby konieczności downcastu w module obsługi zdarzeń kodu aplikacji.TextBox sender
zamiastobject sender
bez nadmiernego komplikowania kodu, jestem pewien, że tak by było.Potrzebujesz downcastingu, gdy coś daje ci nadtyp i musisz postępować inaczej w zależności od podtypu.
Nie, to nie pachnie dobrze. W idealnym świecie
Donation
istniałaby abstrakcyjna metoda,getValue
a każdy podtyp implementujący miałby odpowiednią implementację.Ale co, jeśli te klasy pochodzą z biblioteki, której nie możesz lub nie chcesz zmieniać? Wtedy nie ma innej opcji.
źródło
dynamic
słowo kluczowe, które osiąga ten sam wynik.void accept(IDonationVisitor visitor)
, które jego podklasy implementują, wywołując określone (np. przeciążone) metodyIDonationVisitor
.dynamic
słowa kluczowego.dynamic
wciąż spuszczanie, tylko wolniej.Aby dodać do odpowiedzi Erica Lipperta, ponieważ nie mogę komentować ...
Często można uniknąć downcastingu, refaktoryzując interfejs API w celu użycia ogólnych. Ale generyczne nie zostały dodane do języka aż do wersji 2.0 . Więc nawet jeśli twój własny kod nie musi obsługiwać starożytnych wersji językowych, możesz użyć starszych klas, które nie są sparametryzowane. Na przykład, można zdefiniować pewną klasę do przenoszenia ładunku typu
Object
, który musisz rzucić na typ, o którym wiesz, że tak naprawdę jest.źródło
Istnieje kompromis między językami statycznymi i dynamicznie typowanymi. Wpisywanie statyczne daje kompilatorowi wiele informacji, dzięki którym można uzyskać dość silne gwarancje bezpieczeństwa (części) programów. Jest to jednak kosztowne, ponieważ nie tylko musisz wiedzieć, że twój program jest poprawny, ale musisz napisać odpowiedni kod, aby przekonać kompilator, że tak jest. Innymi słowy, zgłaszanie roszczeń jest łatwiejsze niż ich udowodnienie.
Istnieją „niebezpieczne” konstrukcje, które pomagają w tworzeniu niepotwierdzonych twierdzeń dotyczących kompilatora. Na przykład bezwarunkowe wezwania do
Nullable.Value
, bezwarunkowe sprowadzenia,dynamic
obiekty itp. Pozwalają one na dochodzenie roszczenia („Twierdzę, że obiekta
jestString
, jeśli się mylę, rzucająInvalidCastException
”) bez konieczności jego udowodnienia. Może to być przydatne w przypadkach, gdy udowodnienie, że jest to znacznie trudniejsze niż opłacalne.Nadużywanie tego jest ryzykowne i właśnie dlatego istnieje wyraźna notacja w dół i jest obowiązkowa; to sól syntaktyczna, która ma zwrócić uwagę na niebezpieczną operację. Język mógł zostać zaimplementowany z niejawnym downcastingiem (gdzie wywnioskowany typ jest jednoznaczny), ale to ukryłoby tę niebezpieczną operację, co nie jest pożądane.
źródło
Downcasting jest uważany za zły z kilku powodów. Przede wszystkim myślę, ponieważ jest to anty-OOP.
OOP naprawdę by się to spodobało, gdybyś nigdy nie musiał być przygnębiony, ponieważ jego „raison-d'etre” polega na tym, że polimorfizm oznacza, że nie musisz iść
po prostu to robisz
a kod auto-magicznie robi właściwą rzecz.
Istnieje kilka „trudnych” powodów, aby nie spuszczać:
Ale alternatywa dla downcastingu w niektórych scenariuszach może być dość brzydka.
Klasycznym przykładem jest przetwarzanie komunikatów, w którym nie chcesz dodawać funkcji przetwarzania do obiektu, ale przechowywać go w procesorze komunikatów. Następnie mam
MessageBaseClass
do przetworzenia tonę w tablicy, ale do poprawnego przetwarzania potrzebuję każdej z nich według podtypu.Możesz użyć Double Dispatch, aby obejść problem ( https://en.wikipedia.org/wiki/Double_dispatch ) ... Ale to też ma swoje problemy. Lub możesz po prostu sprowadzić prosty kod na ryzyko tych trudnych powodów.
Ale to było zanim wynaleziono Generics. Teraz możesz uniknąć downcastingu, podając typ, w którym szczegóły zostaną określone później.
MessageProccesor<T>
może zmieniać parametry metody i zwracane wartości o określony typ, ale nadal zapewnia ogólną funkcjonalnośćOczywiście nikt nie zmusza Cię do pisania kodu OOP, a jest wiele funkcji językowych, które są dostępne, ale są niezadowolone, na przykład refleksja.
źródło
static_cast
zamiast niegodynamic_cast
.lParam
na coś konkretnego. Nie jestem jednak pewien, czy to dobry przykład w języku C #.static_cast
zawsze jest szybki ... ale nie ma gwarancji, że nie zawiedzie w nieokreślony sposób, jeśli popełnisz błąd, a typ nie jest tym, czego oczekujesz.Oprócz wszystkich wcześniejszych, wyobraź sobie właściwość Tag, która jest typu obiektowego. Umożliwia przechowywanie wybranego obiektu w innym obiekcie do późniejszego wykorzystania w razie potrzeby. Musisz sprowadzić tę właściwość.
Ogólnie rzecz biorąc, nieużywanie czegoś do dzisiaj nie zawsze oznacza coś bezużytecznego ;-)
źródło
Control
(zamiast używaniaobject Tag
właściwości) byłoby utworzenieDictionary<Control, string>
etykiety do przechowywania każdej kontrolki.Tag
jest to własność publiczna klasy frameworku .Net, która pokazuje, że (mniej więcej) powinieneś z niej korzystać.System.Collections.ArrayList
) lub w inny sposób przestarzałe (powiedzmyIEnumerator.Reset
).Najczęstszym zastosowaniem downcastingu w mojej pracy jest to, że jakiś idiota łamie zasadę substytucji Liskowa.
Wyobraź sobie, że w niektórych bibliotekach zewnętrznych jest interfejs.
I 2 klasy, które to implementują.
Bar
aQux
.Bar
jest dobrze wychowany.Ale
Qux
nie jest dobrze wychowany.Cóż ... teraz nie mam wyboru. I mają na typ kontroli i (ewentualnie) przygnębiony, aby uniknąć mój program zatrzymał się upaść.
źródło
var qux = foo as Qux;
.Innym przykładem w świecie rzeczywistym jest WPF
VisualTreeHelper
. Wykorzystuje downcast do przesyłaniaDependencyObject
doVisual
iVisual3D
. Na przykład VisualTreeHelper.GetParent . Downcast odbywa się w VisualTreeUtils.AsVisualHelper :źródło