Laden...

Zugriff auf Methoden generischer Objekte

Erstellt von DNS46 vor 2 Jahren Letzter Beitrag vor 2 Jahren 473 Views
D
DNS46 Themenstarter:in
21 Beiträge seit 2020
vor 2 Jahren
Zugriff auf Methoden generischer Objekte

Hallo zusammen, mich würde interessieren ob es eine Möglichkeit/ BestPraxis gibt, mit der man Attribute/Methoden eines generischen Objektes zugreifbar machen kann.

Hier ein Beispiel:
Mittels SQL Result soll eine Liste mit PersonenInfos befüllt werden ... hier zunächst typisiert:


static bool reader(MySqlConnection connection, string query, out List<Person> resultset) {
   bool returnvalue = false;
   resultset = new List<Person>();

   MySqlCommand command = new MySqlCommand(query, connection);
   MySqlDataReader reader = null;
   try {
      reader = command.ExecuteReader();
      if (reader.HasRows) {
         while (reader.Read()) {
            resultset.Add(new Person(reader.GetString(0), reader.GetString(1), reader.GetString(2)));
         }
      }
      returnvalue = true;
   }
   catch (Exception e) { log.Error(e); }
   finally {
      connection.Close();
      reader.Close();
   }
   return returnvalue;
}

So - das geht ... Aber geht das anstatt mit dem Personen Objekt auch wie folgt? ...


static bool reader<TType>(MySqlConnection connection, string query, out List<TType> resultset) {
   bool returnvalue = false;
   resultset = new List<TType>();

   MySqlCommand command = new MySqlCommand(query, connection);
   MySqlDataReader reader = null;
   try {
      reader = command.ExecuteReader();
      if (reader.HasRows) {
         while (reader.Read()) {
            //an der Stelle wird's für mich unlogisch ... der generische Typ kann den Konstruktor von Person ja nicht kennen. 
            resultset.Add(new TType(reader.GetString(0), reader.GetString(1), reader.GetString(2)));
         }
      }
      returnvalue = true;
   }
   catch (Exception e) { log.Error(e); }
   finally {
      connection.Close();
      reader.Close();
   }
   return returnvalue;
}

Weiß jemand ob sich das lösen lässt .... und ob man dabei noch Sicherheit um die Position/Anzahl der Methoden-Argumente hinbekommt?

Danke für Denkanstöße 🙂

2.080 Beiträge seit 2012
vor 2 Jahren

Das geht nur mit Reflection.
Du suchst dann per Reflection die Properties oder den Konstruktor, den Du brauchst und arbeitest damit.

Oder Du erfindest ein Interface, dem Du eine Methode gibst, das Name und Wert bekommt.
Die Implementierung muss dann je nach Entity separat schauen, was das ist und wie man es setzt.

Aber was Du versuchst, wurde schon zig Mal entwickelt, nennt sich ORM.
Z.B. EFCore, nHibernate oder Dapper.
EFCore ist vermutlich der modernste ORM, wo nHibernate heute steht, weiß ich nicht.
Dapper ist sehr einfach gestrickt und beschränkt sich auf das Mapping (das, was Du versuchst), während EFCore und nHibernate die komplette Datenbank-Schicht abbilden können, sodass am Ende gar kein (oder kaum) SQL mehr notwendig ist.

Wenn es dir nur um den Spaß an der Freude oder Übung geht, dann liegt es bei dir, was Du versuchst.
Produktiv würde ich aber immer auf einen der ORM setzen, entweder EFCore oder Dapper, je nach Anforderungen.
Eine eigene Reflection-Lösung würde ich nicht mehr entwickeln, sowas ufert schnell aus und in Dapper gibt's das ja schon.
Und die Interface-Lösung ist sehr einfach, aber auch unflexibel und langfristig aufwändiger.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

D
DNS46 Themenstarter:in
21 Beiträge seit 2020
vor 2 Jahren

Danke Palladin007

mit Reflections komm ich an fast alle Methoden der Klasse. Damit könnte via Iteration über die Klassentypen das richtige Objekt mit korrekter Parametrisierung bestimmt werden ... jedoch liefert classtype.GetMethods() ausgerechnet den Konstruktor nicht ... das bekomm ich aber bestimmt noch raus 🙂


Type classtype = typeof(TType);
System.Reflection.MethodInfo[] methods = classtype.GetMethods();
foreach (System.Reflection.MethodInfo method in methods) {    
if("Person" == method.Name) {  
    ...
}

4.942 Beiträge seit 2008
vor 2 Jahren

Dafür gibt es Type.GetConstructors 😉

Edit: Und mit Activator.CreateInstance kannst du dann dynamisch Objekte erzeugen (mit den Parametern als Object[]).

D
DNS46 Themenstarter:in
21 Beiträge seit 2020
vor 2 Jahren

Cool danke. Die API erschlägt oft ein wenig 🙂
Das wäre damit eine mögliche Implementierung.


Person person = createGeneric<Person>(typeof(Person));


public T createGeneric<T>(Type type) {                    
   object[] parameter = null;
   switch (type.Name) {
      case "Person":                    
         parameter = new object[] { "Anna", "20" };                   
         break;
   }
   if (null != parameter) {
      return (T)Activator.CreateInstance(type, parameter);
   }
   return default(T);
}

Nebensächlich aber eine Sache würde mich noch interessieren. Das Attribut Name in ConstructorInfo[] enthält nicht den echten Namen der Klasse sondern ".ctor" das fiel mir in diversen FileLogger-Ausgaben anderer Applikationen auch schon auf. Weiß jemand warum das so ist?

4.942 Beiträge seit 2008
vor 2 Jahren

.ctor sowie .cctor (für den statischen Klassenkonstruktor) sind die reservierten Namen in der [Common] Intermediate Language ([C]IL).
Für C# hat man sich entschieden aber stattdessen den Klassennamen im Source zu verwenden (welcher dann entsprechend beim Kompilieren ersetzt wird).

2.080 Beiträge seit 2012
vor 2 Jahren

Was Du vor hast, wird so funktionieren, langfristig aber zu Problemen führen 😉
Du machst damit die Klassen von den in einer Methode fix definierten Parameter abhängig und musst bei jeder Änderung daran denken, dass Klasse und Reflection-Code noch zueinander passen müssen.
Zum Spielen und Reflection kennen lernen, ok, aber sobald daraus ein tatsächliches Projekt wird, solltest Du lieber andere Wege suchen.

Für Datenbanken wären das z.B. EFCore, das hat das gleiche Problem, hat aber auch viel Logik nur dafür, dass diese komplexen Reflection-Zusammenhänge genau so funktionieren, wie man es erwarten würde.
Für alles Weitere könnte man auf Konzepte wie das Factory-Pattern zurückgreifen oder sein Vorhaben überdenken, dass man gar keine dynamisch erzeugten Instanzen braucht.
Selbst implementierte Reflection würde ich immer möglichst vermeiden oder wenigstens klein halten.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

D
DNS46 Themenstarter:in
21 Beiträge seit 2020
vor 2 Jahren

Ja ganz ideal ist es so nicht. Da eigentlich nur die Anzahl der abzufragenden Attribute der SQL Query mit der Anzahl der Argumente des aufzurufenden Konstruktors übereinstimmen müssen, bin ich dazu umgeschwenkt diese Anzahl noch mit an die Methode zu übergeben. Das erspart die ständige Erweiterung der generischen Methode selbst. Danke für eure Tipps – hat mir echt super weitergeholfen 🙂 Aktuell denke ich mit Reflections gut leben zu können … schaut ja recht übersichtlich aus.

4.942 Beiträge seit 2008
vor 2 Jahren

Mir ist jetzt erst aufgefallen, daß deine createGeneric<T>-Methode so aber keinen Sinn ergibt!
Zum einen gibst du doppelt den Typparameter an, einmal als generischen Typ T und dann noch als Parameter: mittels typeof(T) kommt man auch über den generischen Parameter an den Type.
Und zum anderen ist auch der restliche Code so überflüssig: so könntest du gleich new Person(parameter) verwenden.

Ich meinte eher, daß du mittels Type.GetConstructors die Parameter dynamisch ermittelst (Anzahl und Datentypen) und dann das Parameter-Array aus den Datenbank-Daten entsprechend erzeugst (dabei muß dann aber die Reihenfolge der Parameter stimmen). Und damit rufst du dann einfach Activator.CreateInstance auf.

Statt dem parametrisierten Konstruktor wären hier aber auch Eigenschaften möglich.

Persönlich würde ich der reader<T>-Methode einfach ein Delegate (Func<MySqlDataReader, T>) als Parameter mitgeben, und der Aufrufer übernimmt das Erzeugen der Klassenobjekte:


reader<Person>(connection, query, out persons, reader => new Person(reader.GetString(0), reader.GetString(1), reader.GetString(2));

In deinem konkreten Falle ist aber Palladin007's Vorschlag bzgl. ORM (Object-Relational Mapper) auch überlegenswert, anstatt selber den Code dafür zu schreiben.

PS: Methodennamen sollten (laut .NET Coding-Guide) mit einem Großbuchstaben anfangen.

D
DNS46 Themenstarter:in
21 Beiträge seit 2020
vor 2 Jahren

akt. schaut die methode so aus:


public static List<T> genericReader<T>(DBConnection dbconnection, string query, int attributes)
        {          
            List<T> resultset = new List<T>();           
            Type type = typeof(T);
            object[]  arguments = new object[attributes];           
            
            MySqlConnection connection = dbconnection.getConnection();
            if (!dbconnection.openConnection())
            {
                log.Error("not able to connect to database");
                return default(List<T>);
            }
            MySqlCommand command = new MySqlCommand(query, connection);
            MySqlDataReader reader = null;
            try
            {
                reader = command.ExecuteReader();
                if (reader.HasRows)
                {
                    while (reader.Read())
                    {
                        if (reader.FieldCount == attributes)
                        {
                            for (int i = 0; i < reader.FieldCount; i++)
                            {
                                if (!reader.IsDBNull(i))
                                {
                                    arguments[i] = reader.GetString(i);
                                }
                                else
                                {
                                    arguments[i] = string.Empty;
                                }
                            }
                        }
                        else
                        {
                            return default(List<T>);
                        }
                        T obj = (T)Activator.CreateInstance(type, arguments);//destination class can not have a list type in its constructor 
                        resultset.Add(obj);
                    }
                }
            }
            catch (Exception e)
            {
                resultset = default(List<T>);
                log.Error(e);
            }
            finally
            {
                connection.Close();
                reader.Close();
            }
            return resultset;
        }

Im Falle von mehreren Konstruktoren gäbe es in der Tat ein Problem 🙁
Besonders wenn 2 noch die gleiche Anzahl an Paramenten hätten.
Type.GetConstructors scheint mir aber gar keine Hilfe da hier keinerlei Info über die Parameter des Konstruktors zurückgegeben werden. Bietet Activator.CreateInstance denn eine Möglichkeit einen bestimmten Konstruktor der Klasse aufzurufen? Bisher hatte ich im Test nur einen Konstruktor verwendet.

16.842 Beiträge seit 2008
vor 2 Jahren

Was Du da anfängst ist ein riesen Rattenschwanz. Du wirst dutzende Edge Cases haben, die Du alle abfangen musst, wenn Du das Rad wirklich neu erfinden willst.
Konstruktoren sind generell ein Problem, weshalb es bei Entitäten hier auch die Regel gibt: leerer Konstruktor als Default ist pflicht.

D
DNS46 Themenstarter:in
21 Beiträge seit 2020
vor 2 Jahren

dämmert mir nun auch langsam 😐
Dann bin ich jetzt doch soweit mich euren alternativen Vorschlägen anzunehmen 🙂

4.942 Beiträge seit 2008
vor 2 Jahren

Type.GetConstructors scheint mir aber gar keine Hilfe da hier keinerlei Info über die Parameter des Konstruktors zurückgegeben werden. Bietet denn eine Möglichkeit einen bestimmten Konstruktor der Klasse aufzurufen? Bisher hatte ich im Test nur einen Konstruktor verwendet. ConstructorInfo erbt von MethodBase und dort gibt es die Methode GetParameters (sonst wäre es ja ziemlich nutzlos, eine Liste aller Konstruktoren zu erhalten, wenn man nicht deren Parameter auswerten könnte ;- )

Und Activator.CreateInstance wählt den Konstruktor, welcher am besten (in Anzahl und Datentyp) mit den übergebenen Parametern übereinstimmt, ansonsten wird eine MissingMethodException geworfen.

Ich hoffe, du hast trotzdem dabei etwas über die .NET Interna gelernt?!