Versagt Polymorphie bei überschreibbaren Funktionen?

  • VB.NET

Es gibt 21 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Versagt Polymorphie bei überschreibbaren Funktionen?

    Hallo liebe Community,

    Ich stehe mit der Overrides-Logik bei abgeleiteten Klassen auf dem Kriegsfuss. Ich möchte eine überschreibbare Public Property einer Basisklasse in einer abgeleiteten Klasse so überschreiben, dass diese auch einen vom Rückgabewert abgeleiteten Typ zurückgeben kann. VS meckert, da der Rückgabetyp nicht mit der Syntax der überschriebenen Klasse korreliert. Ich mache ein Beispiel(frei erfunden):

    VB.NET-Quellcode

    1. 'Class A
    2. Public Overridable Function GetX As Control 'Oder eine abstrakte Klasse, mit "MustOverride"
    3. 'Class B : Inherits A
    4. Public Overrides Function GetX As TextBox

    Ich verstehe nur nicht, wo da das Problem liegen soll: Wenn die überschriebene Methode doch einen abgeleiteten Typ zurückgibt, kann er doch als sein Basisobjekt zurückgegeben werden.
    Ok, ich kann es ja auch anders lösen, aber darum geht es mir jetzt nicht. Sondern darum, weshalb der Compiler so ein Konstrukt nicht akzeptiert.
    Momentan müsste ich nämlich die Funktionen je Protected machen und dann eine öffentliche, die den Rückgabewert der Protected-Funktion castet.

    Grüsse

    Higlav
    @ErfinderDesRades: Aber eine TextBox ist ja ein Control. Zurückgeben kann ich es ja schon, aber es wird als Control zurückgegeben. So wie ich das verstehe könnte höchstens OptionInfer seine Probleme kriegen. ?(

    @fichz: Ja, Shadows kenne ich schon. Aber wenn ich eine bestimmte Methode nicht überschreibe, muss ich beim Aufruf als Basisobjekt immer zum richtigen Typ casten, um die richtige Methode auszuführen, oder?
    Ich versteh dein Problem nicht so ganz.
    Gerade bei Properties kannst du diese ja von Typ Control belassen.
    Zuweisen kannst du nun jede Klasse welche von Control erbt:

    VB.NET-Quellcode

    1. Option Strict On
    2. Public Class Form1
    3. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    4. Dim t As New A
    5. t.GetX = New TextBox
    6. End Sub
    7. End Class
    8. Class A
    9. Public Property GetX As Control
    10. End Class


    Um bei deinem Beispiel zu bleiben.

    lg
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten

    Higlav schrieb:

    Aber eine TextBox ist ja ein Control. Zurückgeben kann ich es ja schon, aber es wird als Control zurückgegeben.
    ja - eiglich hast du recht, jdfs. war Rückgabewerte betrifft.
    Aber eine Property mit Textbox-Setter, oder eine Methode mit Textbox-Argument würde die Schnittstelle der Klasse definitiv verengen, und das ist unzulässig.
    Ich denke, du näherst dich dem Problem der CoVarianz/ContraVarianz an - das ist was ganz dolles neues, was mit 2010 eingeführt wurde, dass das wenigstens mit Delegaten geht.

    Aber beim Überschreiben mussich nochmal nachdenken, ob das ühaupt möglich ist.
    ZB. kann das Überschriebene ja durch weitere Erben nochmal überschrieben werden - Welchen Datentyp hätte dann die Über-Überschreibung?
    würde die sich wieder erweitern aufs ursprüngliche Control, oder kann die nur noch weiter verengen, etwa auf MaskedTextbox (mal angenommen MaskedTextbox erbte von Textbox)?
    @fichz: Wirklich ein Problem habe ich nicht(vorliegen), aber mir will nicht einleuchten, wieso es nicht funktioniert. Ich habe es nochmals ausführlicher darzustellen versucht:

    VB.NET-Quellcode

    1. Module TestA
    2. Sub Main()
    3. Dim a = (New BaseA).GetX 'Control
    4. Dim b = (New DerivedA).GetX 'TextBox, gibt aber die Basis, Control zurück. Jetzt müsste gecastet werden...
    5. End Sub
    6. End Module
    7. Public Class BaseA
    8. Public Overridable Function GetX() As Control
    9. Return New Control
    10. End Function
    11. End Class
    12. Public Class DerivedA
    13. Inherits BaseA
    14. 'Ich möchte, dass der Programmierer beim Anwenden dieser Methode
    15. 'Nicht noch casten muss, sondern gleich das korrekte Objekt bekommt.
    16. '(Um Fehler zu vermeiden und die Handlichkeit meines Codes zu gewährleisten)
    17. 'Ich könnte zwar auch in den XML-Kommentaren sagen, man solle den Rückgabewert
    18. 'in eine TextBox konvertieren, aber das ist mir irgendwie zu umständlich, unschön und typunsicher.
    19. Public Overrides Function GetX() As TextBox
    20. Return New TextBox
    21. End Function
    22. End Class
    23. '------------------------------------
    24. 'Alternative(wird aber unübersichtlicher):
    25. Module TestB
    26. Sub Main()
    27. Dim a = (New BaseB).GetX 'Control
    28. Dim b = (New DerivedB).GetX 'TextBox
    29. End Sub
    30. End Module
    31. Public Class BaseB
    32. Protected Overridable Function GetXInternal() As Control
    33. Return New Control
    34. End Function
    35. Public Function GetX() As Control
    36. Return GetXInternal()
    37. End Function
    38. End Class
    39. Public Class DerivedB
    40. Inherits BaseB
    41. Protected Overrides Function GetXInternal() As Control
    42. Return New TextBox
    43. End Function
    44. Public Shadows Function GetX() As TextBox
    45. Return DirectCast(GetXInternal(), TextBox)
    46. End Function
    47. End Class


    @ErfinderDesRades: Ich habe mir mal den Wiki-Artikel zu Kovarianz und Kontravarianz angesehen. So wie ich es verstanden habe, wundere ich mich anscheinend über die fehlende Kovarianz der Rückgabetypen bei überschreibbaren Methoden.

    ErfinderDesRades schrieb:

    ZB. kann das Überschriebene ja durch weitere Erben nochmal überschrieben werden - Welchen Datentyp hätte dann die Über-Überschreibung?
    würde die sich wieder erweitern aufs ursprüngliche Control, oder kann die nur noch weiter verengen, etwa auf MaskedTextbox (mal angenommen MaskedTextbox erbte von Textbox)?

    Da hätte ich jetzt gesagt, dass der Rückgabetyp entweder gleich oder abgeleitet sein müsste, da nur die Methode der ersten Stufe überschrieben wird und nicht diejenige eine Stufe weiter, da die vorige jene schon überschrieb.

    @Artentus: Ich muss zugeben, mit Generikas habe ich mich noch nicht wirklich auseinandergesetzt(Das ist doch das mit dem "T", oder? :huh: ). Ich weiss deshalb nicht, ob das die Lösung für mein Problem ist, oder nicht. Vielleicht kannst du mir kurz erklären, wo du die Lösung mit den Generikas siehst?

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Higlav“ ()

    So meine ich das:

    VB.NET-Quellcode

    1. Public Class Base(Of T As Control)
    2. Public Overridable Function GetX() As T
    3. End Function
    4. End Class
    5. Public Class Derived : Inherits Base(Of TextBox)
    6. Public Overrides Function GetX() As TextBox
    7. End Function
    8. End Class
    Das lässt sich dann aber beispielsweise nicht mehr weitervererben. Und wenn ich mehrere Typen habe, will ich nicht bei jedem Instanzieren Dim b As New Base(Of Control, Double, String, SynchronizationContext)... schreiben. Ausserdem liessen sich dort dann ja alle Typen hineinkritzeln. Dann habe ich statt ein Dim Va As New Vector(Of Acceleration) vielleicht irgendwann ein Dim Va As New Vector(Of Bitmap). Dann greife ich lieber auf meine Protected-Alternative zurück.

    Higlav schrieb:

    Das lässt sich dann aber beispielsweise nicht mehr weitervererben.
    Die abgeleitete Klasse kann selbst wiederum generisch sein.

    Higlav schrieb:

    Ausserdem liessen sich dort dann ja alle Typen hineinkritzeln.
    Nein, dafür ist ja die Typeinschränkung.

    Higlav schrieb:

    Ich schau' mir das mal genauer an.
    schau:

    VB.NET-Quellcode

    1. Public Class Base(Of T As Control)
    2. Public Overridable Function GetX() As T
    3. End Function
    4. End Class
    5. Public Class Derived(Of T As Panel) : Inherits Base(Of T)
    6. Public Overrides Function GetX() As T
    7. End Function
    8. End Class
    9. Public Class FlowLayoutDerived : Inherits Derived(Of FlowLayoutPanel)
    10. Public Overrides Function GetX() As FlowLayoutPanel
    11. End Function
    12. End Class
    13. Public Class TableLayoutDerived : Inherits Derived(Of TableLayoutPanel)
    14. Public Overrides Function GetX() As TableLayoutPanel
    15. End Function
    16. End Class

    (echt kranker Scheiß :thumbup: )

    Insgesamt stimme ich dir zu (bisher - evtl. finden sich doch noch zwingende Hinderungsgründe), dass Kovarianz in Überschreibungen und Interfaces eiglich theoretisch möglich sein müsste.
    Also dass ein ausgehender Datentyp - etwa Function-Rückgabewert - bei Überschreibung eingeengt werden können müsste.
    Etwa dass eine Function(Of Control) durchaus gültig und typsicher auch durch eine Function(Of Panel) überschreibbar sein sollte.

    Allerdings ist der nächste Gedanke dann die KontraVarianz, also die Logik, dass ein eingehender Datentyp - etwa ein Methoden-Argument - bei Überschreibung erweiterbar ist.
    Etwa eine Action(Of FlowLayoutPanel) wäre gültig und typsicher überschrieben durch eine Action(Of Panel).

    In dem Moment tritt aber bei ReadWrite-Properties ein Problem auf, denn der Getter ist ein ausgehender Datentyp, und der Setter ein eingehender.
    Kovarianz+Kontravarianz würde also bedeuten, dass eine Property vom Typ Panel sich in 2 Datentypen aufteilen können müsste:
    den Getter durch FlowlayoutPanel überschreiben (einengen) und den Setter durch Control (erweitern)

    Also Contravarianz scheint mir bei Polymorphie nicht praktisch umsetzbar.
    Also klar

    VB.NET-Quellcode

    1. Public Class Base
    2. Public Overridable Sub SetPanel(ctl As Panel)
    3. End Sub
    4. End Class
    5. Public Class Derived : Inherits Base
    6. Public Overrides Sub SetPanel(ctl As Control)
    7. End Sub
    8. End Class
    ist vom Covarianz-Gedanken her richtig: Du kannst in eine List(Of Base) Bases und Deriveds mischen, und über einen Kamm geschert baseObject.SetPanel(new Panel) aufrufen, das ergibt auch bei den derived-Objekten keinen TypKonflikt.
    Aber schon das Benennungs-Problem... :huh:

    Ist interessant: Offensichtlich erweist sich das Property-Konzept (ich kanns eh nicht leiden) als unüberwindliches Hindernis für die Einführung von Covarianz - was ansonsten glaub sehr nützlich sein könnte.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Das man bei Generikas einengen kann, wusste ich nicht. Ich ging davon aus, dass es schlicht nicht ginge, weshalb ich mich nie wirklich so stark damit beschäftigte. Aber jetzt, wo ich weiss, dass man eine Basisklasse als Voraussetzung angeben kann...
    Ich merke gerade, ich werde nochmals ein wenig umstrukturieren müssen: Grob gesagt wäre laut meiner Planung für das nächste Projekt folgendes möglich:
    • Dim V As New Vector(Of Real)
    • Dim V As New Vector(Of Length)
    • Dim V As New Vector(Of Velocity)
    • Dim V As New Vector(Of Acceleration)
    • Dim V As New Vector(Of Force)
    • Dim V As New Vector(Of Impulse)

    Aber leider dann auch Dim V As New Vector(Of Temperature) oder Dim V As New Vector(Of Luminosity). Die einzelnen Einheiten erben alle von "Real"(für reelle Zahl). D.h. ich kann jetzt nicht einfach eine Public Class Vector(Of T As Real) implementieren, sondern muss da was mit Interfaces basteln, verstehe ich das richtig? Hmm, dann könnte ich ein Public Interface IVector(Of T As Real) imple... - nein, das würde so nicht gehen. Vielleicht ohne Generika? Nur ein "leeres" Interface zur Identifikation? Och Mensch, Ich geh jetzt und probiere mal ein bisschen rum. :D

    Danke euch für eure Hilfe und euer Interesse.

    Mit freundlichen Grüssen,

    Higlav
    Ich weiss, dass es unnötig kompliziert erscheint, aber wenn ich das nächste Projekt angehe wird es erst noch kompliziert. Und da muss ich in den Dimensionen und Werten unterscheiden, damit ich beim Berechnen die Deformation von Materialien nicht von der Beschleunigung sondern von der Kraft und der Fläche abhängig mache. Und da ich alles in einer Liste haben will, die ich dann abarbeite und die Objekte zeichne, muss alles von ein und derselben Basisklasse erben. Zusätzlich soll das Ganze noch erweiterbar sein, weshalb ich keine Typenabfrage bei den Objekten machen kann, sondern deren überschriebenen Werte abgreife und zeichne -> deshalb kam ich auf dieses Thema.

    Ich hasse eigentlich hochkomplexe Gebilde - ausser sie sind von mir. :rolleyes: :D
    Dann leg dir die ganzen Klassen an und gib ihnen ne Value-Property vom Typ Vector2/3 (je nachdem).
    Es ist halt nunmal so, dass ein Computer nur Mathemaisch arbeitet, nicht physikalisch, zwischen einem Geschwindigkeits- und einem Kraftvektor kann daher nicht so ohne weiteres unterschieden werden, es liegt bei dir, die korrekten Werte für die Berechnungen zu benutzen.
    Vektoren sollten übrigens Structures sein.

    gugge auch mal Wpf - da gibts sehr schöne Vektoren.

    was ist eiglich mit artentus' Physic-Engine - kannst du das nicht verwenden? - ach, sorry - ist ja nicht von dir ;)

    Not-invented-here-Syndrom

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Artentus schrieb:

    Es ist halt nunmal so, dass ein Computer nur Mathemaisch arbeitet, nicht physikalisch, zwischen einem Geschwindigkeits- und einem Kraftvektor kann daher nicht so ohne weiteres unterschieden werden, es liegt bei dir, die korrekten Werte für die Berechnungen zu benutzen.

    *sign*
    Ich dachte da an die(teilweise) Implementierung von Einheiten. Anhand deren überschriebenen Value-Properties kann ich den Typ und den Wert abgreifen.

    ErfinderDesRades schrieb:

    was ist eiglich mit artentus' Physic-Engine

    Meinst du MathUtils? Die habe ich mir schon herunergeladen und finde sie toll. :thumbup: Nur leider bringt sie mir nicht die Flexibilität, die ich mir wünschte. Ich will nämlich relativ komplexe Berechnungen durchführen können, die in einem konstruierten imaginären Raum ablaufen(z.B. soll der Energieaustausch bei einem Stahlträgers berechnet werden, der von einer Bleikugel an einem bestimmten Punkt aus 15m Höhe getroffen wird. Auch Wärmeaustausch- und Impulsberechnungen sollten möglich sein - dafür ist mir die Architektur der MathUtil ein wenig zu sperrig - Nichts gegen deine Lib, Artentus, die ist genial). Es soll mir in meinem(zukünftigen) Maschinenbaustudium helfen.
    Ich werde mich beim Aufbau meines Projektes sehr stark an der MathUtils-Lib orientieren. Besonders die Lösungswege für kleinere Probleme(Intersektionsprüfung von z.B. Körpern) werde ich mir aus der MathUtils zusammenklauben. :thumbup:
    Die ganzen Berechnungen werden aber evtl. extrem aufwändig - zu aufwändig, um das Ganze dann auch noch in Echtzeit zeichnen zu können. Ich muss in diesem Fall vielleicht sogar einen Teil mit MatLab implementieren, um die Berechnungen performat genug abzuarbeiten. :wacko:

    ErfinderDesRades schrieb:

    - kannst du das nicht verwenden? - ach, sorry - ist ja nicht von dir ;)

    Not-invented-here-Syndrom

    He, das ist nicht lustig - dieses Syndrom hat es in sich und sollte nicht unterschätzt werden! :D
    Nein, Scherz beiseite: Natürlich könnte ich versuchen, das Ganze auf der MathUtils-Lib aufzubauen, aber ich möchte auch die Hintergründe genau verstehen. Ich mache das nicht, weil ich denke, die Welt bräuchte so eine Library, sondern weil deren Aufbau auch mir selbst hilft, mich besser mit der Materie auseinanderzusetzen und sie noch besser zu verstehen(OOP sowie Mathematik).

    ErfinderDesRades schrieb:

    Vektoren sollten übrigens Structures sein.

    gugge auch mal Wpf - da gibts sehr schöne Vektoren.

    Warum sollten Vektoren Structures sein? Weil sie dann Valuetypes sind und nicht auf'm Heap liegen?
    Dass es bei WPF nochmals eigene Vektoren gibt, wusste ich nicht. Ich kannte nur die Vektoren/Matrizen/Quaternionen aus dem System.Windows.Media.Media3D-Namespace. ^^

    Higlav schrieb:

    Warum sollten Vektoren Structures sein?
    Das ist ein Design-Prinzip: Was eine Identität hat, ist eine Class, was keine Identität hat eine Structure.
    also ein Stuhl hat eine Identität. Denke dir 2 völlig identische Stühle - es sind trotzdem 2 unterscheidbare Objekte. Mach von einem Stuhl die Beine länger - ist immer noch derselbe Stuhl.

    Die Zahl 5 hat keine Identität. zwei 5 en sind nicht unterscheidbar, sondern sind halt 5. Und ändere 5 auf 6, dann ists nicht mehr 5, sondern ist 6.
    Deshalb sind alle Zahlen-Typen Structures, aber das gilt auch für Zeiten, Zeitspannen, Punkte und Vektoren und noch so einiges.