none
Polymorphismus und Aufruf spezieller Methoden RRS feed

  • Frage

  • Hallo NG,

    ich habe 3 Klassen (sagen wir class BaseClass, Derived1 abgeleitet von BaseClass, Derived2

    abgeleitet von BaseClass).

    Objektbildung:

    Je nach Programm-Situation werden die Objekte gebildet:

    BaseClass baseObject = new Derived1();

    oder eben

    BaseClass baseObject = new Derived2();

    Die virtuellen Methoden werden wie erwartet richtig aufgerufen, das passt soweit.

    Problem:

    Wie werden jetzt die speziellen Methoden von Derived1 oder Dervid2 aufgerufen?

    Man kann doch nicht alle speziellen Methoden als virtuelle "leere" Huellen in die

    Basisklassen aufnehmen, damit diese dann auch aufgerufen werden koennen.

    Man kann natuerlich auch spezielle Objekte bilden (Derived1 der1 = new Derived1())

    aber dann ist doch der Sinn von Polymorphismus wieder dahin.

    Wie macht man es dann richtig?

    Vielen Dank fuer Tipps und Vorschlaege.

    Gruss, myamadeus

    Donnerstag, 6. Juni 2013 08:17

Antworten

  • Hallo myamadeus,

    im wesentlichen ist das genau so. Ich möchte nur folgendes abschließend klären:

    Upcasting und Downcasting sind OOP-Begriffe, die semantisch mit den Begriffen superclass (Basisklasse, in UML oben dargestellt) und subclass (abgeleitete Klasse, in UML unter der superclass dargestellt) zusammenhängen:


    Beim Upcasten wird ein Objekt eines abgeleiteten Typs in ein Objekt seiner Basisklasse konvertiert.

    BaseClasse bc = new Derived1(); 

    Beim Downcasten (in C# immer ein code smell) wird ein Objekt der Basisklasse in ein abgeleitetes Objekt konvertiert:

    // Läßt sich nicht kompilieren
    BaseClass bc = new BaseClass();
    Derived1 d1 = (Derived1)((object)bc);
    
    // Dies geht, "stinkt" aber gewaltig
    BaseClass bc = new Derived1();
    Derived1 d1 = (Derived1)bc;

    Beim von dir angesprochenen Snippet:

    BaseClass baseObjekt = new Derived1();

    handelt es sich - in C#-Termini diesmal - um eine implizite Verweiskonvertierung (reference conversion).

    Eine implizite Verweiskonvertierung ist immer erfolgreich und *verändert nicht die referentielle Identität* des konvertierten Objekts. Dies kann über Object.ReferenceEquals(bc, d1) überprüft werden. Nur der Verweistyp (Typ der Referenz) wird dabei geändert, so dass man nach erfolgter Verweiskonvertierung nur noch über die Schnittstelle des neuen Typs auf das Objekt zugreifen kann.

    Gruß
    Marcel

    Donnerstag, 6. Juni 2013 14:41
    Moderator
  • Hi,
    ich meinte eine Lösung etwa so:

    using System;
    
    namespace ConsoleApplication1
      {
      class Program
        {
        static void Main(string[] args)
          {
    
          RezeptUnsercontrol1 c1 = new RezeptUnsercontrol1();
          var res = c1.Execute<RezeptManager1>("Aufruf 1");
    
          Console.WriteLine(res);
          Console.ReadKey();
          }
        }
    
    
      public class UserControlBasis
        {
        public string Execute<T>(string par)
          {
          var c = this.GetInstance<T>();
          return c.GetString(par);
          }
        public static RezeptManagerBasis manager { get; set; }
        }
    
      public static class Extensionclass
        {
        public static RezeptManagerBasis GetInstance<T>(this UserControlBasis uc)
          {
          return (RezeptManagerBasis)Activator.CreateInstance(typeof(T));
          }
        }
    
      public class RezeptUnsercontrol1 : UserControlBasis
        {
        }
    
      public class RezeptUnsercontrol2 : UserControlBasis
        {
        }
    
      public class RezeptManagerBasis
        {
        public string GetString(string par)
          {
          return string.Format("Typ: {0} - {1}",this.GetType(), par);
          }
        }
    
      class RezeptManager1 : RezeptManagerBasis
        {
        }
    
      class RezeptManager2 : RezeptManagerBasis
        {
        }
      }
    
    

    --
    Viele Gruesse
    Peter

    Freitag, 7. Juni 2013 20:47

Alle Antworten

  • Hallo,

    lies mal Kapitel 4 dieses Buches, dann wirst du Polymorphie besser verstehen.

    Gruss,

    LBB

    Donnerstag, 6. Juni 2013 08:50
  • Hallo LittleBlueBird,

    danke fuer den Link, aber das hilft mir nicht weiter. Die Theorie dazu ist mir bekannt, habe

    auch schon etliche Buecher dazu gelesen.

    Was ich suche betrifft mehr die Objektbildung zur Laufzeit, also wann nehme ich ein Objekt

    der Basisklasse und wann das spezielle Objekt der Subklasse?

    Verwende ich ein Objekt der Basisklasse als Upcast, dann kann ich Polymorphie einsetzen,

    komme aber nicht an die speziellen Methoden der Subklasse ran.

    Nehme ich ein spezielles Subclass Objekt, dann ist der Vorteil des Polymorphismus dahin.

    Ich feue mich auf weitere Tipps.

    Gruss, myamadeus

    Donnerstag, 6. Juni 2013 09:15
  • hi myamadeus,

    Wieso soll der Polymorphismus hin sein?

    du rufst die speziellen funktionen der Derived klassen auf.

    DerivedObject.myfunc();

    Wenn du in der Basisklasse bist kannst du natürlich nicht (zumindestens nicht sauber)  auf die Abgeleiteten funktionen zugreifen.

    Die Dirtylösung du castest this in der BaseClass auf DerivedClass....

    Wenn du aber eine Basisfunktion in der Ableitung explizit aufrufen willst dann musst du base.myfunc() aufrufen...

    Vielleicht konkretesierst du dein Problem anhand eines Beispieles dann sieht man leichter was du willst...

    Donnerstag, 6. Juni 2013 09:46
  • Hallo Brian Dahl,

    >Wieso soll der Polymorphismus hin sein?

    Weil ich ein Upcast Objekt habe, also sowas:

    BaseClass baseObjekt = new Derived1();

    Damit kannst du NICHT! die speziellen Methoden der abgeleiteten Klasse

    aufrufen, wenn man nicht auf die Subklasse castet!

    Ich will ja nicht hundert verschiedene Objekte erzeugen, halt eins mit dem

    Basisverhalten.

    Was ich braeuchte ist so ne Art Instance-Manager, der mir entweder ein BaseClass-Objekt oder

    ein SubClass-Objekt erzeugt, je nach Programm-Situation.

    >Die Dirtylösung du castest this in der BaseClass auf DerivedClass....

    Du sagst es, ist wirklich ne Dirtyloesung.

    >Vielleicht konkretesierst du dein Problem anhand eines Beispieles dann sieht man leichter was du

    Ok ich probiers mal.

    Es gibt 2 Rezept-Usercontrols, die von einer gemeinsamen Usercontrol-Basisklasse erben.

    Diese Usercontrols rufen von einer Rezept-Assembly Methoden eines Rezept-Managers auf

    (der besteht auch aus einer Basisklasse und 2 Spezialisierungen). Je nach Rezepttyp (respektive Usercongtrol) wird dann

    ein Upcast-Objekt des REzept-Mangers erzeugt. Es gibt manchmal aber Programmsituationen,

    da will man auf spezielle Methoden des Rezeptmanagers zugreifen. Das geht dann aber mit dem

    Upcast-Objekt nicht mehr, man muesste dann ein spezielles SubClass-Objekt bilden und benutzen.

    Das ist im Programmablauf aber stoerend, man will ja nicht immer Fallunterscheidungen haben,

    ob es nun RezeptTyp1 oder RezeptTyp 2 ist.

    Also wenn es jetzt nicht verstanden wird, muß ich das ganze Programm posten.

    Gruß, myamadeus.

    Donnerstag, 6. Juni 2013 10:11
  • Hallo myamadeus,

    Es ist wichtig zu verstehen, dass ein abgeleitetes Objekt, z.B. eine Instanz von Derived1, gleichzeitig eine Instanz der Basisklasse ist. Derived1 kapselt keine Instanz von BaseClass, Derived1 hat auch sonst keine "Beziehung" (über Referenzen, Pointer etc.) zu BaseClass, nein, Derived1 ist BaseClass (und gleichzeitig mehr).

    Im Laufzeitobjekt von Derived1 werden also zuerst die Felder von BaseClass in der Reihenfolge ihrer Deklaration zu finden sein, dann die eigenen Felder. Zuerst werden diese Felder initialisiert, dann werden die Konstruktoren von BaseClass und DerivedClass in exakt dieser Reihenfolge aufgerufen. Um dir klar zu machen wie sehr Derived1 gleichzeitig BaseClass ist, ein kleines Experiment: Sollte die Basisklasse irgendwelche virtuellen Methoden im Basisklassenkonstruktor aufrufen (bad practice), dann werden die Overrides in Derived1 (!) aufgerufen noch bevor Derived1 zu Ende konstruiert ist:

    class BaseClass {
        public BaseClass() {
            // Nicht nachmachen: bad practice
            Foo();
        }
    
    public virtual void Foo() { Console.WriteLine("BaseClass.Foo()"); } }
    class Derived1 : BaseClass { public override void Foo() { Console.WriteLine("Derived1.Foo()"); } } // Test: Was wird im Konsolenfenster angezeigt? Derived1 d1 = new Derived1(); // Antwort: Derived1.Foo()

    Wenn Derived1.Foo() auf Initialisierung angewiesen ist, die im Derived1-Konstruktor ausgeführt wird, dann wurde die Methode oben noch vor der Initialisierung aufgerufen!

    Es sollte bisher zumindest klar sein, dass BaseClass nicht außerhalb von Derived1 zu suchen ist, sondern ein Teil davon.

    Wann nimmst Du die Basisklasse? - Dann wenn Du die Overrides und Neuimplementierungen der abgeleiteten Klasse nicht brauchst, sondern mit dem Grundstock an Funktionalität zurechtkommst, die die Basisklasse anbietet. Hast Du z.B. eine Methode, welche dir die Art zu der ein Tier gehört auf der Konsole ausgibt, brauchst Du nicht für jede Subklasse der Klasse Animal eine Ausgabemethode zu definieren, sondern einfach nur:

    private void PrintSpecies(Animal animal) {
       Console.WriteLine(animal.Species);
    }

    Du kannst jetzt der Methode beliebige von Animal abgeleitete Instanzen übergeben: Monkey, Giraffe etc., denn jede dieser Laufzeitinstanzen ist ein Animal.

    Im Gegenzug verwendest Du dann eine Ableitung, wenn die in der Basisklasse gekapselten Daten und Verhalten für die speziellen Zwecke unzureichend sind. Eine Monkey-Klasse könnte die Klasse z.B. Animal um die Methode Monkey.Climb() erweitern. Das ist eine Verhaltens-Spezialisierung, die wir weder von einem Animal noch von einer Giraffe erwarten.

    Das Laufzeitobjekt, dass Du mit new Derived1() erstellst, behält auch dann den Laufzeittyp Derived1() wenn Du es einer BasisClass-Variablen zuweist!

    Derived1 d1 = new Derived1();
    BaseClass bc = d1;
    Console.WriteLine(bc.GetType());
    // Gibt "ConsoleApplication1.Derived1" aus

    Obwohl der Laufzeittyp der Variablen bc noch immer Derived1 ist, können wir nicht mehr auf die öffentlichen Member von Derived1 zugreifen, wir sind nun ganz auf die von BaseClass definierte engere Schnittstelle limitiert. Und zwar so sehr, dass wir bc nicht einfach wieder an d1 zurückzuweisen können. Möchten wir das, dann müssen wir eine explizite Konvertierung vornehmen:

    Derived1 d2 = (Derived1) bc;

    Polymorphismus kommt aus dem Griechischen und bedeutet "vielförmig". Unsere Klasse Derived1 ist vielförmig, hat ein Janus-Gesicht, kann mal als Basisklasse mal als Ableitung auftreten, denn sie ist beides.

    Virtuelle Methoden der Basisklasse können über override in der Ableitung überschrieben werden, sie können in der abgeleiteten Klasse neu eingeführt (ausgeblendet) werden (über new), und zeigen so eine vielförmige, polymorphe Gestalt in der Ableitung. Selbst wenn der Laufzeittyp der Basisklassenvariablen bc im obigen Beispiel noch immer Derived1 ist, können wir die Vorteile des Polymorphismus nicht mehr nutzen, da wir die Funktionalität von Derived1 auf die der Basisklasse ganz bewußt eingeschränkt haben.

    P.S. Niemand hindert dich:

    dynamic d = bc;
    d.[OverridenMethod]();

    aufzurufen. Du begibst dich aber damit auf einem gefährlichen Terrain, wo Du nicht nur Intellisense verlierst, sondern auch einen guten Teil der Typsicherheit für die wir alle C# mögen.

    Gruß
    Marcel


    Donnerstag, 6. Juni 2013 10:37
    Moderator
  • Hallo Marcel,

    vielen Dank fuer die ausfuehrliche Antwort.

    Ist alles richtig und schoen erklaert, ich muss es erstmal alles verarbeiten :-)

    Ok, was ich fuer mich jetzt (auch Dank Deiner Hilfe) als logisch erklaert:

    1. Komme ich mit der Funktionalitaet der Basisklasse aus, dann langt ein

    Upcast-Objekt (verdammt wie heisst das Ding eigentlich richtig?).

    Also:

    BaseClass baseObjekt = new Derived1() oder new Derived2().

    Damit nutze ich die Funktionalitaet der Basisklasse inklusive der Vorteile der virtuellen Funktionen

    die (evtl) von der Subklasse ueberschrieben wurden.

    2. Brauche ich aber unbedingt die Methoden der Spezialisierungen, dann fuert kein Weg dran vorbei und ich muss ein NEUES! Objekt der Subklasse erzeugen oder das vorhandene auf

    den Spezialtyp casten:

    Derived1 derived1 = new Derived1();

    oder

    ((Derived1)baseObject).SpecialDerivedMethod().....

    Ich hoffe, ich habe es so richtig verstanden.

    Danke an alle!

    Gruss, mymadeus.

    Donnerstag, 6. Juni 2013 11:22
  • Hallo myamadeus,

    im wesentlichen ist das genau so. Ich möchte nur folgendes abschließend klären:

    Upcasting und Downcasting sind OOP-Begriffe, die semantisch mit den Begriffen superclass (Basisklasse, in UML oben dargestellt) und subclass (abgeleitete Klasse, in UML unter der superclass dargestellt) zusammenhängen:


    Beim Upcasten wird ein Objekt eines abgeleiteten Typs in ein Objekt seiner Basisklasse konvertiert.

    BaseClasse bc = new Derived1(); 

    Beim Downcasten (in C# immer ein code smell) wird ein Objekt der Basisklasse in ein abgeleitetes Objekt konvertiert:

    // Läßt sich nicht kompilieren
    BaseClass bc = new BaseClass();
    Derived1 d1 = (Derived1)((object)bc);
    
    // Dies geht, "stinkt" aber gewaltig
    BaseClass bc = new Derived1();
    Derived1 d1 = (Derived1)bc;

    Beim von dir angesprochenen Snippet:

    BaseClass baseObjekt = new Derived1();

    handelt es sich - in C#-Termini diesmal - um eine implizite Verweiskonvertierung (reference conversion).

    Eine implizite Verweiskonvertierung ist immer erfolgreich und *verändert nicht die referentielle Identität* des konvertierten Objekts. Dies kann über Object.ReferenceEquals(bc, d1) überprüft werden. Nur der Verweistyp (Typ der Referenz) wird dabei geändert, so dass man nach erfolgter Verweiskonvertierung nur noch über die Schnittstelle des neuen Typs auf das Objekt zugreifen kann.

    Gruß
    Marcel

    Donnerstag, 6. Juni 2013 14:41
    Moderator
  • Hallo Marcel,

    super, prima erklaert!

    Danke nochmals.

    GRuss, myamadeus.

    Donnerstag, 6. Juni 2013 15:24
  • Hi,
    so wie ich es verstanden habe, reicht Dir eine Methode, der der Typ mitgeteilt wird (z.B. mit GetType), und die dann eine (beispielsweise überschriebene) Methode, ggf. über einen Delegaten, aufruft.
     
    --
    Peter Fleischer
    Donnerstag, 6. Juni 2013 19:26
  • Hallo Peter,

    sorry, hatte Dich uebersehen.

    Du meinst sowas?:

    object oMyObject = new Derived1();
    Type t = typeof(Derived1);
    MethodInfo testMethod = t.GetMethod("Test");
    testMethod.Invoke(oMyObject, null);

    Ich denke, dass muesste auch klappen.

    Danke fuer den Hinweis.

    Gruss, myamadeus.

    Freitag, 7. Juni 2013 14:03
  • Hi,
    ich meinte eine Lösung etwa so:

    using System;
    
    namespace ConsoleApplication1
      {
      class Program
        {
        static void Main(string[] args)
          {
    
          RezeptUnsercontrol1 c1 = new RezeptUnsercontrol1();
          var res = c1.Execute<RezeptManager1>("Aufruf 1");
    
          Console.WriteLine(res);
          Console.ReadKey();
          }
        }
    
    
      public class UserControlBasis
        {
        public string Execute<T>(string par)
          {
          var c = this.GetInstance<T>();
          return c.GetString(par);
          }
        public static RezeptManagerBasis manager { get; set; }
        }
    
      public static class Extensionclass
        {
        public static RezeptManagerBasis GetInstance<T>(this UserControlBasis uc)
          {
          return (RezeptManagerBasis)Activator.CreateInstance(typeof(T));
          }
        }
    
      public class RezeptUnsercontrol1 : UserControlBasis
        {
        }
    
      public class RezeptUnsercontrol2 : UserControlBasis
        {
        }
    
      public class RezeptManagerBasis
        {
        public string GetString(string par)
          {
          return string.Format("Typ: {0} - {1}",this.GetType(), par);
          }
        }
    
      class RezeptManager1 : RezeptManagerBasis
        {
        }
    
      class RezeptManager2 : RezeptManagerBasis
        {
        }
      }
    
    

    --
    Viele Gruesse
    Peter

    Freitag, 7. Juni 2013 20:47
  • Hallo Peter,

    danke fuer die Antwort.

    Ja, ich denke so was hab ich gesucht! Einen Rezept-Instance manager,

    der mir dynamisch immer das richtige Objekt zu dem entsprechenden Usercontrol

    erzeugt.

    Prima!

    Gruß, myamadeus.

    Montag, 10. Juni 2013 11:27