Existe-t-il d’autres moyens d’appeler une méthode d’interface d’une structure sans boxing sauf dans les classes génériques?

voir l’extrait de code

public interface I0 { void f0(); } public struct S0:I0 { void I0.f0() { } } public class A where E :I0 { public E e; public void call() { e.f0(); } } 

voici le code IL pour call ()

 .maxstack 8 L_0000: ldarg.0 L_0001: ldflda !0 Temp.A`1::e L_0006: constrained !E L_000c: callvirt instance void Temp.I0::f0() L_0011: ret 

voir référence pour contraint

Le préfixe contraint peut également être utilisé pour invoquer des méthodes d’interface sur des types de valeur, car la méthode de type de valeur implémentant la méthode d’interface peut être modifiée à l’aide de MethodImpl. Si le préfixe contraint n’est pas utilisé, le compilateur est obligé de choisir les méthodes du type de valeur à lier au moment de la compilation. L’utilisation du préfixe contraint permet à MSIL de se lier à la méthode qui implémente la méthode d’interface au moment de l’exécution, plutôt qu’au moment de la compilation.

Cela signifie qu’il appellera une méthode contenant le code de la méthode d’interface f0 sans enchaîner la structure.

Existe-t-il d’autres méthodes de calage de la méthode d’interface sans boxing comme ci-dessus GenericClass en C #?

Cela dépend … vous dites spécifiquement que vous ne voulez pas de classe générique … la seule autre option est une méthode générique dans une classe non générique . La seule autre fois où vous pouvez obtenir que le compilateur émette un appel constrained est si vous appelez ToSsortingng() , GetHashCode() ou Equals() (depuis un object ) sur une struct , puisque celles-ci sont ensuite constrained – si la struct a un override sera call ; s’il n’y a pas de callvirt , ils seront callvirt . C’est pourquoi vous devriez toujours override ces 3 pour toute struct ; p Mais je m’éloigne du sujet. Un exemple simple serait une classe d’utilitaire avec des méthodes statiques. Les méthodes d’ extension seraient un exemple idéal, car vous bénéficiez également de l’avantage que le compilateur bascule automatiquement entre l’API publique / implicite et l’API extension / explicite, sans que vous ayez besoin changer de code. Par exemple, ce qui suit (qui montre à la fois une implémentation implicite et explicite) n’a pas de boxe, avec un call et un constrained + callvirt , qui sera implémenté via un call au JIT:

 using System; interface IFoo { void Bar(); } struct ExplicitImpl : IFoo { void IFoo.Bar() { Console.WriteLine("ExplicitImpl"); } } struct ImplicitImpl : IFoo { public void Bar() {Console.WriteLine("ImplicitImpl");} } static class FooExtensions { public static void Bar(this T foo) where T : IFoo { foo.Bar(); } } static class Program { static void Main() { var expl = new ExplicitImpl(); expl.Bar(); // via extension method var impl = new ImplicitImpl(); impl.Bar(); // direct } } 

Et voici les éléments clés de l’IL:

 .method private hidebysig static void Main() cil managed { .entrypoint .maxstack 1 .locals init ( [0] valuetype ExplicitImpl expl, [1] valuetype ImplicitImpl impl) L_0000: ldloca.s expl L_0002: initobj ExplicitImpl L_0008: ldloc.0 L_0009: call void FooExtensions::Bar(!!0) L_000e: ldloca.s impl L_0010: initobj ImplicitImpl L_0016: ldloca.s impl L_0018: call instance void ImplicitImpl::Bar() L_001d: ret } .method public hidebysig static void Bar<(IFoo) T>(!!T foo) cil managed { .custom instance void [mscorlib]System.Runtime.ComstackrServices.ExtensionAtsortingbute::.ctor() .maxstack 8 L_0000: ldarga.s foo L_0002: constrained. !!T L_0008: callvirt instance void IFoo::Bar() L_000d: ret } 

Un inconvénient d’une méthode d’extension, cependant, est qu’elle ldloc.0 une copie supplémentaire de la struct (voir ldloc.0 ) sur la stack, ce qui peut poser problème si elle est surdimensionnée ou si elle est une méthode en mutation ( que vous devriez éviter de toute façon). Si tel est le cas, un paramètre ref est utile, mais notez qu’une méthode d’ extension ne peut pas avoir ref this paramètre ref this – vous ne pouvez donc pas le faire avec une méthode d’extension. Mais considérons:

 Bar(ref expl); Bar(ref impl); 

avec:

 static void Bar(ref T foo) where T : IFoo { foo.Bar(); } 

lequel est:

 L_001d: ldloca.s expl L_001f: call void Program::Bar(!!0&) L_0024: ldloca.s impl L_0026: call void Program::Bar(!!0&) 

avec:

 .method private hidebysig static void Bar<(IFoo) T>(!!T& foo) cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: constrained. !!T L_0007: callvirt instance void IFoo::Bar() L_000c: ret } 

Toujours pas de boxe, mais maintenant nous ne copions jamais non plus la structure, même dans le cas explicite .

Les interfaces étant traitées comme des types de référence, il n’ya aucun moyen d’appeler une méthode sur une structure référencée par une interface sans avoir à encombrer d’abord la structure sous-jacente.

Lors de l’utilisation d’une méthode générique imposant le type pour implémenter l’interface, le compilateur C # lève simplement les détails de l’implémentation réels et, partant, la convention d’appel au runtime. Heureusement, le compilateur C # est assez intelligent pour indiquer au compilateur JIT que le type soumis implémente l’interface X et peut être une structure. Avec cette information, le compilateur JIT peut comprendre comment appeler la méthode Y déclarée par l’interface X.

L’astuce ci-dessus n’est pas utilisable pour un appel de méthode non générique car il n’existe aucun moyen pratique pour le compilateur JIT de déterminer le type réel représenté par l’argument X lorsque X est une classe ou une interface non scellée. Par conséquent, le compilateur C # n’a aucun moyen de générer un JIT qui traite une table de consultation si le type représenté par l’interface transmise est une classe non scellée et une invocation de méthode directe qui traite des structures et des classes scellées.

En théorie, lorsque vous utilisez Dynamic, vous pouvez empêcher la boxe. Toutefois, la perte de performance liée à l’introduction du DLR ne produira probablement aucun avantage.