Laden...

Klasse T in generischer Methode instanziieren

Erstellt von pollito vor einem Jahr Letzter Beitrag vor einem Jahr 594 Views
pollito Themenstarter:in
314 Beiträge seit 2010
vor einem Jahr
Klasse T in generischer Methode instanziieren

Hallo,

ich brauche wieder einmal euer Expertenwissen. Ich habe unten stehenden Code einer generischen Methode. Diese untersucht die über <T> übermittelte Klasse, wobei sie die Methoden der Klasse ermittelt, die alle nachstehend aufgeführten Kriterien erfüllen:

  • public
  • static
  • Name beginnt mit "nv"
  • keine Parameter
  • Rückgabewert bool

Das funktioniert auch. Nun aber zu meinem Problem:

Wie kann ich die über T übergebende Klasse instanziieren? So was wie "var Test = new typeof(T)()" oder ähnlich. Ich stehe auf dem Schlauch.

namespace myTest
{
	class Program
	{
		static void Main(string[] args)
		{
			myMethod<KlasseA>()
			myMethod<KlasseB>()
			myMethod<KlasseC>()
		}

		public void myMethod<T>()
		{
			// Nimmt alle zur <T> passenden Methoden auf.
			var MethodMap = new Dictionary<string, Func<bool>>();

			// Uns interessieren nur öffentliche, statische Methoden.
			MethodInfo[] myMethods = typeof(T).GetMethods(BindingFlags.Public | BindingFlags.Static);

			// Darüber hinaus möchten wir nur die Methoden, die mit "nv"" beginnen, parameterlos sind und bool zurückliefern.
			foreach (MethodInfo myMethod in myMethods.Where(m => m.Name.StartsWith(c.PostProcessingPrefix) == true && m.GetParameters().Length == 0 && m.ReturnType == typeof(bool)))
			{
				MethodInfo? Method = typeof(T).GetMethod(myMethod.Name, Type.EmptyTypes);

				if (Method != null)
				{
					MethodMap.Add(myMethod.Name, (Func<bool>)Delegate.CreateDelegate(typeof(Func<bool>), Method));
				}
			}

			// Wie kann ich an dieser Stelle die über T übergebende Klasse instanziieren?
		}
	}

	public class KlasseA
	{
		...
	}

	public class KlasseB
	{
		...
	}

	public class KlasseC
	{
		...
	}
}

René

D
261 Beiträge seit 2015
vor einem Jahr
5.657 Beiträge seit 2006
vor einem Jahr

Oder so:


        T MyMethod<T>() where T: new()
        {
            return new T();
        }

Weeks of programming can save you hours of planning

2.078 Beiträge seit 2012
vor einem Jahr

... oder Du schränkst den Typ entsprechend ein:


T Create<T>()
    where T: new()
{
    return new T();
}

Damit erschlägst Du dann auch gleich abstrakte Klassen, die Du so ja nicht nutzen kannst.

Übrigens klingt dein Vorhaben nach etwas, was man lieber anders lösen sollte 😉

Z.B. würde ich sowas NIEMALS vom Namens abhängig machen, entweder der Name ist fix vorgegeben und dann kann ich auch ein Interface schreiben, oder ich versuche es möglichst zu vermeiden.
Und wenn es unbedingt doch machen muss, würde ich eher ein Attribut erstellen, nach dem ich dann suchen kann, dann ist der Name auch egal und im Attribut können noch ein paar Daten ergänzt werden.

Und MrSparkle war schneller 🙂

pollito Themenstarter:in
314 Beiträge seit 2010
vor einem Jahr

Danke euch Dreien! Beides funktioniert, wobei ich mich für die Einschränkung entschieden habe, da sie etwas durchsichtiger ist.

Ich verstehe, dass das Festzurren am Namen für ernste Mienen sorgt. Für meine Anwendung ist das aber so gewollt: Es sollen Klassen erstellt werden, die u. a. Werte aus anderen Quellen verarbeiten und an ein anderes System überstellen – also eine typische Schnittstelle zwischen zwei Systemen.

Jede Schnittstelle ist eine Klasse, die sog. Nachverarbeitungsmethoden bietet bzw. bieten kann. Diese Nachverarbeitungsmethoden werden für jedes Feld/Spalte automatisch aufgerufen, sofern sie in der benutzten Klasse vorhanden sind und unter anderem deren Methodenname den Feldname mit Präfix "nv" entsprechen. Beispiel: PartNumber ==> **nv**PartNumber.

Da es um sehr viele Schnittstellen mit vielen Feldern/Spalten geht, soll der Programmier- und Verwaltungsaufwand auf ein Minimum reduziert werden, daher diese Mimik: Es reicht, eine Klasse um eine passende Nachverarbeitungsmethode zu ergänzen, damit diese automatisch aufgerufen wird.

René

D
261 Beiträge seit 2015
vor einem Jahr

Also ich würde lieber das hier wählen


public class KlasseA
{
    [PostProcess("PartNumber")] // 
    public static bool TransformPartNumberIntoXyz() 
    {
           // Methode hat einen sprechenden Namen bzw. es ist möglich ihr einen sprechenden Namen zu geben
           // Standard .NET Namenskonventionen wurden eingehalten
    }
}

statt diesem


public class KlasseA
{
    public static bool nvPartNumber() 
    {
           // nv steht vermutlich für Nachverarbeitung (Englisch und Deutsch gemischt 👎)
    }
}

2.078 Beiträge seit 2012
vor einem Jahr

Oder die Klasse bekommt ein Interface mit einer Methode, die Bool zurück gibt, dann klappt's auch ohne Reflection.

D
261 Beiträge seit 2015
vor einem Jahr

Wenn es einen guten Grund gibt, dass die Methoden static sind, dann muss er aber noch etwas warten. Static Interface Member sind erst in der Preview.

pollito Themenstarter:in
314 Beiträge seit 2010
vor einem Jahr

Für diesen einen Fall nutzen uns die Attributen leider nichts: Z. B. liefert die Quelle Daten über die Datei "Parts_22", welche u. a. über 102 Spalten verfügt – 58 davon brauchen eine sogenannte Nachverarbeitung, so dass es dafür 58 Methoden gibt.

In Anlehnung an die Quelle bekommt die Klasse ebenfalls den Namen "Parts_22" – alle solche Klassen werden von einer anderen Klasse abgeleitet, die gemeinsame Funktionalität bietet.

Wenn nun für eine Spalte eine Nachverarbeitungsmethode notwendig ist, so heißt diese Methode wie die nachzuverarbeitende Spalte mit dem Präfix "nv". Wird nun beispielweise die Quelle um eine Spalte erweitert (z. B. Art2NoH), so wird diese zwar an das Zielsystem übertragen, mangels Nachverarbeitungsmethode aber nicht weiter beachtet. Ist das aber erwünscht, so reicht es, die Klasse Parts_22 um die Methode nvArt2NoH mit der passenden Signatur zu erweitern und schon wird diese ausgeführt.

Im Übrigen werden die Methoden durch ihr Präfix im Editor bei der Codevervollständigung alle schön gebündelt angezeigt. Auch ein Vorteil.

Das ist der Hintergrund.

Und deutsch und englisch: Manchmal verwende ich auch spanisch... 🙂

Nochmals lieben Dank für die vielen Vorschläge und hasta la próxima!

René

D
261 Beiträge seit 2015
vor einem Jahr

Im Übrigen werden die Methoden durch ihr Präfix im Editor bei der Codevervollständigung alle schön gebündelt angezeigt. Auch ein Vorteil.

Sofern du die Methoden manuell aufrufst dann stimmt das. Ich bin davon ausgegangen, dass du die Methoden nur via Reflection aufrufst.

Für diesen einen Fall nutzen uns die Attributen leider nichts: [...]

Warum die Attribute dabei nichts nutzen sollen, hast du aber nicht beschrieben.

Nach deiner ausführlicheren Beschreibung, würde ich es jetzt so umsetzen:


[File("Parts_22")]
public class FilePostProcessor_Parts_22 : BaseProcessor
{
    [PostProcess("PartNumber")]
    public static bool TransformPartNumberIntoXyz() 
    {
        // do something
    }

    [PostProcess("Art2NoH")]
    public static bool TransformArt2NoHIntoZyx() 
    {
        // do something
    }
}

BaseProcess ist deine Basisklasse mit der gemeinsamen Funktionalität.
Ein weiterer Vorteil wenn du die Attribute verwendest:

  • Du kannst einen Roslyn Analyzer bauen, der bei vorhandenem PostProcess Attribute die Methodensignatur erzwingt.
  • Du kannst zur Runtime überprüfen ob alle Methoden die das Attribute haben, auch die richtige Signatur haben.
    In deinem Fall kann es schnell passieren, dass eine Signatur falsch ist und das fällt dann fast nicht auf. (Außer dass die Nachverarbeitung des Feldes nicht stattfindet)

Ich hab das jetzt nur der Vollständigkeit halber geschrieben. Vielleicht hat mal jemand ein ähnliches Problem und sucht etwas Inspiration.

pollito Themenstarter:in
314 Beiträge seit 2010
vor einem Jahr

Im Übrigen werden die Methoden durch ihr Präfix im Editor bei der Codevervollständigung alle schön gebündelt angezeigt. Auch ein Vorteil.
Sofern du die Methoden manuell aufrufst dann stimmt das. Ich bin davon ausgegangen, dass du die Methoden nur via Reflection aufrufst.

Eigentlich werden sie nicht manuell aufgerufen – da hast du Recht. Dennoch mag ich die Namensgebung, welche eine direkte Zuordnung zwischen einer Spalte und deren Verarbeitung herstellt. Das mag auch historische Gründe haben: Unser Hauptprodukt (eine große, 30 Jahre alte Datenbankanwendung) fußt darauf und wir sind immer gut damit zu recht gekommen. Die Macht der Gewöhnung...

Für diesen einen Fall nutzen uns die Attributen leider nichts: [...]
Warum die Attribute dabei nichts nutzen sollen, hast du aber nicht beschrieben.

Bei dieser Anwendung sehe ich keinen Mehrwert darin, wenn wir weiterhin auf unserer alten Zuordnung Feld/Spalte --> Methode beharren.

Nach deiner ausführlicheren Beschreibung, würde ich es jetzt so umsetzen:

  
[File("Parts_22")]  
public class FilePostProcessor_Parts_22 : BaseProcessor  
{  
    [PostProcess("PartNumber")]  
    public static bool TransformPartNumberIntoXyz()   
    {  
        // do something  
    }  
  
    [PostProcess("Art2NoH")]  
    public static bool TransformArt2NoHIntoZyx()   
    {  
        // do something  
    }  
}  
  

BaseProcess ist deine Basisklasse mit der gemeinsamen Funktionalität.
Ein weiterer Vorteil wenn du die Attribute verwendest:

  • Du kannst einen Roslyn Analyzer bauen, der bei vorhandenem PostProcess Attribute die Methodensignatur erzwingt.

Ja, richtig, aber durch unsere Vorgehensweise wird eine Methode nie aufgerufen, wenn Sie nicht die richtige Signatur und Namen hat.

  • Du kannst zur Runtime überprüfen ob alle Methoden die das Attribute haben, auch die richtige Signatur haben.
    In deinem Fall kann es schnell passieren, dass eine Signatur falsch ist und das fällt dann fast nicht auf. (Außer dass die Nachverarbeitung des Feldes nicht stattfindet)

Auch richtig, aber auch das ist in dieser Anwendung sekundär: Nachverarbeitung haben einen definierten Zweck und werden ohne ausführliche Tests nicht implementiert. Der erste Test ist der Aufruf über Reflection – erst wenn dieser stattfindet, wird die Logik implementiert.

Ich hab das jetzt nur der Vollständigkeit halber geschrieben. Vielleicht hat mal jemand ein ähnliches Problem und sucht etwas Inspiration.

Auf jeden Fall bedanke ich mich ausdrücklich bei dir, denn du hast mir einige Denkanstöße gegeben, die ich anderorts gut gebrauchen kann.

Schönes Wochenende dir und allen anderen!

René

D
152 Beiträge seit 2013
vor einem Jahr

Warum die Methoden umständlich per Reflection in ein Dictionary packen gerade wenn die statisch sind und nicht bei der Definition.


        Dictionary<string, Func<bool>> methodMap = new();

        methodMap["true"] = static () =>
        {
            return true;
        };

        methodMap["false"] = static () =>
        {
            return false;
        };

pollito Themenstarter:in
314 Beiträge seit 2010
vor einem Jahr

Warum die Methoden umständlich per Reflection in ein Dictionary packen gerade wenn die statisch sind und nicht bei der Definition.

Ganz einfach: Es werden einmalig beim Programmstart die Methoden der passenden Klasse zur Nachverarbeitung der Spaltenwerte der Quelle ermittelt und diese in ein temporäres Dictionary gepackt. Gleichzeitig werden alle Spalten inkl. deren Spaltennamen in der Quelle ermittelt. Nun findet ein Abgleich statt: Gibt es für die gerade untersuchte Spalte in der Quelle eine passende Methode (Nachverarbeitungsmethode)? – Die Kriterien wurden bereits eingangs dargelegt (static, public, keine Parameter, Rückgabe bool und Name wie die Spalte aber Präfix dazu).

Wenn eine Nachverarbeitungsmethode gefunden wurde, wird diese mit der sonstigen Feldinformation gespeichert.

Ich hatte auch erwähnt, dass der Pflegeaufwand auf ein Minimum reduziert werden soll: Kommen weitere Spalten in der Quelle dazu bzw. müssen bestehende Spalten anders behandelt werden, so soll es reichen, die jeweilige Klasse für die Quelle um die passenden Methoden (Signatur und Name müssen passen) zu erweitern. Das restliche Programm soll unangetastet bleiben.

René