Délégué ouvert pour la méthode d’interface générique

J’essaie de créer un délégué d’instance ouverte pour une méthode d’interface générique, mais je continue à recevoir une exception NotSupportedException. Voici le code simplifié qui ne fonctionnera pas:

interface IFoo { void Bar(T j); } class Foo : IFoo { public void Bar(T j) { } } static void Main(ssortingng[] args) { var bar = typeof(IFoo).GetMethod("Bar").MakeGenericMethod(typeof(int)); var x = Delegate.CreateDelegate(typeof(Action), null, bar); } 

La dernière ligne lève NotSupportedException, “La méthode spécifiée n’est pas prise en charge”. Par comparaison, un délégué d’instance ouverte non générique fonctionne correctement:

 interface IFoo { void Bar(int j); } class Foo : IFoo { public void Bar(int j) { } } static void Main(ssortingng[] args) { var bar = typeof(IFoo).GetMethod("Bar"); var x = Delegate.CreateDelegate(typeof(Action), null, bar); } 

Et un délégué générique fermé fonctionne également:

 interface IFoo { void Bar(T j); } class Foo : IFoo { public void Bar(T j) { } } static void Main(ssortingng[] args) { var bar = typeof(IFoo).GetMethod("Bar").MakeGenericMethod(typeof(int)); var x = Delegate.CreateDelegate(typeof(Action), new Foo(), bar); } 

Ainsi, la recette des delegates génériques fermés et des delegates d’instance ouverte fonctionne séparément, mais pas lorsqu’elle est combinée. Cela commence à ressembler à un bogue d’exécution ou à une omission intentionnelle. Quelqu’un a une idée ici?

Ceci est une récapitulation du sujet et de ce problème spécifique pour ceux qui trouvent cette question (car il semble que le PO ait déjà obtenu sa réponse sur Microsoft Connect).


Réponse

La création d’un délégué générique d’instance ouverte pour une méthode d’interface générique est impossible (comme confirmé par Microsoft ici ). Actuellement, il est possible d’implémenter n’importe laquelle des combinaisons suivantes de méthodes interface / classe à instance ouverte / fermée statique, générique / non générique (avec des exemples de code fournis à la fin de la réponse):

  • délégué non générique d’instance ouverte pour une méthode d’interface non générique
  • délégué statique statique fermé pour une méthode d’interface générique
  • délégué statique non générique fermé pour une méthode d’interface non générique
  • délégué générique d’instance ouverte pour une méthode de classe générique
  • délégué non générique d’instance ouverte pour une méthode de classe non générique
  • délégué statique statique fermé pour une méthode de classe générique
  • délégué statique non générique fermé pour une méthode de classe non générique

Généralement, le meilleur remplaçant pour un délégué générique d’instance ouverte pour une méthode d’interface générique est un délégué générique d’instance ouverte pour une méthode de classe générique.


Échantillons de code

  • délégué non générique d’instance ouverte pour une méthode d’interface non générique

     interface IFoo { void Bar(int j); } class Foo : IFoo { public void Bar(int j) { } } static void Main(ssortingng[] args) { var bar = typeof(IFoo).GetMethod("Bar"); var x = Delegate.CreateDelegate(typeof(Action), null, bar); } 
  • délégué statique statique fermé pour une méthode d’interface générique

      interface IFoo { void Bar(T j); } class Foo : IFoo { public void Bar(T j) { } } static void Main(ssortingng[] args) { var bar = typeof(IFoo).GetMethod("Bar").MakeGenericMethod(typeof(int)); var x = Delegate.CreateDelegate(typeof(Action), new Foo(), bar); } 
  • délégué statique non générique fermé pour une méthode d’interface non générique

      interface IFoo { void Bar(int j); } class Foo : IFoo { public void Bar(int j) { } } static void Main(ssortingng[] args) { var bar = typeof(IFoo).GetMethod("Bar"); var x = Delegate.CreateDelegate(typeof(Action), new Foo(), bar); } 
  • délégué générique d’instance ouverte pour une méthode de classe générique

     class Foo { public void Bar(T j) { } } static void Main(ssortingng[] args) { var bar = typeof(Foo).GetMethod("Bar").MakeGenericMethod(typeof(int)); var x = Delegate.CreateDelegate(typeof(Action), null, bar); } 
  • délégué non générique d’instance ouverte pour une méthode de classe non générique

     class Foo { public void Bar(int j) { } } static void Main(ssortingng[] args) { var bar = typeof(Foo).GetMethod("Bar"); var x = Delegate.CreateDelegate(typeof(Action), null, bar); } 
  • délégué statique statique fermé pour une méthode de classe générique

     class Foo { public void Bar(T j) { } } static void Main(ssortingng[] args) { var bar = typeof(Foo).GetMethod("Bar").MakeGenericMethod(typeof(int)); var x = Delegate.CreateDelegate(typeof(Action), new Foo(), bar); } 
  • délégué statique non générique fermé pour une méthode de classe non générique

     class Foo { public void Bar(int j) { } } static void Main(ssortingng[] args) { var bar = typeof(Foo).GetMethod("Bar"); var x = Delegate.CreateDelegate(typeof(Action), new Foo(), bar); } 

Microsoft a répondu qu’il est un problème connu que le CLR ne peut pas le faire, mais cela ne peut pas être résolu dans la version actuelle de .NET. On ne comprend toujours pas pourquoi cela n’est pas possible, comme je l’ai expliqué. Les delegates ouverts ne doivent pas réutiliser la logique de répartition utilisée partout ailleurs dans le CLR pour une raison quelconque, ce qui me semble bizarre.

De manière inhabituelle, si vous en avez vraiment besoin et que cela ne vous dérange pas de jeter trop d’infrastructures au problème, vous pouvez utiliser ldvirtftn et calli .

Cela me semble très étrange, car c’est ce que j’ai pensé être ce qu’un délégué a fait derrière la scène pour faire essentiellement ceci:

 public class MyAction{ public virtual void Invoke(SomeClass @this) { ldarg.1 dup ldvirtftn SomeClass.GenericMethod calli void *(argument) ret } 

Ldvirtftn effectue une recherche pour déterminer le pointeur de la fonction à appeler pour cette méthode particulière. Si vous utilisez une méthode générique non virtuelle, les performances sont à peu près identiques à celles d’un délégué lié à la même fonction. Et s’il s’agit d’une méthode générique virtuelle, elle est environ deux fois plus lente, ce qui signifie que cela fonctionne toujours, c’est donc une amélioration.
J’ai créé ce fichier à l’aide de reflect.emit. Il semble bien fonctionner et il peut invoquer une méthode générique virtuelle fermée. Malheureusement, contrairement à un délégué, ce type est lié à une méthode spécifique. Cependant, le fait que le moteur d’exécution ne vous permette pas de créer une méthode dynamic qui utilise ldvirtftn , ldftn ou calli opcode est ldvirtftn ldftn .

  public class SomeType { public virtual void DoNothing() { Console.WriteLine(typeof(T)); } } public abstract class MyAction { public abstract void Invoke(SomeType type); } public static void Main(ssortingng[] args) { TypeBuilder builder = AppDomain.CurrentDomain .DefineDynamicAssembly(new AssemblyName(MethodBase.GetCurrentMethod().DeclaringType.Name), AssemblyBuilderAccess.RunAndCollect) .DefineDynamicModule("Module").DefineType("MyType", TypeAtsortingbutes.AnsiClass | TypeAtsortingbutes.AutoClass | TypeAtsortingbutes.Class | TypeAtsortingbutes.Public | TypeAtsortingbutes.Sealed, typeof (MyAction)); var ilgen = builder.DefineMethod("Invoke", MethodAtsortingbutes.Public | MethodAtsortingbutes.HideBySig | MethodAtsortingbutes.Final | MethodAtsortingbutes.Virtual, CallingConventions.HasThis, typeof (void), new[] {typeof (SomeType)}).GetILGenerator(); ilgen.Emit(OpCodes.Ldarg_1); ilgen.Emit(OpCodes.Dup); ilgen.Emit(OpCodes.Ldvirtftn, typeof (SomeType).GetMethod("DoNothing").MakeGenericMethod(typeof (int))); ilgen.Emit(OpCodes.Calli, SignatureHelper.GetMethodSigHelper(CallingConventions.HasThis, typeof (void))); ilgen.Emit(OpCodes.Ret); MyAction action = Activator.CreateInstance(builder.CreateType()) as MyAction; action.Invoke(new SomeType()); } 

Si la génération de code vous convient, vous pouvez utiliser des arbres d’expression ou une méthode dynamic pour simplement appeler la méthode. C’est un peu plus lent qu’un délégué direct, mais nous parlons d’une petite surcharge.