Laden...

Methodennamen erst zur Laufzeit bekannt

Erstellt von pollito vor einem Jahr Letzter Beitrag vor einem Jahr 480 Views
pollito Themenstarter:in
314 Beiträge seit 2010
vor einem Jahr
Methodennamen erst zur Laufzeit bekannt

Hallo,

ich möchte fragen, ob es bessere Möglichkeiten gibt, Methoden aufzurufen, deren Namen erst zur Laufzeit bekannt sind?


var klasse = new test();

for (int i = 1; i <= 3; i++)
{
	_ = klasse.GetType().GetMethod($"Func{i}").Invoke(klasse, new object[] { i });
}

public class test
{
	public void Func1(int a)
	{
		Console.WriteLine($"Func{a}");
	}
	public void Func2(int a)
	{
		Console.WriteLine($"Func{a}");
	}
	public void Func3(int a)
	{
		Console.WriteLine($"Func{a}");
	} 
}

Dann gleich eine Anschlussfrage:

Wie mache ich es, wenn die Klasse test in eine statische Klasse überführt wird?

Vielen Dank!

René

René

16.807 Beiträge seit 2008
vor einem Jahr

Was ist denn Dein Ziel, was hast Du vor?
Methodennamen werden optimiert, daher sollte man deren Namen nicht über Magic Strings aufrufen.

Normalerweise gibt es für alle solcher Anforderungen entsprechende Pattern / Architekturen, die sowas besser lösen.
Dazu müsste man wissen, was Du vor hast.

2.078 Beiträge seit 2012
vor einem Jahr

Du kannst nur den Reflection-Aufruf umgestalten, aber es bleibt immer noch Reflection.
Z.B. ist es meines Wissens nach performanter, wenn Du die Methode nicht einfach ausführst, sondern ein Delegate dafür erstellst.

Aber besser wäre natürlich, Du müsstest gar nicht auf Reflection zurückgreifen, damit sparst Du dir diese ganze Komplexität.

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

Danke Euch beiden!

Was ist denn Dein Ziel, was hast Du vor?

Ich bekomme große CSV-Dateien mit zum Teil mehreren hundert Spalten. Aus der ersten Spalte erhalte ich die Spaltennamen, deren Werte ab der zweiten Zeile geprüft und verarbeitet werden müssen. Diese Verarbeitung findet in einer für die jeweilige Spalte extra programmierten Methode statt. Somit hat man u. U. hunderte solcher Methoden. Nun möchte ich vermeiden, eine elend lange Switch-Anweisung mit hunderten von Cases zu programmieren, um jeweils die richtige Methode aufzurufen.

Sicher, ich könnte auch ein Dictionary verwenden, dessen Key der Spaltennamen ist und als Wert den passenden Funktionszeiger enthält. Das wäre mein zweiter Kandidat. Dennoch wäre es mir lieber, die Methoden direkt anhand des jeweiligen Spaltennamens aufzurufen, indem die aufzurufenden Methoden den Spaltennamen entsprechen. Kommen Spalten dazu, so muss ich lediglich die passenden Methoden implementieren, ohne mich mit einem Dictionary oder einer Switch-Anweisung befassen zu müssen – beim Dictionary würde ich zudem hunderte von Codezeilen sparen; bei der Switch-Anweisung sogar noch mehr.

René

16.807 Beiträge seit 2008
vor einem Jahr

Dennoch wäre es mir lieber, die Methoden direkt anhand des jeweiligen Spaltennamens aufzurufen, indem die aufzurufenden Methoden den Spaltennamen entsprechen.

Das ist halt nicht der Sinn / Funktionsweise der .NET Runtime.
.NET ist keine Scripting Runtime wie es PHP oder JavaScript der Fall wäre.

Dir bleibt hier nur Reflection, wenn Du das so (und nicht über einen geeigneten Pattern) machen willst - mit allen Vor- und Nachteilen.

2.078 Beiträge seit 2012
vor einem Jahr

Also ich würde daraus einzelne Klassen machen, die dann alle ein bestimmtes Interface implementieren.
Typen suchen und instanziieren ist immer noch Reflection, aber einfacher, als Methoden suchen und aufrufen.

16.807 Beiträge seit 2008
vor einem Jahr

Kannst völlig auf Reflection verzichten...
.. Factory Pattern
.. Flyweight Pattern
.. Interpreter Pattern
.. Mediator Pattern
..

2.078 Beiträge seit 2012
vor einem Jahr

Naja, wobei man am Ende ja trotzdem noch Reflection braucht, um die jeweiligen Implementierungen zu finden.
Oder man registriert sie alle einzeln im Code, aber das will pollito ja nicht - was ich auch nachvollziehen kann ^^

16.807 Beiträge seit 2008
vor einem Jahr

Klar, brauchst ne Registry zwischen Reaktion und Aktion => Dictionary.
Schwarze Magie is halt leider auch bei .NET nicht.

4.931 Beiträge seit 2008
vor einem Jahr

Hallo,

Dann gleich eine Anschlussfrage:

Wie mache ich es, wenn die Klasse test in eine statische Klasse überführt wird?

dafür benötigst du dann typeof(Type) sowie BindingFlags.Static, s. C# – How to call a static method using reflection.

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

OK, nun weiß ich, wie ich das machen werde.

Reflection brauche ich nur einmal, um in einer Factory die Delegates in einem Dictionary abzulegen. Gleichzeitig löse ich das Problem bzw. erfülle ich mir den Wunsch 🙂, static zu verwenden. Später kann ich direkt über den Spaltennamen die passende Methode über DynamicInvoke aufrufen.


using System.Linq.Expressions;
using System.Reflection;

Dictionary<string, Delegate> MethodenMap = new Dictionary<string, Delegate>();

for (int i = 1; i <= 3; i++)
{
	MethodenMap.Add($"Func{i}", GenerateDelegate(typeof(test).GetMethod($"Func{i}", new[] { typeof(int) })));
}

for (int i = 1; i <= 3; i++)
{
	MethodenMap[$"Func{i}"].DynamicInvoke(i);
}

Console.WriteLine("Enter, um zu beenden...");
Console.ReadLine();

static Delegate GenerateDelegate(MethodInfo Methode)
{
	if (Methode == null)					throw new ArgumentNullException	("Methode");
	if (Methode.IsStatic == false)			throw new ArgumentException		("Die übergebende Methode muss statisch sein.", "Methode");
	if (Methode.IsGenericMethod == true)	throw new ArgumentException		("Die übergebende Methode darf nicht generisch sein.", "Methode");

	return Methode.CreateDelegate(Expression.GetDelegateType(
		Methode.GetParameters()
			.Select(p => p.ParameterType)
			.Concat(new[] { Methode.ReturnType })
			.ToArray()));
}

public static class test
{
	public static void Func1(int a)
	{
		Console.WriteLine($"Func{a}");
	}
	public static void Func2(int a)
	{
		Console.WriteLine($"Func{a}");
	}
	public static void Func3(int a)
	{
		Console.WriteLine($"Func{a}");
	}
}

Danke für eure Tipps – diese haben mich auf die richtige Spur gebracht.

René

2.078 Beiträge seit 2012
vor einem Jahr

Du kannst anhand eines Delegates auch eine Action<int> erzeugen - macht den Aufruf etwas lesbarer

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

Du kannst anhand eines Delegates auch eine Action<int> erzeugen - macht den Aufruf etwas lesbarer

Ja, richtig, oder Func, falls man etwas zurückliefern möchte.

Vielen Dank!

René

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

Nachtrag
Ich habe das ganze , wie von Palladin007 empfohlen, auf Action umgestellt:


Dictionary<string, Action<int>> MethodenMap	= new Dictionary<string, Action<int>>();

for (int i = 1; i <= 3; i++)
{
	MethodenMap.Add($"Func{i}", GenerateDelegate(typeof(test).GetMethod($"Func{i}", new[] { typeof(int) })) as Action<int>);
}

for (int i = 1; i <= 3; i++)
{
	MethodenMap[$"Func{i}"](i);
}

Der Aufruf MethodenMap[$"Func{i}"](i); ist dadurch in der Tat eleganter, aber da habe ich gleich eine weitere Frage:

Ist die Umwandlung des Delegate in eine Action mit dem as-Operator (hier as Action<int>) der richtige Weg oder gibt es andere (bessere) Möglichkeiten?

René

2.078 Beiträge seit 2012
vor einem Jahr

Nutze doch einfach die Überladung, bei der Du den Delegate-Typ mitgeben kannst.
Dann tut's einfaches Casten

Delegate.CreateDelegate Method (System)

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

Du hast mich kurz vor dem Ins-Bett-gehen nochmals angetriggert. Ich habe das Beispiel etwas modifiziert, wodurch es m. E. klarer wird:


using System.Linq.Expressions;
using System.Reflection;

Dictionary<string, Action<int>> MethodenMap	= new Dictionary<string, Action<int>>();

for (int i = 1; i <= 3; i++)
{
	MethodenMap.Add($"Func{i}", CreateAction($"Func{i}"));
}

for (int i = 1; i <= 3; i++)
{
	MethodenMap[$"Func{i}"](i);
}

Console.WriteLine("Enter, um zu beenden...");
Console.ReadLine();

static Action<int> CreateAction(string FuncName)
{
	MethodInfo? Methode = typeof(test).GetMethod(FuncName, new[] { typeof(int) });

	if (Methode == null)					throw new ArgumentNullException	("Methode");
	if (Methode.IsStatic == false)			throw new ArgumentException		("Die übergebende Methode muss statisch sein.", "Methode");
	if (Methode.IsGenericMethod == true)	throw new ArgumentException		("Die übergebende Methode darf nicht generisch sein.", "Methode");

	// Danke an Palladin007 – es sieht schon übersichtlicher aus! ;-)
	return (Action<int>)Delegate.CreateDelegate(typeof(Action<int>), Methode);

	//return (Action<int>)Methode.CreateDelegate(Expression.GetDelegateType
	//	(
	//		Methode.GetParameters()
	//			.Select(p => p.ParameterType)
	//			.Concat(new[] { Methode.ReturnType })
	//			.ToArray())
	//	);
}

public static class test
{
	public static void Func1(int a)
	{
		Console.WriteLine($"Func{a}");
	}
	public static void Func2(int a)
	{
		Console.WriteLine($"Func{a}");
	}
	public static void Func3(int a)
	{
		Console.WriteLine($"Func{a}");
	}
}

Vielen Dank und gute Nacht!

René

2.078 Beiträge seit 2012
vor einem Jahr

Warum nicht einfach so?


return (Action<int>)Delegate.CreateDelegate(typeof(Action<int>), myMethodInfo);

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

Warum nicht einfach so?

  
return (Action<int>)Delegate.CreateDelegate(typeof(Action<int>), myMethodInfo);  
  

Frei nach dem Motto, warum einfach, wenn es kompliziert geht? Nochmals herzlichen Dank, es funktioniert super.

Ich habe den Code oben entsprechend angepasst.

René