Announcement

Collapse
No announcement yet.

Dynamische Typkonvertierung zur Laufzeit

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Dynamische Typkonvertierung zur Laufzeit

    Ich möchte zur Laufzeit mein Programm entscheiden lassen, in welchen Typ das übergebene Objekt konvertiert werden soll, damit es an die richtige überladene Methode übergeben wird.

    Ich bin mir sicher, dass diese Frage schon einmal gestellt wurde, kann aber keine Antwort darauf finden.

    Folgendes Szenario (nicht elegant):
    [highlight=c#]public bool SpeichereObjekt(ref ITour4UObjekt MeinObjekt)
    {
    if (MeinObjekt is clsKunde) InsertObjektInDB((clsKunde)MeinObjekt);
    if (MeinObjekt is clsMitarbeiter) InsertObjektInDB((clsMitarbeiter)MeinObjekt);
    //alle weiteren möglichen Typen dieser Schnittstelle müssen abgefragt und geboxt werden
    return true;
    }
    private bool InsertObjektInDB(clsKunde Kunde)
    {
    return true;
    }
    private bool InsertObjektInDB(clsMitarbeiter Kunde)
    {
    return true;
    }[/highlight]

    Folgender kläglicher Versuch meinerseits deutet an, was ich gerne hätte:
    Code:
    InsertObjektInDB((MeinObjekt.GetType())MeinObjekt);
    Eventuell gehe ich ja auch einfach nur den falschen Weg und es gibt ein einfaches Konzept über das Prinzip der Generik, wie ich eine solche Unterscheidung treffen kann.

    Hat jemand einen Lösungsvorschlag?
    Zuletzt editiert von TheoFontane; 18.09.2009, 12:53.

  • #2
    Wenn es keine Funktion für die Parentklasse eines Mitarbeiters oder Kunden gibt, dann sollte das eigentlich auch ohne casten funktionieren, dann kann C# nämlich sauber entscheiden zu welcher Funktion es passt.
    Gibt es allerdings eine Funktion für die Oberklasse (z.B. InsertObjektInDB(clsPerson Kunde)), dann musst Du per cast entscheiden welche Funktion verwendet werden soll.

    Bin aber auch kein OOP Gott und eine schönere Lösung würde mich auch interessieren

    Comment


    • #3
      in VB.NET wäre es folgendes

      [highlight=vbnet]
      if MeinObjekt.getType is GetType(clsKunde) then InsertObjectDB(DirecCast(MeinObjekt,clsKunde)
      [/highlight]

      oh ich glaube ich hab dich falsch verstanden oder? Du willst es komplett selbst entscheiden ohne eine TypEntscheidung sprich eine variable Zeile ich weiß nicht ob das geht
      Unsere Jugend ist unerträglich, unverantwortlich und entsetzlich anzusehen! - Aristoteles

      Comment


      • #4
        Bei Generics hättest du das selbe Problem.
        Das konkrete Methodenziel bei einer überladenen Funktion muss, so wie du das gemacht hast, zur Compiletime feststehen. Das konkrete Ziel also erst zur Runtime über den Typ zu ermitteln ist zu spät das überfordert den Polymorphiemechanismus.

        Eine dynamische Ermittlung der richtigen Methode wäre nur über Reflection möglich. Das ergäbe vielleicht kürzeren aber sicherlich deutlich hässlicheren Code.

        Die einzige nutzbare polymorphe Größe in deinem Beispiel ist das ITour4UObjekt Interface. Du müsstest also das speichern über diese Interface abhändeln. z.B. Indem du dem Interface eine Save Methode verpasst und die Instanz mit den Methoden zum speichern übergibst. Die konkrete Instanz kann dann einfach InsertObjektInDB mit sich selbst aufrufen.
        Fände ich nicht so besonders da nun die Modelklasse plötzlich etwas über ihre Persistierung wissen müsste.

        Im Moment würde ich sagen du hast zwar in deinem gezeigten Code nicht die letzten coolsten Features benutzt und ohne das findet man das (auch oft selbst ) unelegant, oft tun es aber ja auch die einfachen Lösungen

        Was ich allerdings merkwürdig finde ist da du überhaupt mit polymorphen Methoden arbeitest. Daraus würde ich schließen das du das Speichern für die verschiedenen Modelklassen in einer Klasse zusammengefasst hast. Das fände ich nun wirklich
        weniger gut und ich würde das als die eigentliche Wurzel deines Problem ausmachen. Wieso hast du nicht äquivalent zu den n*Modelklassen die ein gemeinsames Interface implementieren n*Speicherklassen die ein entsprechendes allgemeines Speicherinterface implementieren? Auf dem Niveau wäre es vermutlich einfacher eine elegante Lösung zu finden.

        Comment


        • #5
          Hallo,

          in welchen Typ das übergebene Objekt konvertiert werden soll, damit es an die richtige überladene Methode übergeben wird.
          Das ist somit ein Double-Dispatch. Die Umsetzung kann am elegantesten mit dem Besucher-Entwurfsmuster (Visitor-Pattern) geschehen.

          Sollte dir dieses Muster nicht bekannt sein dann kannst du dir dieses Beispiel anschauen.


          mfG Gü
          "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

          Comment


          • #6
            @das-d:
            Die Variante in VB.NET bringt mich nicht weiter, da dort GENAU wie in meinem unelegantem Ausgangsszenario jeder Typ einzeln geprüft wird.

            @fanderlf:
            Du hast für den Fall recht, wenn es sich um "normale" Vererbung handelt. Ich habe jedoch Klassen, die alle gemeinsam eine Schnittstelle implementiert haben (daher das "I" in "ITour4UObjekt"). Ich hätte das "Detail" mit der Schnittstelle deutlich erwähnen sollen.

            Leider bin ich jetzt nicht weiter als vorher. Hat niemand eine Idee? Es muss doch einfach eine elegantere Variante geben...

            Comment


            • #7
              Hat niemand eine Idee?
              In kürzester Zeit ist einiges passiert.....


              mfG Gü
              "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

              Comment


              • #8
                @Gü: Äh Visitor-Pattern ist doch schon genau das was Theo macht(auch wenn er es nicht so nennt) SpeichereObjekt entspricht Reise.Accept in deinem Beispiel. Nur das er hier nicht alle besuchen will sondern nur ein spezielles Ziel. Oder übersehe ich etwas entscheidendes?

                Comment


                • #9
                  Oha... da haben gleich mehrere zur selben Zeit geantwortet.

                  @Ralf Jansen:
                  Vielen Dank für deinen Beitrag. Du hast richtig erfasst, dass ich keine Persistierungsanweisungen in meinen Datenklassen haben möchte. Ich bin mir aber nicht sicher, ob du deinen Gedanken mit den "Speicherklassen" zu Ende gedacht hast. Ich möchte nach außen genau EINE Methode haben, die ein Objekt vom Interface XY erwartet. Diese Methode soll dann anhand des Objekttyps entscheiden, welche Speichermethode (in welcher Speicherklasse) aufgerufen werden soll. Wenn ich etwas übersehen habe, freue ich mich über Feedback, aber dann stehe ich doch im Grunde wieder bei meinem Ausgangsszenario - bloß dass die Methoden nicht in einer sondern in vielen Klassen gekapselt sind.

                  @gfoidl:
                  Das Visitormuster sieht interessant aus - allerdings kann ich daran keinen Nutzen für meinen Fall erkennen. Die Datenklassen sollen keine Methoden haben, da sie über das Web transportiert werden. Das Visitormuster werde ich mir aber trotzdem für zukünftige Muster im Hinterkopf behalten.

                  Für weitere Anregungen wäre ich sehr dankbar.

                  Comment


                  • #10
                    Originally posted by Ralf Jansen View Post
                    @Gü: Äh Visitor-Pattern ist doch schon genau das was Theo macht(auch wenn er es nicht so nennt) SpeichereObjekt entspricht Reise.Accept in deinem Beispiel. Nur das er hier nicht alle besuchen will sondern nur ein spezielles Ziel. Oder übersehe ich etwas entscheidendes?
                    Nur dem Unterschied dass Theo für jedes Objekt den Typ prüfen muss und beim Besucher-Muster dies durch Double-Dispatch automatisch gelöst wird. Das ist auch der (einzige) Vorteil dieses Musters.


                    Die Datenklassen sollen keine Methoden haben, da sie über das Web transportiert werden.
                    Dazu könntest du mit Erweiterungsmethoden die Adaption durchführen bzw. das Muster mit Erweiterungsmethoden umsetzen.


                    mfG Gü
                    "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

                    Comment


                    • #11
                      Nur dem Unterschied dass Theo für jedes Objekt den Typ prüfen muss und beim Besucher-Muster dies durch Double-Dispatch automatisch gelöst wird. Das ist auch der (einzige) Vorteil dieses Musters.
                      Ich begreifs immer noch nicht. Sorry. Ist wohl nicht mein Tag

                      Folgender Code aus dem Pattern Beispiel. Von dem ich ausgehe das es der SpeichereObjekt Methode entspricht. Da beim speichern nicht alle Methoden aufgerufen werden sollen, sondern nur die eine spezielle die sich um den konkreten Typ kümmert, müsste er doch hier immer noch den konkreten Typ prüfen(mit einer ausufernden IF-Verzweigung) so wie jetzt auch anstatt dem foreach?

                      Code:
                      public class Reise : List<IVisitorElement>
                      {
                          public void Accept(ILandVisitor visitor)
                          {
                              foreach (IVisitorElement element in this)
                                  element.Accept(visitor);
                          }
                      }

                      Comment


                      • #12
                        Schau dir die Schnittstelle ILandVisitor (den Besucher) an. Die bietet für jedes konkrete IVisitorElement (also das was besucht werden soll) eine Methode in Form von Überladungen.

                        [highlight=c#]
                        element.Accept(visitor);
                        [/highlight]
                        rüft die konkrete Implementierung auf (Dispatch) zB die für AUT:
                        [highlight=c#]
                        public void Accept(ILandVisitor visitor)
                        {
                        visitor.Visit(this);
                        }
                        [/highlight]
                        Durch diesen Aufruf wird der konkreten Implementierung des ILandVisitors (Dispatch - also zum 2. mal und daher Double-Dispatch) mit this eine konkrete Instanz übergeben welche die entsprechende Überladung "auswählt".

                        Hoffe es ist ein wenig verständlich wie es funktioniert.

                        sondern nur die eine spezielle die sich um den konkreten Typ kümmert, müsste er doch hier immer noch den konkreten Typ prüfen(mit einer ausufernden IF-Verzweigung)
                        Die Prüfung des konkreten Typs muss nicht explizit erfolgen. Das geschieht während der Laufzeit durch die entsprechenden virtuellen Interface-Aufrufe und die Wahl der Überladung.

                        Du kannst auch mal mit dem Debugger durch das (einfache) Beispiel laufen und schauen wie wann was gedispatched wird. Wenn dir Erweiterungsmethoden ein Begriff sind dann ist deren Funktionsweise vergleichbar mit dem Muster.


                        Sollten die ursprünglichen Klasse nicht geändert werden dürfen stellen Erweiterungsmethoden ein gute Alternative/Ergänzung dar.


                        mfG Gü
                        "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

                        Comment


                        • #13
                          Ich habe mir das Beispiel von gfoidl mal zu Herzen genommen und das Prinzip der Erweiterungsmethoden ausprobiert. Gut ist, dass der Code zur Persistierung nicht in den Datenklassen steht, sondern außerhalb liegt.
                          Allerdings stehe ich jetzt tatsächlich wieder vor dem Problem, dass ich nicht weiß, welchen Typ ich übergeben muss - oder ich wende etwas falsch an.

                          Hier mal der Codeauszug meiner Erweiterungsmethoden:
                          [highlight=c#]namespace Erweiterungen
                          {
                          using Tour4UDBWCF;
                          using System;

                          public static class clsErweiterungen
                          {
                          public static bool SpeichereObjektInDB(this ITour4UObjekt myITour4UObjekt, clsKunde kunde)
                          {
                          return true;
                          }
                          public static bool SpeichereObjektInDB(this ITour4UObjekt myITour4UObjekt, clsMitarbeiter mitarbeiter)
                          {
                          return true;
                          }
                          }
                          }[/highlight]

                          Und hier der Auszug meiner Dienstklasse:
                          [highlight=c#]namespace Tour4UDBWCF
                          {
                          using Erweiterungen;
                          public class Tour4UDBDienst
                          {
                          public bool SpeichereObjekt(ref ITour4UObjekt MeinObjekt)
                          {

                          MeinObjekt.SpeichereObjektInDB(???);
                          }
                          }
                          }[/highlight]

                          Ich weiß nun, dass man einfach statische Erweiterungsmethoden ansprechen kann, aber wie mir das in meinem Beispiel hilft, habe ich noch nicht verstanden. Meine Datenklassen(clsKunde, clsMitarbeiter, usw.) haben nur einfache Properties. Die Schnittstelle ITour4UObjekt schreibt bisher nur Properties vor. Ich werde das Gefühl nicht los, dass ich deinen Vorschlag noch nicht in seiner Gänze begriffen habe.

                          Wie gebe ich dem System nun an, welches Objekt die Erweiterungsmethode bekommen soll? Und was gebe ich als Parameter an?
                          Zuletzt editiert von TheoFontane; 18.09.2009, 12:54.

                          Comment


                          • #14
                            Wie gebe ich dem System nun an, welches Objekt die Erweiterungsmethode bekommen soll? Und was gebe ich als Parameter an?
                            So wie du es gemacht hast must du wieder eine auf den richtigen Typen gecastete Version von MeinObject übergeben. Das bringt dich wenig weiter gegenüber der Ausgangsversion.

                            Richtiger wäre (richtiger nicht richtig, feiner Unterschied) :

                            Code:
                            public static bool SpeichereObjektInDB(this clsKunde kunde)
                            {
                                   return true;
                            }
                            Dann müsstest du aber vorher MeinObject auf den richtigen Typen casten und währst hier.

                            Code:
                            (clsKunde)MeinObjekt.SpeichereObjektInDB();
                            Auch nicht das was du willst.


                            Ich bin mir aber nicht sicher, ob du deinen Gedanken mit den "Speicherklassen" zu Ende gedacht hast.
                            Nun ja. Durchdacht war das nicht. Wie auch. Habe deine Frage gelesen und direkt geantwortet. Zum denken war da nicht viel Zeit Letztlich ist es eine allgemein Weisheit. Polymorphie über Methodenüberladung ist ein Compiletimefeature. Polymorphie durch Ableitung und Methodenüberschreibung ist ein Runtimefeature. Wenn du eine Entscheidung zur Laufzeit brauchst ist klar das dein Ansatz mit Methodenüberladung nicht funktionieren kann (egal ob als Extension Method oder als normale Methode) sondern das nur die Ableitung mit Methodenüberschreibung funktionieren kann. Wenn du letzteres nicht willst bleibt dir nur eine Lösung die so ähnlich ist wie deine Ausgangslösung oder du bastelst dir eine Methodenauflösung zur Laufzeit. Das geht wie bereits erwähnt nur per Reflection.


                            @Gü : Danke dir. Nachdem ich heute Morgen gemerkt habe das dein Beispiel aus mehreren Teilen besteht und ich mir auch den Rest angesehen habe wurde es klarer

                            Comment


                            • #15
                              Ok, wie es scheint werde ich den Kampf gegen unnötigen Quellcode aufgeben müssen. Immerhin wurde es durch die Erweiterungsmethoden etwas aufgeräumter:
                              [highlight=c#]public bool SpeichereObjekt(ref ITour4UObjekt MeinObjekt)
                              {
                              if (MeinObjekt is clsKunde) return ((clsKunde)MeinObjekt).SpeichereObjektInDB();
                              if (MeinObjekt is clsMitarbeiter) return ((clsMitarbeiter)MeinObjekt).SpeichereObjektInDB() ;
                              }[/highlight]


                              Leider konnte ich das Verfahren bisher nur auf die Speicherfunktion anwenden. Dort gibt es ja bereits ein instanziiertes Objekt. Was mache ich aber in der Ladefunktion? Ich dachte zuerst an eine Methode, die eine ID und einen Typ erwartet, aber damit bin ich nicht weiter gekommen.

                              Aktuell sieht es dort dank eines unflexiblen Enumerators grauslich aus:
                              [highlight=c#]public ITour4UObjekt LadeObjekt(int ID, enuKlassenArten enmKlassenArt)
                              {
                              switch (enmKlassenArt)
                              {
                              case enuKlassenArten.clsKunde:
                              return clsErweiterungen.LadeKundeAusDB(ID);
                              case enuKlassenArten.clsMitarbeiter:
                              return clsErweiterungen.LadeMitarbeiterAusDB(ID);
                              }
                              }[/highlight]

                              Fällt dazu noch jemandem eine elegantere Variante ein?
                              Zuletzt editiert von TheoFontane; 18.09.2009, 12:54. Reason: Code-Formatierung

                              Comment

                              Working...
                              X