2012-11-26 17 views
10

vedere frammento di codiceEsistono altri modi per chiamare un metodo di interfaccia di una struct senza boxing tranne che nelle classi generiche?

public interface I0 
{ 
    void f0(); 
} 
public struct S0:I0 
{ 
    void I0.f0() 
    { 

    } 
} 
public class A<E> where E :I0 
{ 
    public E e; 
    public void call() 
    { 
     e.f0(); 
    } 
} 

ecco codice IL per chiamata()

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

vedi domanda constrained

Il prefisso vincolata può essere utilizzato anche per invocare metodi di interfaccia in tipi di valore, poiché il metodo del tipo valore che implementa il metodo dell'interfaccia può essere modificato utilizzando un metodo Method. Se il prefisso vincolato non viene utilizzato, il compilatore è costretto a scegliere quale dei metodi del tipo valore legarsi a in fase di compilazione. L'utilizzo del prefisso vincolato consente a MSIL di collegarsi al metodo che implementa il metodo di interfaccia in fase di esecuzione anziché in fase di compilazione.

Ciò significa che chiamerà un metodo contenente il codice di metodo dell'interfaccia di f0 senza inscatolare la struct.

Esistono altri modi per il metodo di interfaccia di caling senza boxing come sopra GenericClass in C#?

+0

(Credo che questo dovrebbe essere un commento :)) Vedi http://stackoverflow.com/questions/3032750/struct-interfaces-and-boxing – Matthias

+0

grazie per la tua risposta. Ho modificato il titolo: come chiamare tranne in generico? – Vince

+1

Per quanto mi riguarda, non ho letto il post completo, perché dal titolo mi aspettavo qualcosa di diverso :) Alla tua domanda: I * guess * no. – Matthias

risposta

15

Dipende ... che specificamente dire che non vuoi un generico classe ... l'unica altra opzione è un metodo generico in una non generica class . L'unica volta altro è possibile ottenere il compilatore ad emettere una chiamata constrained è se si chiama ToString(), GetHashCode() o Equals() (da object) su un struct, dal momento che questi sono allora constrained - se il struct ha un override saranno call; se non ha un override, sarà callvirt. È per questo che dovresti sempre override quelli 3 per qualsiasi struct; p Ma sto divagando.Un semplice esempio potrebbe essere una classe di utilità con alcuni metodi statici: l'estensione sarebbe un esempio ideale, poiché si ha anche il vantaggio che il compilatore passerà automaticamente tra l'API pubblica/implicita e l'API estensione/esplicita, senza di te sempre bisogno di cambiare codice. Ad esempio, il seguente (che mostra sia un'implementazione implicita ed esplicita) non ha la boxe, con un call e uno constrained + callvirt, che sarà attuata tramite call al 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<T>(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 
    } 
} 

Ed ecco i bit della chiave di 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<valuetype ExplicitImpl>(!!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.CompilerServices.ExtensionAttribute::.ctor() 
    .maxstack 8 
    L_0000: ldarga.s foo 
    L_0002: constrained. !!T 
    L_0008: callvirt instance void IFoo::Bar() 
    L_000d: ret 
} 

uno svantaggio di un metodo di estensione, però, è che si sta facendo una copia extra del struct (vedi ldloc.0) nello stack, che potrebbe essere un problema se è sovradimensionato, o se è un metodo di muting (che dovresti evitare comunque). In questo caso, è utile un parametro ref, ma nota che un metodo di estensione 10 non può avere un parametro ref this, quindi non è possibile farlo con un metodo di estensione. Ma prendere in considerazione:

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

con:

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

che è:

L_001d: ldloca.s expl 
L_001f: call void Program::Bar<valuetype ExplicitImpl>(!!0&) 
L_0024: ldloca.s impl 
L_0026: call void Program::Bar<valuetype ImplicitImpl>(!!0&) 

con:

.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 
} 

Ancora nessuna boxe, ma ora abbiamo anche mai copiare lo struct , anche per 01 esplicitocaso.

+0

perfetti, paga i miei rispetti! – Vince

0

Poiché le interfacce sono trattate come tipi di riferimento, non è possibile chiamare un metodo su una struttura a cui fa riferimento un'interfaccia senza dover prima inserire la struttura sottostante.

Quando si utilizza un metodo generico che impone il tipo per implementare l'interfaccia, il compilatore C# solleva semplicemente i dettagli di implementazione effettivi e quindi la convenzione di chiamata al runtime. Fortunatamente il compilatore C# è abbastanza intelligente da insegnare al compilatore JIT che il tipo sottoposto implementa l'interfaccia X e potrebbe essere una struttura. Con queste informazioni il compilatore JIT può capire come richiamare il metodo Y dichiarato dall'interfaccia X.

Il trucco precedente non è utilizzabile per una chiamata di metodo non generica poiché non esiste un modo pratico per il compilatore JIT per capire tipo effettivo rappresentato dall'argomento X quando X è una classe o un'interfaccia non sigillata. Quindi, non c'è modo per il compilatore C# di generare JIT che si occupa di una tabella di ricerca nel caso in cui il tipo rappresentato dall'interfaccia passata sia una classe non sigillata e un'invocazione di metodo diretta che si occupa di strutture e classi sigillate.

Quando si utilizza la dinamica è possibile, in teoria, evitare la boxe, tuttavia la perdita di prestazioni per l'introduzione del DLR probabilmente non porterà alcun beneficio.

+2

Il DLR è piuttosto pesante da boxe ... anzi, 'dynamic' è implementato * essenzialmente * come' oggetto'. Non sono sicuro che la tua ultima riga si alzi, almeno senza ulteriori spiegazioni/dettagli. –

Problemi correlati