Was ist eine angemessene Verwendung von Downcasting?


67

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.Objectzu 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?

5
Was Sie beschreiben, wird üblicherweise als "Downcasting" bezeichnet. Könnten Sie mit diesem Wissen zuerst selbst etwas mehr recherchieren und erklären, warum die vorhandenen Gründe, die Sie gefunden haben, nicht ausreichen? TL; DR: Downcasting unterbricht die statische Typisierung, aber manchmal ist ein Typensystem zu restriktiv. Es ist also sinnvoll, dass eine Sprache sicheres Downcasting bietet.
Amon

Meinen Sie Downcasting? Upcasting würde durch die Klassenhierarchie erfolgen, dh von Unterklasse zu Oberklasse, im Gegensatz zu Downcasting, von Oberklasse zu Unterklasse ...
Andrei Socaciu

Ich entschuldige mich: Sie haben Recht. Ich habe die Frage bearbeitet, um "upcast" in "downcast" umzubenennen.
ChrisW

@amon en.wikipedia.org/wiki/Downcasting gibt als Beispiel "In Zeichenfolge konvertieren" an (von .Net unterstützt wird die Verwendung einer virtuellen Zeichenfolge 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 .
ChrisW

4
@ ChrisW Das ist before Java had support for genericsnicht because 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.
Ordous

Antworten:


12

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:

class A
{
    // Nothing here, but if you're a C++ programmer who dislikes object.Equals(object),
    // pretend it's not there and we have abstract bool A.Equals(A) instead.
}
class B : A
{
    private int x;
    public override bool Equals(object other)
    {
        var casted = other as B;  // cautious downcast (dynamic_cast in C++)
        return casted != null && this.x == casted.x;
    }
}

Clone:

class A : ICloneable
{
    // Again, if you dislike ICloneable, that's not the point.
    // Just ignore that and pretend this method returns type A instead of object.
    public virtual object Clone()
    {
        return this.MemberwiseClone();  // some sane default behavior, whatever
    }
}
class B : A
{
    private int[] x;
    public override object Clone()
    {
        var copy = (B)base.Clone();  // known downcast (static_cast in C++)
        copy.x = (int[])this.x.Clone();  // oh hey, another downcast!!
        return copy;
    }
}

Stream.EndRead/Write:

class MyStream : Stream
{
    private class AsyncResult : IAsyncResult
    {
        // ...
    }
    public override int EndRead(IAsyncResult other)
    {
        return Blah((AsyncResult)other);  // another downcast (likely ~static_cast)
    }
}

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.


9
"Codegeruch" bedeutet nicht "dieses Design ist definitiv schlecht ", sondern "dieses Design ist verdächtig ". Dass es Sprachmerkmale gibt, die von Natur aus verdächtig sind, ist eine Frage des Pragmatismus des Sprachautors
Caleth

5
@Caleth: Und mein ganzer Punkt ist, dass diese Designs die ganze Zeit auftauchen und dass absolut nichts an den von mir gezeigten Designs von Natur aus verdächtig ist, und daher ist das Casting kein Codegeruch.
Mehrdad

4
@Caleth Ich bin anderer Meinung. Codegeruch bedeutet für mich, dass der Code schlecht ist oder zumindest verbessert werden könnte.
Konrad Rudolph

6
@Mehrdad Ihre Meinung scheint zu sein, dass Code-Gerüche die Schuld der Anwendungsprogrammierer sein müssen. Warum? Nicht jeder Codegeruch muss ein tatsächliches Problem sein oder eine Lösung haben. Ein Code-Geruch nach Martin Fowler ist einfach "eine Oberflächenangabe, die normalerweise einem tieferen Problem im System entspricht" und object.Equalspasst 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.
Voo

5
@Mehrdad Nun, mal sehen, was einer der ursprünglichen Sprachdesigner zu Equals zu sagen hat. 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.
Voo

135

Downcasting ist unbeliebt, vielleicht ein Codegeruch

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.

unter welchen umständen ist es angebracht, code zu schreiben, der downcasts?

In allen Fällen, in denen:

  • Sie verfügen über eine 100% korrekte Kenntnis eines Fakts über den Laufzeittyp eines Ausdrucks, der spezifischer ist als der Typ des Ausdrucks zur Kompilierungszeit
  • Sie müssen diese Tatsache ausnutzen, um eine Funktion des Objekts zu verwenden, die für den Kompilierungstyp nicht verfügbar ist
  • Es ist ein besserer Zeit- und Arbeitsaufwand, die Besetzung zu schreiben, als das Programm zu überarbeiten, um einen der ersten beiden Punkte zu eliminieren.

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.

warum wird downcasting von der sprache unterstützt?

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?


101
Denken Sie daran, diese Plakate in der High School an den Wänden "Was beliebt ist, ist nicht immer richtig, was richtig ist, ist nicht immer beliebt?" Sie dachten, sie wollten Sie davon abhalten, Drogen zu nehmen oder in der High School schwanger zu werden, aber sie warnten tatsächlich vor den Gefahren technischer Schulden.
CorsiKa

14
@ EricLippert, natürlich die foreach-Anweisung. Dies geschah, bevor IEnumerable generisch war, oder?
Arturo Torres Sánchez

18
Diese Erklärung machte meinen Tag. Als Lehrer werde ich oft gefragt, warum C # so oder so gestaltet ist, und meine Antworten basieren oft auf Motivationen, die Sie und Ihre Kollegen hier und in Ihren Blogs geteilt haben. Es ist oft etwas schwieriger, die Folgefrage "Aber warum ?" Zu beantworten . Und hier finde ich die perfekte Antwort: "C # wurde von pragmatischen Programmierern erfunden, die Jobs zu erledigen haben, für pragmatische Programmierer, die Jobs zu erledigen haben." :-) Danke @EricLippert!
Zano

12
Nebenbei: In meinem obigen Kommentar wollte ich natürlich sagen, dass wir einen Downcast von A nach B haben. Ich mag die Verwendung von "up" und "down" und "parent" und "child" nicht, wenn ich durch eine Klassenhierarchie navigiere, und ich verstehe sie die ganze Zeit falsch in meinem Schreiben. Die Beziehung zwischen den Typen ist eine Superset / Subset-Beziehung. Warum also eine Richtung nach oben oder unten damit verbunden sein sollte, weiß ich nicht. Und bring mich nicht mal dazu, mit "Eltern" anzufangen. Die Eltern von Giraffe sind keine Säugetiere, die Eltern von Giraffe sind Herr und Frau Giraffe.
Eric Lippert

9
Object.Equalsist 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 kein Equals(oder GetHashcodeoder ToString) Object.
Eric Lippert

33

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 Typ senderoder sogar ohne Verwendung senderzu behandeln, in anderen Fällen sendermuss es jedoch auf einen spezifischeren Typ umgewandelt werden, um das Ereignis zu behandeln.

Beispielsweise haben Sie möglicherweise zwei TextBoxs und möchten einen einzelnen Delegaten verwenden, um ein Ereignis von jedem von ihnen zu behandeln. Dann müssten Sie senderin eine TextBoxumwandeln, damit Sie auf die erforderlichen Eigenschaften zugreifen können, um das Ereignis zu behandeln:

private void TextBox_TextChanged(object sender, EventArgs e)
{
   TextBox box = (TextBox)sender;
   box.BackColor = string.IsNullOrEmpty(box.Text) ? Color.Red : Color.White;
}

Ja, in diesem Beispiel können Sie eine eigene Unterklasse erstellen, für TextBoxdie dies intern durchgeführt wurde. Nicht immer praktisch oder möglich.


2
Danke, das ist ein gutes Beispiel. Die TextChangedVeranstaltung ist Mitglied von Control- Ich frage mich, warum sendernicht Typ Controlstatt Typ object? Ich frage mich auch, ob das Framework (theoretisch) das Ereignis für jede Unterklasse neu definiert haben könnte (so dass z. B. TextBoxeine neue Version des Ereignisses TextBoxals Absendertyp verwendet werden könnte), für die Downcasting erforderlich ist (oder nicht). zu TextBox) innerhalb der TextBoxImplementierung (um den neuen Ereignistyp zu implementieren), aber es würde vermieden, dass der Ereignishandler des Anwendungscodes heruntergestuft wird.
ChrisW

3
Dies wird als akzeptabel angesehen, da es schwierig ist, die Ereignisbehandlung zu verallgemeinern, wenn jedes Ereignis eine eigene Methodensignatur hat. Obwohl ich nehme an, dass dies eher eine akzeptierte Einschränkung als eine bewährte Vorgehensweise ist, da die Alternative tatsächlich problematischer ist. Wenn es möglich wäre, mit einem TextBox senderstatt mit einem zu beginnen, object senderohne den Code übermäßig zu komplizieren, wäre dies sicherlich der Fall.
Neil

6
@Neil Die Eventing-Systeme sind speziell darauf ausgelegt, beliebige Datenblöcke an beliebige Objekte weiterzuleiten. Das ist fast unmöglich, ohne Laufzeitprüfungen auf Typkorrektheit durchzuführen.
Joker_vD

@Joker_vD Idealerweise würde die API natürlich stark typisierte Rückrufsignaturen mit generischer Kontravarianz unterstützen. Die Tatsache, dass .NET dies (überwiegend) nicht tut, ist lediglich auf die Tatsache zurückzuführen, dass Generika und Kovarianz später eingeführt wurden. - Mit anderen Worten, Downcasting hier (und im Allgemeinen anderswo) ist aufgrund von Unzulänglichkeiten in der Sprache / API erforderlich, nicht, weil es grundsätzlich eine gute Idee ist.
Konrad Rudolph

@KonradRudolph Sicher, aber wie würden Sie Ereignisse beliebigen Typs in der Ereigniswarteschlange speichern? Befreien Sie sich nur von der Warteschlange und gehen Sie zum Direktversand?
Joker_vD

17

Sie brauchen Downcasting, wenn Ihnen etwas einen Supertyp gibt, und Sie müssen ihn je nach Subtyp unterschiedlich behandeln.

decimal value;
Donation d = user.getDonation();
if (d is CashDonation) {
     value = ((CashDonation)d).getValue();
}
if (d is ItemDonation) {
     value = itemValueEstimator.estimateValueOf(((ItemDonation)d).getItem());
}
System.out.println("Thank you for your generous donation of $" + value);

Nein, das riecht nicht gut. In einer perfekten Welt Donationhätte eine abstrakte Methode getValueund 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.


Dies scheint ein guter Zeitpunkt zu sein, um das Besuchermuster zu verwenden. Oder das dynamicSchlüsselwort, mit dem das gleiche Ergebnis erzielt wird.
user2023861

3
@ user2023861 Nein, ich denke, das Besuchermuster hängt von der Kooperation der Spendenunterklassen ab - dh Spenden müssen eine Zusammenfassung deklarieren void accept(IDonationVisitor visitor), die ihre Unterklassen durch Aufrufen bestimmter (z. B. überladener) Methoden von implementieren IDonationVisitor.
ChrisW

@ ChrisW, da hast du recht. Ohne Kooperation könnten Sie es dennoch mit dem dynamicSchlüsselwort tun .
user2023861

In diesem speziellen Fall können Sie der Spende eine EstimateValue-Methode hinzufügen.
user253751

@ user2023861: dynamicnoch downcasting , nur langsamer.
Mehrdad

12

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.


In der Tat könnte man sagen, dass Generics in Java oder C # im Wesentlichen nur ein sicherer Wrapper sind, um Objekte der Oberklasse zu speichern und auf die richtige (vom Compiler überprüfte) Unterklasse herunterzuspielen, bevor die Elemente erneut verwendet werden. (Im Gegensatz zu C ++ Templates, die den Code neu schreiben immer die Unterklasse selbst zu verwenden und vermeiden somit zu gesenkten mit - die Gedächtnisleistung steigern kann, wenn auch oft auf Kosten der Übersetzungszeit und Größe der ausführbaren Datei.)
leftaroundabout

4

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, dynamicObjekte usw. Sie ermöglichen es Ihnen, eine Behauptung ("Ich behaupte, dass ein Objekt aein String, wenn ich mich irre, wirf ein InvalidCastException") 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.


Ich schätze, ich frage dann nach einigen Beispielen, wo / wann es notwendig oder wünschenswert ist (dh eine gute Praxis), diese Art von "unbewiesener Behauptung" zu machen.
ChrisW

1
Wie wäre es mit einem Casting des Ergebnisses einer Reflektionsoperation? stackoverflow.com/a/3255716/3141234
Alexander

3

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

if(object is X)
{
    //do the thing we do with X's
}
else
{
    //of the thing we do with Y's
}

du tust es einfach

x.DoThing()

und der Code macht automatisch das Richtige.

Es gibt einige "harte" Gründe, nicht niederzuschlagen:

  • In C ++ ist es langsam.
  • Sie erhalten einen Laufzeitfehler, wenn Sie den falschen Typ auswählen.

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 MessageBaseClassin 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 Funktionen

Natü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.


1
Ich bezweifle, dass es in C ++ langsam ist, besonders wenn Sie static_caststattdessen dynamic_cast.
ChrisW

"Message-Processing" - Ich denke, das bedeutet zB, lParamauf etwas Bestimmtes zu werfen . Ich bin mir jedoch nicht sicher, ob das ein gutes C # -Beispiel ist.
ChrisW

3
@ChrisW - na ja, es static_castist 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.
Jules

@Jules: Das ist völlig nebensächlich.
Mehrdad

@Mehrdad, es ist genau der Punkt. das äquivalente c # in c ++ zu machen ist langsam. und das ist der traditionelle Grund gegen das Casting. Der alternative static_cast ist nicht äquivalent und wird aufgrund des undefinierten Verhaltens als die schlechtere Wahl angesehen
Ewan

0

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 ;-)


Siehe ja zum Beispiel Was die Tag - Eigenschaft in .net verwenden ist . In einer der Antworten schrieb jemand, ich habe sie verwendet, um Anweisungen für den Benutzer in Windows Forms-Anwendungen einzugeben. Als das Steuerelement-GotFocus-Ereignis ausgelöst wurde, wurde der Anweisungseigenschaft Label.Text der Wert der Steuerelement-Tag-Eigenschaft zugewiesen, die die Anweisungszeichenfolge enthielt. Ich schätze, eine alternative Möglichkeit, die zu erweitern Control(anstatt die object TagEigenschaft zu verwenden), wäre, eine zu erstellen, Dictionary<Control, string>in der die Beschriftung für jedes Steuerelement gespeichert wird.
ChrisW

1
Diese Antwort gefällt mir, weil sie Tageine öffentliche Eigenschaft einer .Net-Framework-Klasse ist, was zeigt, dass Sie sie (mehr oder weniger) verwenden sollen.
ChrisW

@ChrisW: Nicht zu sagen, dass die Schlussfolgerung falsch (oder richtig) ist, aber beachten Sie, dass dies eine schlampige Argumentation ist, da es viele öffentliche .NET-Funktionen gibt, die veraltet (etwa System.Collections.ArrayList) oder anderweitig veraltet (etwa IEnumerator.Reset) sind.
Mehrdad

0

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.

public interface Foo
{
     void SayHello();
     void SayGoodbye();
 }

Und 2 Klassen, die es implementieren. Barund Qux. Barist gut erzogen.

public class Bar
{
    void SayHello()
    {
        Console.WriteLine(“Bar says hello.”);
    }
    void SayGoodbye()
    {
         Console.WriteLine(“Bar says goodbye”);
    }
}

Ist Quxaber nicht gut erzogen.

public class Qux
{
    void SayHello()
    {
        Console.WriteLine(“Qux says hello.”);
    }
    void SayGoodbye()
    {
        throw new NotImplementedException();
    }
}

Nun ... jetzt habe ich keine Wahl. Ich muss check und (möglicherweise) downcast eingeben, um zu vermeiden, dass mein Programm abstürzt.


1
Nicht wahr für dieses Beispiel. Sie könnten einfach die NotImplementedException abfangen, wenn Sie SayGoodbye aufrufen.
Per von Zweigbergk

Es ist vielleicht ein stark vereinfachtes Beispiel, aber arbeiten Sie mit einigen der älteren .Net-APIs ein wenig und Sie werden in diese Situation geraten. Manchmal ist es am einfachsten, den Code sicher zu schreiben var qux = foo as Qux;.
RubberDuck

0

Ein weiteres Beispiel aus der Praxis sind WPFs VisualTreeHelper. Es verwendet Downcast zum Casting DependencyObjectauf Visualund Visual3D. Zum Beispiel VisualTreeHelper.GetParent . Der Downcast erfolgt in VisualTreeUtils.AsVisualHelper :

private static bool AsVisualHelper(DependencyObject element, out Visual visual, out Visual3D visual3D)
{
    Visual elementAsVisual = element as Visual;

    if (elementAsVisual != null)
    {
        visual = elementAsVisual;
        visual3D = null;
        return true;
    }

    Visual3D elementAsVisual3D = element as Visual3D;

    if (elementAsVisual3D != null)
    {
        visual = null;
        visual3D = elementAsVisual3D;
        return true;
    }            

    visual = null;
    visual3D = null;
    return false;
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.