2015-07-16 7 views
10

Di solito, il trattamento di una struct S come interfaccia I attiverà il boxing automatico della struttura, che può avere un impatto sulle prestazioni se eseguito spesso. Tuttavia, se scrivo un metodo generico utilizzando un parametro di tipo T : I e lo chiamo con un S, il compilatore non tralascia il boxing, poiché conosce il tipo S e non deve utilizzare l'interfaccia?I generici C# impediscono l'autoboxing delle strutture in questo caso?

Questo codice mostra il mio punto:

interface I{ 
    void foo(); 
} 

struct S : I { 
    public void foo() { /* do something */ } 
} 

class Y { 

    void doFoo(I i){ 
     i.foo(); 
    } 
    void doFooGeneric<T>(T t) where T : I { 
     t.foo(); // <--- Will an S be boxed here?? 
    } 

    public static void Main(string[] args){ 
     S x; 
     doFoo(x); // x is boxed 
     doFooGeneric(x); // x is not boxed, at least not here, right? 
    } 

} 

Il metodo doFoo chiama foo() su un oggetto di tipo I, così una volta la chiamiamo con un S, che S otterrà in scatola. Il metodo doFooGeneric fa la stessa cosa. Tuttavia, una volta che lo chiamiamo con un S, non potrebbe essere richiesto alcun autoboxing, dal momento che il runtime sa come chiamare foo() su un S. Ma sarà fatto? O il runtime ciecamente boxing S a un I per chiamare il metodo di interfaccia?

+1

Prova ad aggiungere il vincolo 'struct' - vale a dire' vuoto doFooGeneric (T t) dove T: struct, I { ' – AlexFoxGill

+0

FWIW, utilizzando la funzione di' IsBoxed' da [questa risposta] (http://stackoverflow.com/a/5807132/791010) dice che non è in scatola. Non conosco i dettagli del perché però. –

+0

Correlato se non duplicato: http://stackoverflow.com/questions/3032750/struct-interfaces-and-boxing e appositamente [questa risposta] (http://stackoverflow.com/a/3033357/961113) di Marc Gravell – Habib

risposta

7
void doFooGeneric<T>(T t) where T : I { 
    t.foo(); // <--- Will an S be boxed here?? 
} 

La boxe sarà evitata lì!

Il tipo di struttura S è sigillato. Per le versioni del tipo di valore del parametro tipo T al metodo doFooGeneric precedente, il compilatore C# fornisce il codice che chiama direttamente il membro della struttura pertinente, senza boxing.

Che è bello.

Vedere la risposta di Sameer per alcuni dettagli tecnici.


OK, quindi mi è venuto in mente un esempio. Sarò interessato a esempi migliori se qualcuno ha qualche:

using System; 
using System.Collections.Generic; 

namespace AvoidBoxing 
{ 
    static class Program 
    { 
    static void Main() 
    { 
     var myStruct = new List<int> { 10, 20, 30, }.GetEnumerator(); 
     myStruct.MoveNext(); // moves to '10' in list 

     // 
     // UNCOMMENT ONLY *ONE* OF THESE CALLS: 
     // 

     //UseMyStruct(ref myStruct); 
     //UseMyStructAndBox(ref myStruct); 

     Console.WriteLine("After call, current is now: " + myStruct.Current); // 10 or 20? 
    } 

    static void UseMyStruct<T>(ref T myStruct) where T : IEnumerator<int> 
    { 
     myStruct.MoveNext(); 
    } 

    static void UseMyStructAndBox<T>(ref T myStruct) 
    { 
     ((IEnumerator<int>)myStruct).MoveNext(); 
    } 
    } 
} 

Ecco il tipo di myStruct è un valore di tipo mutabile che contiene un riferimento al List<>, e tiene anche "contro" che ricorda indice di ciò che in lo List<> che abbiamo raggiunto fino ad ora.

Ho dovuto usare ref, altrimenti il ​​valore-tipo sarebbe stato copiato dal valore quando passato in uno dei due metodi!

Quando annullo la chiamata a UseMyStruct (solo), questo metodo sposta il "contatore" all'interno del nostro tipo di valore con una posizione in avanti. Se lo facesse in una copia inscatolata del tipo di valore, non lo vedremmo nell'istanza originale della struct.

Per vedere qual è la differenza con il pugilato, provare la chiamata UseMyStructAndBox invece (commento UseMyStruct di nuovo). Crea una scatola nel cast e lo MoveNext si verifica su una copia. Quindi l'output è diverso!


A coloro che sono infelici con (o confusi da) il ref, basta scrivere fuori dalla Current all'interno del metodo, invece. Quindi possiamo sbarazzarci di ref.Esempio:

static void F<T>(T t) where T : IEnumerator<int> 
{ 
    t.MoveNext(); // OK, not boxed 
    Console.WriteLine(t.Current); 
} 

static void G<T>(T t) where T : IEnumerator<int> 
{ 
    ((IEnumerator<int>)t).MoveNext(); // We said "Box!", it will box; 'Move' happens to a copy 
    Console.WriteLine(t.Current); 
} 
+0

Se sì, allora sì, sarebbe essere molto bello Ma sei sicuro? La risposta di Habib afferma esattamente il contrario. – gexicide

+0

@gexicide, no, la mia risposta era sbagliata rispetto al 'dove T: I' altrimenti era corretto. – Habib

+0

Questa ottimizzazione è estremamente importante ad esempio per Array.Sort . Questo dovrebbe funzionare. – usr

Problemi correlati