it-swarm.com.de

Was ist eine richtige Verwendung von Downcasting?

Downcasting bedeutet das Umwandeln von einer Basisklasse (oder Schnittstelle) in eine Unterklasse oder Blattklasse.

Ein Beispiel für einen Niedergeschlagenen könnte sein, wenn Sie aus System.Object zu einem anderen Typ.

Downcasting ist unbeliebt, möglicherweise ein Codegeruch: Objektorientiert doctrine ist es vorzuziehen, beispielsweise virtuelle oder abstrakte Methoden anstelle von Downcasting zu definieren und aufzurufen.

  • Was sind, wenn überhaupt, gute und richtige Anwendungsfälle für Downcasting? Das heißt, unter welchen Umständen ist es angemessen, Code zu schreiben, der heruntergespielt wird?
  • Wenn Ihre Antwort "keine" lautet, warum wird Downcasting dann von der Sprache unterstützt?
71
ChrisW

Hier sind einige geeignete Verwendungszwecke von Downcasting.

Und ich respektvoll vehement stimme nicht z mit anderen hier, die sagen, dass die Verwendung von Downcasts definitiv ist ein Code-Geruch, weil ich glaube, dass es keinen anderen vernünftigen Weg gibt, um 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 sagen möchten, dass es sich um Code-Gerüche handelt, müssen Sie bessere Lösungen für diese bereitstellen (auch wenn sie aufgrund von Unannehmlichkeiten vermieden werden). Ich glaube nicht, dass es bessere Lösungen gibt.

13
user541686

Downcasting ist unbeliebt, vielleicht ein Code-Geruch

Ich stimme dir nicht zu. Downcasting ist äußerst beliebt ; Eine große Anzahl realer Programme enthält einen oder mehrere Downcasts. Und es ist nicht vielleicht ein Code-Geruch. Es ist definitiv ein Codegeruch. Aus diesem Grund muss sich der Downcasting-Vorgang im Programmtext manifestieren.. Auf diese Weise können Sie den Geruch leichter wahrnehmen und die Aufmerksamkeit der Codeüberprüfung darauf richten.

unter welchen Umständen ist es angemessen, Code zu schreiben, der nach unten geht?

In jedem Fall, in dem:

  • sie haben eine 100% korrekte Kenntnis einer Tatsache über den Laufzeittyp eines Ausdrucks, die spezifischer ist als der Typ zur Kompilierungszeit des Ausdrucks, und
  • sie müssen diese Tatsache ausnutzen, um eine Funktion des Objekts zu verwenden, die für den Typ zur Kompilierungszeit nicht verfügbar ist, und
  • es ist besser, Zeit und Mühe zu verwenden, um die Besetzung zu schreiben, als das Programm umzugestalten, um einen der ersten beiden Punkte zu eliminieren.

Wenn Sie ein Programm kostengünstig umgestalten können, damit entweder der Laufzeittyp vom Compiler abgeleitet werden kann, oder um das Programm so umzugestalten, dass Sie nicht die Fähigkeit des weiter abgeleiteten Typs benötigen, tun Sie dies. Downcasts wurden der Sprache für die Umstände hinzugefügt, in denen es schwer und teuer ist. ), um das Programm zu überarbeiten.

warum wird Downcasting von der Sprache unterstützt?

C # wurde von pragmatischen Programmierern erfunden, die Aufgaben zu erledigen haben, für pragmatische Programmierer, die Aufgaben zu erledigen haben. Die C # -Designer sind keine Puristen OO. Und das System vom Typ C # ist nicht perfekt; es unterschätzt vom Design her die Einschränkungen, die dem Laufzeittyp einer Variablen auferlegt werden können.

Außerdem ist Downcasting in C # sehr sicher. Wir haben eine starke Garantie dafür, dass der Downcast zur Laufzeit überprüft wird. Wenn er nicht überprüft werden kann, tut das Programm das Richtige 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, einen Downcast in C # ohne mit einem expliziten Cast-Operator zu erzeugen. Können Sie sich solche Szenarien vorstellen? Da es sich auch um potenzielle Codegerüche handelt, welche Designfaktoren haben Ihrer Meinung nach bei der Entwicklung eines Features eine Rolle gespielt, das zu einem Absturz führen könnte, ohne dass der Code eine offensichtliche Besetzung enthält?

139
Eric Lippert

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 sender oder sogar ohne Verwendung von sender zu behandeln, in anderen Fällen muss sender in einen höheren Wert umgewandelt werden -spezifischer Typ zur Behandlung des Ereignisses.

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 sender in ein TextBox umwandeln, 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 von TextBox erstellen, die dies intern durchgeführt hat. Nicht immer praktisch oder möglich.

33
mmathis

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

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 hätte Donation eine abstrakte Methode getValue und jeder implementierende Subtyp hätte eine geeignete 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.

17
Philipp

Zu Eric Lipperts Antwort hinzufügen, da ich keinen Kommentar abgeben kann ...

Sie können Downcasting häufig vermeiden, indem Sie eine API für die Verwendung von Generika umgestalten. Aber Generika wurden der Sprache erst in Version 2.0 hinzugefügt. Selbst wenn Ihr eigener Code keine alten Sprachversionen unterstützen muss, verwenden Sie möglicherweise ältere Klassen, die nicht parametrisiert sind. Beispielsweise kann eine Klasse so definiert werden, dass sie eine Nutzlast vom Typ Object enthält, die Sie in den Typ umwandeln müssen, von dem Sie wissen, dass er tatsächlich ist.

12
StackOverthrow

Es gibt einen Kompromiss zwischen statischen und dynamisch typisierten Sprachen. Die statische Typisierung gibt dem Compiler viele Informationen, um ziemlich starke Garantien für die Sicherheit von (Teilen von) Programmen geben zu können. 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 geltend zu machen ist einfacher als sie zu beweisen.

Es gibt "unsichere" Konstrukte, mit denen Sie unbewiesene Aussagen gegenüber dem Compiler treffen können. Zum Beispiel bedingungslose Aufrufe von Nullable.Value , bedingungslose Downcasts, dynamic Objekte usw. Sie ermöglichen es Ihnen, einen Anspruch geltend zu machen ("Ich behaupte, dass das Objekt a ein String ist, wenn ich ' m falsch, wirf ein InvalidCastException ") , ohne dass es beweisen muss. Dies kann in Fällen nützlich sein, in denen der Nachweis erheblich schwieriger als sinnvoll ist.

Dies zu missbrauchen ist riskant, weshalb die explizite Downcast-Notation existiert und obligatorisch ist. Es ist syntaktisches Salz , um die Aufmerksamkeit auf eine unsichere Operation zu lenken. Die Sprache könnte mit implizitem Downcasting implementiert worden sein (wobei der abgeleitete Typ eindeutig ist), aber das würde diese unsichere Operation verbergen, was nicht wünschenswert ist.

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 niemals niedergeschlagen werden müssten, da seine Existenzberechtigung darin besteht, dass 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 niedergeschlagen zu werden:

  • 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 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 für das Risiko dieser schwierigen Gründe herunterladen.

Dies war jedoch vor der Erfindung der Generika. Jetzt können Sie Downcasting vermeiden, indem Sie einen Typ angeben, in dem die Details später angegeben werden.

MessageProccesor<T> kann seine Methodenparameter und Rückgabewerte um den angegebenen Typ variieren, bietet jedoch weiterhin allgemeine Funktionen

Natürlich zwingt Sie niemand, OOP Code) zu schreiben, und es gibt viele Sprachfunktionen, die bereitgestellt werden, aber verpönt sind, wie z. B. Reflexion.

3
Ewan

Stellen Sie sich zusätzlich zu allen zuvor genannten eine Tag-Eigenschaft vor, die vom Typ Objekt ist. Auf diese Weise können Sie ein Objekt Ihrer Wahl in einem anderen Objekt speichern, um es später nach Bedarf zu verwenden. Sie müssen diese Eigenschaft niederschlagen.

Im Allgemeinen ist es nicht immer ein Hinweis auf etwas Nutzloses, bis heute etwas nicht zu benutzen ;-)

0
puck

Die häufigste Verwendung von Downcasting in meiner Arbeit ist darauf zurückzuführen, dass ein Idiot gegen Liskovs Substitutionsprinzip verstößt.

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. Bar und Qux. Bar benimmt sich gut.

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

Aber Qux benimmt sich nicht gut.

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

Nun ... jetzt habe ich keine Wahl. Ich muss, um check und (möglicherweise) downcast einzugeben, um zu vermeiden, dass mein Programm zum Absturz kommt.

0
RubberDuck

Ein weiteres Beispiel aus der Praxis ist das VisualTreeHelper von WPF. Es verwendet Downcast, um DependencyObject in Visual und 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;
}
0
zwcloud