2012-03-24 6 views
3

Dies ist eine Art Folge zu einer prior thread. Ich baue einen kleinen Wrapper, um Uploads für dynamisch typisierte Methoden durchzuführen, die von meinen Benutzern bereitgestellt werden. Das Schema funktioniert gut ... aber nur für statische Methoden. Obwohl CreateDelegate auch für Instanzmethoden arbeiten soll, löst es, wenn es mit diesen verwendet wird, einen "Binding Error" aus, wenn die Methode isStatic flag falsch ist (weil das Flag für die Fehlerüberschreitung falsch ist, gibt es null zurück). . Hier ist ein Codebeispiel, in dem Sie dies sehen können.CreateDelegate weigert sich, Delegaten für Instanzmethoden zu erstellen

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

namespace ConsoleApplication4 
{ 
    delegate void myFoo(int i, string s); 
    delegate void myNull(); 

    internal class Callable 
    { 
     internal int nParams; 
     internal Type[] ptypes; 
     internal Delegate cb; 
     internal static Type[] actions = { typeof(Action), typeof(Action<>), typeof(Action<,>), typeof(Action<,,>), typeof(Action<,,,>), typeof(Action<,,,,>), 
             typeof(Action<,,,,,>), typeof(Action<,,,,,,>), typeof(Action<,,,,,,,>), typeof(Action<,,,,,,,,>), typeof(Action<,,,,,,,,,>), 
             typeof(Action<,,,,,,,,,,>), typeof(Action<,,,,,,,,,,,>), typeof(Action<,,,,,,,,,,,,>), typeof(Action<,,,,,,,,,,,,,>), typeof(Action<,,,,,,,,,,,,,,>) }; 

     internal Callable(Delegate hisCb) 
     { 
      MethodInfo mi = hisCb.Method; 
      ParameterInfo[] pi = mi.GetParameters(); 
      ptypes = pi.Select(p => p.ParameterType).ToArray(); 
      nParams = ptypes.Length; 
      if (nParams > 0 && nParams < 17) 
      { 
       cb = Delegate.CreateDelegate(actions[nParams].MakeGenericType(ptypes), mi, false); 
       if (cb == null) 
        Console.WriteLine("Warning: Unsuccessful attempt to CreateDelegate for " + hisCb + ", with methodinfo " + mi); 
       else 
        Console.WriteLine("Successful attempt to CreateDelegate for " + hisCb + ", with methodinfo " + mi); 
      } 
      else 
       cb = hisCb; 
     } 

     internal void doUpcall(object[] args) 
     { 
      if (args.Length != nParams) 
       throw new ArgumentException("Argument count must match number of parameters"); 
      switch (nParams) 
      { 
       case 1: 
        ((dynamic)cb).Invoke((dynamic)args[0]); 
        break; 
       case 2: 
        ((dynamic)cb).Invoke((dynamic)args[0], (dynamic)args[1]); 
        break; 
        // ... cases 3-15 similar, omitted to save space 
       default: 
        cb.DynamicInvoke((dynamic)args); 
        break; 
      } 
     } 
    } 

    internal class FooBar 
    { 
     internal FooBar() 
     { 
     } 

     internal static void printFields(int i, string s) 
     { 
      Console.WriteLine("In FooBar.printField-s with i="+i+", s="+s); 
     } 

     internal void printFieldi(int i, string s) 
     { 
      Console.WriteLine("In FooBar.printField-i with i=" + i + ", s=" + s); 
     } 
    } 

    internal class Program 
    { 
     private static void Main(string[] args) 
     { 
      FooBar myFooBar = new FooBar(); 
      Callable cbfb0 = new Callable((myFoo)FooBar.printFields); 
      cbfb0.doUpcall(new object[] { 77, "myfb" }); 
      Callable cbfb1 = new Callable((myFoo)myFooBar.printFieldi); 
      cbfb1.doUpcall(new object[] { 77, "myfb" }); 
      string pc = "Main"; 
      Callable cb0 = new Callable((myNull)delegate() { Console.WriteLine("Hello from myNull"); }); 
      cb0.doUpcall(new object[0]); 
      Callable cb1 = new Callable((myFoo)delegate(int i, string s) { Console.WriteLine("i=" + i + ", s.Length = " + s.Length); }); 
      Console.WriteLine("About to attempt to call Foo: Good args"); 
      cb1.doUpcall(new object[] { 2, "bar" }); 
      Console.WriteLine("After calling Foo"); 
      Callable cb2 = new Callable((myFoo)delegate(int i, string s) { Console.WriteLine("My parent class is " + pc + ", i=" + i + ", s.Length = " + s.Length); }); 
      Console.WriteLine("About to attempt to call Foo: Good args"); 
      cb2.doUpcall(new object[] { 12, "Bar" }); 
      Console.WriteLine("After calling Foo"); 
      System.Threading.Thread.Sleep(15000); 
     } 

     private static void Foo(int i, string s) 
     { 
      Console.WriteLine("i=" + i + ", s.Length = " + s.Length); 
     } 
    } 
} 

Kann mir jemand helfen zu verstehen, warum CreateDelegate verhält sich so? Die C# - und .NET-Referenzinformationen geben an, dass sie sowohl für statische als auch für Instanzmethoden funktionieren sollten. Wenn Sie den Fall "nicht erfolgreich" unterbrechen, können Sie bestätigen, dass der Wert des Flags "mi.isStatic" den Erfolg oder Misserfolg bestimmt.

PS: Beachten Sie die Verwendung von (dynamisch), um die Argumente zur Laufzeit auf die benötigten Typen zu übertragen! Das finde ich cool. Früher war es unmöglich - Sie möchten eine Umwandlung (T) durchführen, wissen aber nicht, was der Typ T sein wird und können daher ein Objekt vom Typ T erstellen, aber keinen dynamischen Aufruf an eine Methode, die dieses Objekt verwendet wie in meinen 15 Fallaussagen. Indem ich auf (dynamisch) umwandle, vermeide ich dieses Problem - löst ein Problem, für das es Dutzende von alten Threads zu geben scheint, die nicht gelöst wurden! (Und das verbessert den Code, der im vorherigen Thread vorgeschlagen wurde ... der dasselbe Casting-Problem mit bekannten Typen hatte).

+1

Tipp: Sie 'Expression.GetActionType (ptypes)' verwenden können, erhalten der Delegattyp –

+0

Ok, aber würde immer noch die switch-Anweisung und die 15 Fälle brauchen, oder? Das Array von Aktionen ist hässlich; Ich werde diese Änderung vornehmen. Aber die große Switch-Anweisung ist meiner Ansicht nach nerviger! Trotzdem funktioniert das, und es behebt mein Problem vollständig (das war, dass Benutzerausnahmen den Aufruf-Stack bis zur .Invoke abwickeln, wenn der Debugger nicht im First-Chance-Modus läuft ...). Also bin ich ein glücklicher Camper, sogar mit der hässlichen Switch-Anweisung in meinem Code! –

Antwort

5

CreateDelegate erstellt einen aufrufbaren Delegaten - um dies zu tun, muss die Instanz die Methode aufrufen. Für statische Methoden ist keine Instanz erforderlich. Sie rufen die Überladung auf, die das Erstellen eines Delegaten für eine statische Methode ermöglicht.

Um einen Delegierten für eine Instanz Methode zu erstellen, müssen Sie eine Overlaod verwenden, die Sie auch in der Instanz passieren lässt:

cb = Delegate.CreateDelegate(actions[nParams].MakeGenericType(ptypes), 
         hisCb.Target, 
         mi, false); 
+0

Danke, Philip! Versuchte dies und es funktioniert. Dieses Board ist unglaublich. –

1

Der einzige Anruf, den ich zu Delegate.CreateDelegate sehe, ist derjenige, der die Überladung aufruft, die Type, MethodInfo, bool dauert. Die documentation heißt es, dass es

einen Delegierten des angegebenen Typs erstellt die angegebene statische Methode zur Darstellung ...

Wenn Sie eine Instanz-Methode haben, können Sie eine andere Überlastung zu nennen haben (eine, die Nimmt eine object), um einen Delegaten daraus zu erstellen.

+0

Danke, Gabe! Ich hätte gefragt, wie man das macht, aber Philip erklärte unten ... –

Verwandte Themen