Downcasting bedeutet das Umwandeln von einer Basisklasse (oder Schnittstelle) in eine Unterklasse oder Blattklasse.
Ein Beispiel für einen Downcast könnte sein, wenn Sie von System.Object
zu einem anderen Typ wechseln.
Downcasting ist unbeliebt, vielleicht ein Code-Geruch: Objektorientierte Doktrin ist es beispielsweise vorzuziehen, virtuelle oder abstrakte Methoden zu definieren und aufzurufen, anstatt Downcasting.
- Was sind gute und angemessene Anwendungsfälle für das Downcasting? Das heißt, unter welchen Umständen ist es angemessen, heruntergestoßenen Code zu schreiben?
- Wenn Ihre Antwort "keine" lautet, warum wird Downcasting dann von der Sprache unterstützt?
c#
object-oriented
ChrisW
quelle
quelle
ToString
). Das andere Beispiel ist "Java-Container", da Java keine Generics unterstützt (was C # tut). stackoverflow.com/questions/1524197/downcast-and-upcast gibt an, was Downcasting ist , jedoch ohne Beispiele dafür, wann dies angemessen ist .before Java had support for generics
nichtbecause Java doesn't support generics
. Und amon gab Ihnen so ziemlich die Antwort - wenn das Typsystem, mit dem Sie arbeiten, zu restriktiv ist und Sie nicht in der Lage sind, es zu ändern.Antworten:
Hier sind einige Verwendungszwecke von Downcasting.
Und ich widerspreche anderen hier mit Respekt vehement , dass die Verwendung von Downcasts definitiv ein Codegeruch ist , weil ich glaube, dass es keinen anderen vernünftigen Weg gibt, die entsprechenden Probleme zu lösen.
Equals
:Clone
:Stream.EndRead/Write
:Wenn Sie behaupten, dass dies Code-Gerüche sind, müssen Sie bessere Lösungen für sie bereitstellen (auch wenn sie aufgrund von Unannehmlichkeiten vermieden werden). Ich glaube nicht, dass es bessere Lösungen gibt.
quelle
object.Equals
passt recht gut zu dieser Beschreibung (versuchen Sie, einen Gleichheitsvertrag in einer Superklasse korrekt bereitzustellen, damit die Unterklassen ihn korrekt implementieren können - es ist absolut nicht trivial). Dass wir es als Anwendungsprogrammierer nicht lösen können (in einigen Fällen können wir es vermeiden), ist ein anderes Thema.Object.Equals is horrid. Equality computation in C# is completely messed up
(Erics Kommentar zu seiner Antwort). Es ist komisch, dass Sie Ihre Meinung nicht noch einmal überdenken möchten, selbst wenn einer der Hauptdesigner der Sprache selbst Ihnen sagt, dass Sie sich irren.Ich stimme dir nicht zu. Downcasting ist sehr beliebt ; Eine große Anzahl realer Programme enthält einen oder mehrere Downcasts. Und es ist vielleicht kein Codegeruch . Es ist definitiv ein Codegeruch. Aus diesem Grund muss der Downcasting-Vorgang im Programmtext enthalten sein . Es ist so, dass Sie den Geruch leichter bemerken und die Aufmerksamkeit der Code-Überprüfung darauf richten können.
In allen Fällen, in denen:
Wenn Sie ein Programm kostengünstig umgestalten können, so dass entweder der Laufzeit-Typ vom Compiler abgeleitet werden kann, oder das Programm umgestalten können, damit Sie nicht die Fähigkeit des weiter abgeleiteten Typs benötigen, dann tun Sie dies. Downcasts wurden zu der Sprache hinzugefügt, wenn es schwierig und teuer ist , das Programm so umzugestalten.
C # wurde von pragmatischen Programmierern erfunden, die Jobs zu erledigen haben, für pragmatische Programmierer, die Jobs zu erledigen haben. Die C # -Designer sind keine OO-Puristen. Und das C # -System ist nicht perfekt; Designbedingt werden die Einschränkungen, die für den Laufzeittyp einer Variablen gelten können, unterschätzt.
Downcasting ist auch in C # sehr sicher. Wir haben eine starke Garantie, dass der Downcast zur Laufzeit überprüft wird. Wenn dies nicht möglich ist, wird das Programm das Richtige tun und stürzt ab. Das ist wunderbar; Wenn sich herausstellt, dass Ihr 100% korrektes Verständnis der Typensemantik zu 99,99% korrekt ist, funktioniert Ihr Programm zu 99,99% und stürzt den Rest der Zeit ab, anstatt sich unvorhersehbar zu verhalten und Benutzerdaten in 0,01% der Fälle zu beschädigen .
ÜBUNG: Es gibt mindestens eine Möglichkeit, in C # einen Downcast zu erstellen, ohne einen expliziten Umwandlungsoperator zu verwenden. Können Sie sich solche Szenarien vorstellen? Da es sich auch um potenzielle Code-Gerüche handelt, welche Designfaktoren sind Ihrer Meinung nach für das Design eines Features verantwortlich, das einen Absturz nach unten verursachen kann, ohne dass eine Manifestierung im Code vorgenommen wird?
quelle
Object.Equals
ist schrecklich . Die Gleichheitsberechnung in C # ist völlig durcheinander , viel zu durcheinander, um es in einem Kommentar zu erklären. Es gibt so viele Möglichkeiten, Gleichheit darzustellen. es ist sehr einfach, sie inkonsistent zu machen; es ist sehr einfach, in der Tat erforderlich , die Eigenschaften eines Gleichheitsoperator (Symmetrie, Reflexivität und Transitivität) erforderlich zu verletzen. Würde ich heute ein Typensystem von Grund auf neu entwerfen, gäbe es überhaupt keinEquals
(oderGetHashcode
oderToString
)Object
.Ereignishandler haben normalerweise die Signatur
MethodName(object sender, EventArgs e)
. In einigen Fällen ist es möglich, das Ereignis ohne Rücksicht auf den Typsender
oder sogar ohne Verwendungsender
zu behandeln, in anderen Fällensender
muss es jedoch auf einen spezifischeren Typ umgewandelt werden, um das Ereignis zu behandeln.Beispielsweise haben Sie möglicherweise zwei
TextBox
s und möchten einen einzelnen Delegaten verwenden, um ein Ereignis von jedem von ihnen zu behandeln. Dann müssten Siesender
in eineTextBox
umwandeln, damit Sie auf die erforderlichen Eigenschaften zugreifen können, um das Ereignis zu behandeln:Ja, in diesem Beispiel können Sie eine eigene Unterklasse erstellen, für
TextBox
die dies intern durchgeführt wurde. Nicht immer praktisch oder möglich.quelle
TextChanged
Veranstaltung ist Mitglied vonControl
- Ich frage mich, warumsender
nicht TypControl
statt Typobject
? Ich frage mich auch, ob das Framework (theoretisch) das Ereignis für jede Unterklasse neu definiert haben könnte (so dass z. B.TextBox
eine neue Version des EreignissesTextBox
als Absendertyp verwendet werden könnte), für die Downcasting erforderlich ist (oder nicht). zuTextBox
) innerhalb derTextBox
Implementierung (um den neuen Ereignistyp zu implementieren), aber es würde vermieden, dass der Ereignishandler des Anwendungscodes heruntergestuft wird.TextBox sender
statt mit einem zu beginnen,object sender
ohne den Code übermäßig zu komplizieren, wäre dies sicherlich der Fall.Sie brauchen Downcasting, wenn Ihnen etwas einen Supertyp gibt, und Sie müssen ihn je nach Subtyp unterschiedlich behandeln.
Nein, das riecht nicht gut. In einer perfekten Welt
Donation
hätte eine abstrakte MethodegetValue
und jeder implementierende Subtyp hätte eine entsprechende Implementierung.Aber was ist, wenn diese Klassen aus einer Bibliothek stammen, die Sie nicht ändern können oder wollen? Dann gibt es keine andere Möglichkeit.
quelle
dynamic
Schlüsselwort, mit dem das gleiche Ergebnis erzielt wird.void accept(IDonationVisitor visitor)
, die ihre Unterklassen durch Aufrufen bestimmter (z. B. überladener) Methoden von implementierenIDonationVisitor
.dynamic
Schlüsselwort tun .dynamic
noch downcasting , nur langsamer.Hinzufügen zu Eric Lipperts Antwort, da ich keinen Kommentar abgeben kann ...
Sie können das Downcasting häufig vermeiden, indem Sie eine API für die Verwendung von Generika umgestalten. Aber Generika wurden erst die Version 2.0 der Sprache hinzugefügt . Selbst wenn Ihr eigener Code keine alten Sprachversionen unterstützen muss, werden Sie möglicherweise Legacy-Klassen verwenden, die nicht parametrisiert sind. Zum Beispiel kann eine Klasse so definiert werden, dass sie eine Nutzlast vom Typ trägt
Object
, die Sie gezwungenermaßen in den Typ umwandeln müssen, von dem Sie wissen, dass er tatsächlich ist.quelle
Es gibt einen Kompromiss zwischen statischen und dynamisch getippten Sprachen. Durch die statische Typisierung erhält der Compiler eine Vielzahl von Informationen, um relativ starke Garantien für die Sicherheit von (Teilen von) Programmen zu geben. Dies ist jedoch mit Kosten verbunden, da Sie nicht nur wissen müssen, dass Ihr Programm korrekt ist, sondern auch den entsprechenden Code schreiben müssen, um den Compiler davon zu überzeugen, dass dies der Fall ist. Mit anderen Worten, Ansprüche zu erheben ist einfacher als sie zu beweisen.
Es gibt "unsichere" Konstrukte, mit deren Hilfe Sie dem Compiler unbewiesene Aussagen machen können . Zum Beispiel bedingungslose Anrufe an
Nullable.Value
, bedingungslose Unterdrückung,dynamic
Objekte usw. Sie ermöglichen es Ihnen, eine Behauptung ("Ich behaupte, dass ein Objekta
einString
, wenn ich mich irre, wirf einInvalidCastException
") geltend zu machen, ohne dass Sie dies beweisen müssen. Dies kann in Fällen hilfreich sein, in denen der Nachweis erheblich schwieriger ist als der Wert.Missbrauch ist riskant, weshalb die explizite Downcast-Notation existiert und obligatorisch ist. Es ist syntaktisches Salz , das die Aufmerksamkeit auf eine unsichere Operation lenken soll. Die Sprache hätte mit implizitem Downcasting implementiert werden können (wobei der abgeleitete Typ eindeutig ist), aber das würde diese unsichere Operation verbergen, was nicht wünschenswert ist.
quelle
Downcasting wird aus mehreren Gründen als schlecht angesehen. In erster Linie denke ich, weil es Anti-OOP ist.
OOP würde es wirklich mögen, wenn Sie nie niedergeschlagen werden müssten, da Polymorphismus bedeutet, dass Sie nicht gehen müssen
du tust es einfach
und der Code macht automatisch das Richtige.
Es gibt einige "harte" Gründe, nicht niederzuschlagen:
Aber die Alternative zum Downcasting in einigen Szenarien kann ziemlich hässlich sein.
Das klassische Beispiel ist die Nachrichtenverarbeitung, bei der Sie die Verarbeitungsfunktion nicht zum Objekt hinzufügen, sondern im Nachrichtenprozessor behalten möchten. Ich habe dann eine Tonne
MessageBaseClass
in einem Array zu verarbeiten, aber ich brauche auch jede nach Subtyp für die korrekte Verarbeitung.Sie können Double Dispatch verwenden, um das Problem zu umgehen ( https://en.wikipedia.org/wiki/Double_dispatch ) ... Aber das hat auch seine Probleme. Oder Sie können einfach einen einfachen Code unter dem Risiko dieser schwerwiegenden Gründe herunterschleudern.
Dies war jedoch, bevor die Generika erfunden wurden. Jetzt können Sie das Downcasting vermeiden, indem Sie einen Typ angeben, in dem die später angegebenen Details enthalten sind.
MessageProccesor<T>
Sie können die Methodenparameter und Rückgabewerte nach dem angegebenen Typ variieren, bieten jedoch weiterhin allgemeine FunktionenNatürlich zwingt Sie niemand, OOP-Code zu schreiben, und es gibt viele Sprachfunktionen, die bereitgestellt werden, die jedoch verpönt sind, wie z. B. Reflexion.
quelle
static_cast
stattdessendynamic_cast
.lParam
auf etwas Bestimmtes zu werfen . Ich bin mir jedoch nicht sicher, ob das ein gutes C # -Beispiel ist.static_cast
ist immer schnell ... aber es ist nicht garantiert, dass es nicht auf undefinierte Weise versagt, wenn Sie einen Fehler machen und der Typ nicht Ihren Erwartungen entspricht.Stellen Sie sich zusätzlich zu allen zuvor genannten eine Tag-Eigenschaft vor, die vom Typ object ist. Auf diese Weise können Sie ein Objekt Ihrer Wahl in einem anderen Objekt speichern und später bei Bedarf verwenden. Sie müssen diese Eigenschaft niederwerfen.
Im Allgemeinen ist das Nichtgebrauchen von etwas bis heute nicht immer ein Hinweis auf etwas Nutzloses ;-)
quelle
Control
(anstatt dieobject Tag
Eigenschaft zu verwenden), wäre, eine zu erstellen,Dictionary<Control, string>
in der die Beschriftung für jedes Steuerelement gespeichert wird.Tag
eine öffentliche Eigenschaft einer .Net-Framework-Klasse ist, was zeigt, dass Sie sie (mehr oder weniger) verwenden sollen.System.Collections.ArrayList
) oder anderweitig veraltet (etwaIEnumerator.Reset
) sind.Der häufigste Einsatz von Downcasting in meiner Arbeit ist darauf zurückzuführen, dass ein Idiot das Substitutionsprinzip von Liskov verletzt.
Stellen Sie sich vor, es gibt eine Schnittstelle in einer Drittanbieter-Bibliothek.
Und 2 Klassen, die es implementieren.
Bar
undQux
.Bar
ist gut erzogen.Ist
Qux
aber nicht gut erzogen.Nun ... jetzt habe ich keine Wahl. Ich muss check und (möglicherweise) downcast eingeben, um zu vermeiden, dass mein Programm abstürzt.
quelle
var qux = foo as Qux;
.Ein weiteres Beispiel aus der Praxis sind WPFs
VisualTreeHelper
. Es verwendet Downcast zum CastingDependencyObject
aufVisual
undVisual3D
. Zum Beispiel VisualTreeHelper.GetParent . Der Downcast erfolgt in VisualTreeUtils.AsVisualHelper :quelle