Laden...

Delegate aus Methode erstellen

Erstellt von marcom vor 13 Jahren Letzter Beitrag vor 13 Jahren 2.686 Views
M
marcom Themenstarter:in
123 Beiträge seit 2007
vor 13 Jahren
Delegate aus Methode erstellen

Hallo zusammen,

ich habe ein Objekt, dass mehrere Events bereitstellt, die ich abonniere. Die Parameter sind dabei immer unterschiedlich. Ich möchte diese Parameter zwischenspeichern und später, aus einem anderen Thread heraus, eine bestimmte Methode aufrufen. Ich hatte nun überlegt, eine Containerklasse zu erstellen, wo ich sowolhl die Parameter als auch die Methode, die später aufgerufen werden soll, speichere (also z.B. einAnderesObjekt.Methode) Ich möchte diese Methode so generisch wie möglich speichern, deswegen hatte ich überlegt, der Containerklasse ein Delegate zu übergeben. Doch sobald ich diesem Delegate die Methode zuweisen möchte, bekomme ich die Fehlermeldung "cannot convert from method group to delegate". Gibt es eine Möglichkeit, das irgendwie zu realisieren, ohne unbedingt auf Reflection und Strings umsteigen zu müssen?

Danke & Gruß

Mark

5.941 Beiträge seit 2005
vor 13 Jahren

Hoi Mark

Ja, die gibt es.
Seit .NET 3.0 (AFAIK) gibt es die Delegates der Typen: Action, Func<T>, sowie Action<T, ........> und Func<T...., TResult>.

Wenn du nur eine Aktion ausführen musst, also eine Signatur vom Typ:

void MethodName();

hast, entspricht das in jedem Fall dem Delegatetypen: Action.

Action<T> entspricht void MethodName(T parameter)
Action<T, T2> entspricht void MethodName(T parameter, T2 parameterTwo)

Func<TResult> entspricht TResult MethodName();
Func<T, TResult> entspricht TResult MethodName(T parameter);

Und so weiter, bei Action gibts weitere Parameter und bei Func dasselbe, das letzte Typargument ist jeweils der Typ des Rückgabewertes.

Natürlich kannst du dir auch selber Delegates anlegen, oder generische Delegates. Wenn du < .NET 3.0 arbeitest, ist das sinnvoll.

Deine beschriebene Fehlermeldung bekommst du wohl, weil die Methodensignatur nicht auf den Delegate passt (Der eine Methodensignatur repräsentiert, enthält).

Schlussendlich ist ein Delegate ja dann ein Zeiger auf eine Methode, aber zusätzlich weiss er über die Methodensignatur Bescheid.

Ich hoffe das hilft dir weiter.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

M
marcom Themenstarter:in
123 Beiträge seit 2007
vor 13 Jahren

Hallo Peter,

danke für die Antwort.

Leider ist die Signatur aber nicht immer gleich. Kurzes Beispiel:



class ContainerClass
{
   public Object[] Params{get;set;}
   public Delegate Func{get;set;}
}

class SomeClass
{
   private List<ContainerClass> list = new List<...>();

   private void Init()
   {
      var obj1 = new ObjektMitEvents();

      obj1.Event1 += MyFunc1;
      obj1.Event2 += MyFunc2;
      ...
   }

   private void MyFunc1(Object a, int b, int c)
   {
      //Compilerfehler
      this.list.Add(new ContainerClass{Params = new Object[] {a,b,c}, Func = AsyncFunc1});
   }

   private void MyFunc2(double x, String s)
   {
      //Compilerfehler
      this.list.Add(new ContainerClass{Params = new Object[] {x,s}, Func = AsyncFunc2});
   }

   private void AsyncFunc1(Object a, int b, int c)
   {
   }

   private void AsyncFunc2(double x, String s)
   {
   }

...
}

Ich hoffe, das veranschaulicht mein Problem etwas besser.

5.941 Beiträge seit 2005
vor 13 Jahren

Salute marcom

Ja das tut es.

Du kannst alles, wie du es schon gemacht hast, theoretisch auf Delegate oder sogar den Typ object abbilden.
Auch eine Containerklasse mit object[] für Argumente (oder null für keine), sowie eben einer Eigenschaft für die Delegatespeicherung ist schon der richtige Ansatz / Weg.

Das Problem ist, dass sobald du den Delegaten dann schlussendlich brauchen willst, du schlussendlich auch den konkreten Typ brauchst.
Aber nicht in jedem Fall.

Wenn du also weisst: Ich habe einen Delegate, der kann Argumente bekommen, muss aber nicht. Und der kann ein Ergebnis zurückgeben, oder nicht, kannst du das mit dem Typ Delegate abbilden.

Aufruf dann wie folgt:


Delegate testDelegate; // Irgend eine Methode ist darin referenziert.
object[] arguments; // Entweder null, wenn keine Argumente enthalten sind, oder die Argumente in einem object[].

object result = testDelegate.DynamicInvoke(arguments);

Was du hier wissen musst?
Nur ob es eine Rückgabe gibt, und wenn, was für ein Typ diese Rückgabe besitzt.
Dann castest du die Rückgabe und alles passt. Der Rest ist nicht wichtig.

Wenn das nicht reicht, musst du den konkreten Typen des Delegates zum Zeitpunkt des Aufrufes wissen.
Es kann sehr gut sein, das du diesen zwar zur Speicherung sein, aber nicht mitgibst. Oder den nicht mal weisst. Allerdings weisst du ihn dann bei der Aufruf des Delegates.

Einfaches Beispiel ist eine Methode wie folgt:


TTypeOfResult RequestSomeResult<TTypeOfResult>()
{
    return (TTypeOfResult)delegateDictionary[typof(TTypeOfResult)].DynamicInvoke(arguments);
}

Wenn das immer noch nicht reicht, musst du dich wohl eingehender mit der Metaprogrammierung beschäftigen, sprich: Reflection, späte Bindung, frühe Bindung, dynamisches Laden von Assemblies, etc... sowie auch der Typisierung zur Laufzeit, nicht zur Kompilezeit. (Dynamische Typisierung vs. statische Typisierung).

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

M
marcom Themenstarter:in
123 Beiträge seit 2007
vor 13 Jahren

Hi Peter,

das Problem ist nicht das Aufrufen des Delegates (das würde ich auch über DynamicInvoke machen), sondern das einfache Speichern des "Funktionzeigers".
Dafür hab ich in Deinem Post auch keinen Ansatz gesehen.

mit Action oder Func ginge das auch nicht, da man hier den genauen Typen vorgeben muss, was ich eben vermeiden will.

916 Beiträge seit 2008
vor 13 Jahren

Doch


Delegate testDelegate; // Irgend eine Methode ist darin referenziert.

Again what learned...

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo marcom,

mit Action oder Func ginge das auch nicht, da man hier den genauen Typen vorgeben muss, was ich eben vermeiden will.

die musst du ja nur an der Stelle angeben, wo du bestimmst, auf welche Methode der Delegat zeigen soll, also beim Aufruf der Add-Methode. Und da lässt sich das m.E. auch nicht vermeiden, denn woher sollte er bei

this.list.Add(new ContainerClass{Params = new Object[] {a,b,c}, Func = AsyncFunc1});

rein aus dem Namen wissen, ob er

private void AsyncFunc1(Object a, int b, int c)

oder

private void AsyncFunc1(Object a, int b, int c, int d)

verwenden soll. Der Name bezeichnet eben nur eine Methoden-Gruppe, keine konkrete Methode, genau wie die Fehlermeldung sagt.

herbivore

771 Beiträge seit 2009
vor 13 Jahren

Hallo marcom,

die Lösung besteht darin, daß du für jede Methode auch einen entsprechenden 'delegate' erzeugen mußt und anschließend dann eine Konvertierung:


delegate void Del1(Object a, int b, int c);
delegate void Del2(double x, String s);

private void MyFunc1(Object a, int b, int c)
{
	//Compilerfehler
	this.list.Add(new ContainerClass{Params = new Object[] {a,b,c}, Func = new Del1(AsyncFunc1)});
}

private void MyFunc2(double x, String s)
{
	//Compilerfehler
	this.list.Add(new ContainerClass{Params = new Object[] {x,s}, Func = new Del2(AsyncFunc2)});
}

Obwohl Delegate bzw. MulticastDelegate die Basisklasse für alle eigenen Delegates ist, kann nur durch explizite Verwendung des 'delegate' Schlüsselworts eine benutzerdefinierte Methode einem Delegate übergeben werden (Compiler-Magie!).

Und ab .NET 3.0 kann man dann selbstverständlich die schon erwähnten generischen Action<> bzw. Func<> Delegates benutzen, d.h.


Func = new Action<Object, int, int>(AsyncFunc1);
// sowie
Func = new Action<double, String>(AsyncFunc2);

P.S: Warum verwendest du "Object" und "String" (anstatt "object" und "string", aber "int" und "double"? Ich hätte jetzt nur die C#-Schlüsselwörter benutzt, habe aber deinen Code trotzdem so gelassen -)

Hinweis von herbivore vor 13 Jahren

Letzteres ist eine reine Geschmacksfrage, die schon öfter besprochen wurde. Das brauchen wir hier bitte nicht zu wiederholen.

M
marcom Themenstarter:in
123 Beiträge seit 2007
vor 13 Jahren

Hallo Leute!

Erstmal vielen Dank für die zahlreichen Antworten!

Ich habe das Problem mit Action<T> gelöst, wie von Peter und cat vorgeschlagen. Damit ist die Implementierung am einfachsten Erweiterbar, falls neue Methoden (bzw. Events) hinzukommen, was wahrscheinlich ist.

Das ist auch der Grund, weshalb ich den Prototypen für die delegates vermeiden wollte, da ich für jede neue Methode einen neuen delegate erstellen muss, was hierbei entfällt (bzw. automatisch gemacht wird?!)

5.941 Beiträge seit 2005
vor 13 Jahren

Hallo marcom

Das freut mich.
Ja, die Delegates werden vom Kompiler inferiert und schlussendlich im Code ersetzt / hinzugefügt.
Es entfällt ab .NET 2.0 (AFAIK) also += new XYZEventHandler(method);

Weil der Kompiler sowieso wissen kann, von welchem Typ - Signatur - deine Methode ist.

Es entfällt ab .NET 2.0 (AFAIK) also += method;

Du kannst es aber auch explizit angeben, und so etwas anderes nutzen.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011