Announcement

Collapse
No announcement yet.

Dynamische Typkonvertierung zur Laufzeit

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

  • #16
    Ok, wie es scheint werde ich den Kampf gegen unnötigen Quellcode aufgeben müssen. Immerhin wurde es durch die Erweiterungsmethoden etwas aufgeräumter:
    Ich hab vergessen warum du den Code von ITour4UObject nicht ändern willst/kannst, aber wenn das möglich wäre füge dort die Methoden hinzu und es gibt keine Probleme. Sollte das nicht gehen kann das Besucher-Muster angewendet werden. Dafür muss in der klassischen Version zu den Klassen eine Methode Accept hinzugefügt werden. Sollte dies auch nicht erwünscht sein dann kann eine Erweiterungsmethode verwendet werden. Wie Ralf oben angemerkt hat gibt es den Unterschied zwischen Compile- und Runtime.

    Durch Reflektion kann zur Runtime einiges angestellt werden. ZB anhand des aktuellen Typs eine Überladung einer Methode auswählen -> und das ist genau das Problem vor dem du stehst.

    Schau dir mal das Beispiel an. Wenn ich das Problem richtig verstanden habe ist das eine Elegante Lösung wenn der Quellcode von ITour4UObject nicht geändert werden soll:
    [highlight=c#]
    using System;
    using System.Collections.Generic;
    using System.Reflection;

    namespace ConsoleApplication1
    {
    class Program
    {
    static void Main(string[] args)
    {
    Kunde k = new Kunde();
    Mitarbeiter m = new Mitarbeiter();
    List<ITout4UObject> list = new List<ITout4UObject> { k, m };

    k.Save();
    m.Save();

    foreach (ITout4UObject item in list)
    item.Save();
    }
    }

    interface ITout4UObject { }
    class Kunde : ITout4UObject { }
    class Mitarbeiter : ITout4UObject { }

    static class MyExtensions
    {
    public static bool Save(this ITout4UObject tourObject)
    {
    Type t = typeof(MyExtensions);
    MethodInfo mi = t.GetMethod("Save", new Type[] { tourObject.GetType() });
    object result = mi.Invoke(null, new object[] { tourObject });

    return (bool)result;
    }

    public static bool Save(this Kunde kunde)
    {
    Console.WriteLine("Speicher Kunde.");
    return true;

    }
    public static bool Save(this Mitarbeiter mitarbeiter)
    {
    Console.WriteLine("Speichere Mitarbeiter.");
    return true;
    }
    }
    }
    [/highlight]
    Die if/switch Verzweigung wurde durch Reflektion ersetzt bei der die entsprechende Überladung zur Laufzeit anhand des vorliegenden konkreten Typs gewählt wird.


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

    Comment


    • #17
      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.
      Wie werden die Objekte in der Datenbank gespeichert?
      Welche Datenbank bzw. O/R-Mapper, DataSet?
      Wie werden die Objekte in der Datenbank unterschieden - d.h. welche Spalte kennzeichnet ob es ein Kunde oder Mitarbeiter ist - das nennt sich übrigens Diskriminator.

      Ohne das zu Wissen kann ich nur das Stichwort auf ein weiteres Entwurfsmuster geben: Fabriken

      Wegen der Muster siehst du also dass es solche Probleme schon einmal gab und daher wurden Musterlösungen entworfen die sich im Laufe der Zeit als sinnvoll herauskristalliersierten. Es schadet somit nicht wenn man die Ideen hinter den Mustern im Kopf hat denn dann muss man nicht immer alles neu erdenken sondern kann sich an die Musterlösungen orientieren - diese sollten als Inspiration verstanden werden und nach als Vorlage zum Kopieren, daher ist es nicht notwendig die Muster auswendig zu können -> eben nur die Idee und herangehensweise dahinter.

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

      Comment


      • #18
        BOAH WIE GEIL. Der Code ist ja der blanke Sex. DANKE!

        Sorry, aber genau das hatte ich gemeint. Einziger Wermutstropfen ist der ausgeschriebene Methodenname beim Reflection-Code, da so bei Code-Verschlüsselung oder genereller Namensänderung Schwierigkeiten entstehen (können).

        Die Ladeprozedur würde ich gerne analog zum Speichern aufsetzen, damit das Codebild halbwegs konsistent ist.

        Die Datenbank ist eine beliebige relationale Datenbank (Oracle oder MS SQL Express). Einen OR/Mapper verwende ich nicht. Ich weiß, dass es NHibernate gibt, aber das war in ersten Feldversuchen sehr umständlich zu konfigurieren. Hinzu kommt, dass ich mir nicht sicher bin, ob die Klassen noch vernünftig persistiert werden können, wenn sie erst einmal durch den Orbit (das Web) geschickt worden sind und zurück kommen.
        Wenn ich das richtig in Erinnerung habe, so gibt es im Grunde immer eine Schicht, die sich den Status der Objekte merkt und ihn verwaltet.

        Ich würde im Rahmen dieses Projekts gerne einfach eine analog zum Speichern verlaufende "LadeObjekt"-Methode schreiben, die eine ID und einen Klassentyp erwartet.

        In etwa so:
        Code:
        public ITour4UObjekt LadeObjekt(int ID, Type t)
        {
           return Erweiterungen.LadeObjektAusDB(ID, t.(???));
        }
        Wie ich aus dem übergebenen Typen t mit meinem neuen magischen Freund (der Reflection-Klasse) auf meine Erweiterungsmethoden komme, ist mir aber noch fremd.

        Muss ich die Signatur der Methode ändern? Oder verdrehe ich hier gerade etwas?

        Comment


        • #19
          Ich würde im Rahmen dieses Projekts gerne einfach eine analog zum Speichern verlaufende "LadeObjekt"-Methode schreiben, die eine ID und einen Klassentyp erwartet.
          Ist beim Laden der Typ schon bekannt. Wenn ja kannst du direkt die Methode aufrufen.

          Werden Kunde und Mitarbeiter in der selben Tabelle gespeichert oder getrennt?
          Wenn es die selbe Tabelle ist wie wird in der Tabelle zwischen Kunde und Mitarbeiter unterschieden?

          Ohne diese Info kann ich nichts sagen.

          Anmerkung: Der obige Code ist übrigens auch im oben verlinkten Blog-Artikel zu sehen.


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

          Comment


          • #20
            Alle Objekte werden in separaten Tabellen gespeichert.

            Ja, der zu ladende Typ ist bereits beim Laden bekannt. Wenn ich aber von außen direkt die Methode aufrufen will, so muss ich doch n*Methoden mit "public" kennzeichnen. Ich hätte lieber eine PUBLIC-Methode, die parametrisiert erkennt, welchen Typ sie zurück geben muss.

            Vielleicht sehe ich auch den Wald vor lauter Brettern vor dem Kopf nicht und es gibt eine simple Lösung, die ich nicht auf meiner Hand bemerke.

            Comment


            • #21
              Die n*Methoden brauchst du irgendwo sowieso. Also könntest du diese auch über eine Erweiterungsmethode zentral aufrufen lassen.

              Das alles sind aber nur Umwege und deshalb wäre das direkte Aufrufen einfacher. Es wird eine statische Methode zum Load benötigt - diese kann nicht zu Schnittstellen hinzugefügt werden. Daher eine abstrakte Basisklasse die ITout4Object implementiert und von der Kunde und Mitarbeiter erben...argh die sollen ja nicht geändert werden -> deshalb wäre besser zu erste entwerfen und dann coden


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

              Comment


              • #22
                Vielleicht können wir das ganze nochmal in eine etwas andere Richtung lenken. Ich habe schonmal davon gesprochen das du diese Methoden in einzelne Klassen auslagern solltest und Gü hat auch schonmal das Stichwort Fabrikpattern angebracht.

                Stell dir vor du hättest zu deinen ITour4UObjekt Klassen jeweils eine korrespondierende Klasse die das laden,speichern und sonstiges für diese Klasse erledigt. Nehmen wir an diese implementieren ein ITour4UObjektDBServiceClass Interface entsprechend folgendem Code.

                [Highlight=C#]public interface ITour4UObjektDBServiceClass
                {
                ITour4UObjekt LadeAusDB(string id);
                bool Save(ITour4UObjekt model);
                };[/Highlight]

                Wenn du zu einem ITour4UObjekt jetzt eine eindeutige ITour4UObjektDBServiceClass finden könntest hättest du kein Problem mehr einfach an dieser Klasse die entsprechenden Methoden aufzurufen. Wie finde ich eine entsprechende Klasse? Über eine Fabrik die mir wenn ich den konkreten Typ einer ITour4UObjekt Klasse reingebe eine konkrete ITour4UObjektDBServiceClass Klasse zurückgibt oder eben gleich das richtige an dieser Klasse ausführt (save, load etc.).

                Die folgende Klasse DBService würde so eine Fabrik implementieren und die gewünschte Funktion direkt ausführen.

                [Highlight=C#] public static class DBService
                {
                private static Dictionary<Type, Type> mapping =
                new Dictionary<Type, Type>();

                public static void Register<TTour4UObjekt, TTour4UObjektDBServiceClass>()
                where TTour4UObjekt : ITour4UObjekt
                where TTour4UObjektDBServiceClass : ITour4UObjektDBServiceClass
                {
                mapping.Add(typeof(TTour4UObjekt), typeof(TTour4UObjektDBServiceClass));
                }

                public static ITour4UObjekt Load<T>(int id)
                {
                return GetDBServiceClass(typeof(T)).LadeAusDB(id);
                }

                public static bool Save(ITour4UObjekt model)
                {
                return GetDBServiceClass(model.GetType()).Save(model);
                }

                private static ITour4UObjektDBServiceClass GetDBServiceClass(Type type)
                {
                Type dBserviceClassType = mapping[type];
                return (ITour4UObjektDBServiceClass)Activator.CreateInsta nce(dBserviceClassType);
                }
                }[/Highlight]

                zu benutzen etwa so

                [Highlight=C#]
                // Registrierungen irgendwann beim Programmstart
                DBService.Register<clsKunde, clsKundeDBServiceClass>();
                // .... weitere Klassen

                // laden
                int id = 16;
                ITour4UObjekt einKunde = DBService.Load<clsKunde>(id);

                // speichern
                bool result = DBService.Save(einKunde);
                [/Highlight]

                Vorteile :
                - Du hast die verschiedenen Aspekte halbwegs ordentlich getrennt, heißt deine einzelnen Klassen bleiben vom Umfang überschaubar.
                - Wenn du eine neue ITour4UObjekt schreibst musst du nur eine neue TTour4UObjektDBServiceClass schreiben und registrieren. Die Fabrik bleibt unverändert.


                Disclaimer : Diese Code hat nie einen Compiler gesehen. Kein Gewähr auf Funktion. Jedes Exceptionhandling fehlt absichtlich!

                Comment


                • #23
                  Sauberer Ansatz/Lösung

                  Anmerkung:
                  FormatterServices.GetUninitializedObject(myType); ist um Ecken schneller als Activator.CreateInstance


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

                  Comment


                  • #24
                    @Gü :
                    Hast du FormatterServices.GetUninitializedObject schonmal sinnvoll eingesetzt?
                    Der ist schneller weil er keinen Konstruktor aufruft, das kann in den meisten Fällen nicht gut sein

                    Comment


                    • #25
                      Originally posted by Ralf Jansen View Post
                      @Gü :
                      Hast du FormatterServices.GetUninitializedObject schonmal sinnvoll eingesetzt?
                      Der ist schneller weil er keinen Konstruktor aufruft, das kann in den meisten Fällen nicht gut sein
                      Nein, nur probiert.

                      Aber durch den von dir genannten Grund scheidet er für die meisten Fälle aus. Danke für dieses (wichtigen) Hinwei.


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

                      Comment


                      • #26
                        Wow... ich habe dein Architekturmuster übernommen und nun das Gefühl, dass die Lösung zum Greifen nahe ist. Auch den FormatterServices-Dienst habe ich in Anspruch genommen.

                        Jetzt gibt es nur noch einen offenen Punkt:

                        In der statischen Datenbankdienstklasse meldet mir der Compiler bei der Methode "Register" den Fehler "Einschränkungsringabhängigkeit zwischen ITour4UObjekt und ITour4UObjekt". Die selbe Meldung erscheint noch einmal für ITour4UObjektDBServiceClass.

                        Da mir dieses Konzept völlig neu ist, wäre ich dankbar, wenn mir noch ganz kurz jemand
                        a) den Fehler mit der Ringabhängigkeit entfernt und
                        b) einen ganz kurzen Satz zu den Where-Klauseln verliert.

                        Es fehlt nur noch ein Schritt bis zur Perfektion! Vielen Dank bei allen Beteiligten für die konstruktiven Beiträge! Ich werde mir unbedingt mehr Zeit für Architekturmuster und Generik nehmen müssen.


                        EDIT: Den Fehler habe ich selber gefunden: Die Forumsschriftart ließ das T wie ein I aussehen.

                        Comment


                        • #27
                          Zeig mal deinen Aufruf der Register Methode und die Signatur der beiden Klassen die du da übergeben hast.


                          Edit zu b.) Man übergibt ja bei generics Typen. Erstmal wäre so jeder Typ erlaubt. Mit der Where Bedingung kann man die erlaubten Typen weiter einschränken. Eben zum Beispiel das sie ein bestimmtes Interface implementieren müssen. Man könnte aber auch zum Beispiel einschränken das der Typ einen bestimmten Konstruktor haben muß.
                          Bei der Bedingung für das ITour4UObjektDBServiceClass Interface wäre das sogar sinnvoll da wir die ja irgendwo instanziieren wollen. Man sollte die so erweitern

                          where TTour4UObjektDBServiceClass : ITour4UObjektDBServiceClass, new()

                          dann ist klar das die Klasse einen Defaultkonstruktor braucht. Beim FormatterServices wäre das unnötig der ruft ja keinen Konstruktor auf
                          Zuletzt editiert von Ralf Jansen; 17.09.2009, 16:38.

                          Comment

                          Working...
                          X