Evento di boxe in C #


86

Sto cercando di raccogliere tutte le situazioni in cui si verifica la boxe in C #:

  • Conversione del tipo di valore in System.Objecttipo:

    struct S { }
    object box = new S();
    
  • Conversione del tipo di valore in System.ValueTypetipo:

    struct S { }
    System.ValueType box = new S();
    
  • Conversione del valore del tipo di enumerazione in System.Enumtipo:

    enum E { A }
    System.Enum box = E.A;
    
  • Conversione del tipo di valore in riferimento all'interfaccia:

    interface I { }
    struct S : I { }
    I box = new S();
    
  • Utilizzo dei tipi di valore nella concatenazione di stringhe C #:

    char c = F();
    string s1 = "char value will box" + c;
    

    nota: le costanti di chartipo vengono concatenate in fase di compilazione

    Nota: a partire dalla versione 6.0 del compilatore C # Ottimizza concatenazione che coinvolge bool, char, IntPtr, UIntPtrtipi

  • Creazione di delegato dal metodo di istanza del tipo di valore:

    struct S { public void M() {} }
    Action box = new S().M;
    
  • Chiamata di metodi virtuali non sostituiti sui tipi di valore:

    enum E { A }
    E.A.GetHashCode();
    
  • Uso di modelli di costanti C # 7.0 sotto isespressione:

    int x = …;
    if (x is 42) { … } // boxes both 'x' and '42'!
    
  • Boxing nelle conversioni di tipi di tupla C #:

    (int, byte) _tuple;
    
    public (object, object) M() {
      return _tuple; // 2x boxing
    }
    
  • Parametri opzionali di objecttipo con valori predefiniti del tipo di valore:

    void M([Optional, DefaultParameterValue(42)] object o);
    M(); // boxing at call-site
    
  • Verifica del valore del tipo generico non vincolato per null:

    bool M<T>(T t) => t != null;
    string M<T>(T t) => t?.ToString(); // ?. checks for null
    M(42);
    

    nota: questo può essere ottimizzato da JIT in alcuni runtime .NET

  • Valore del test di structtipo di tipo non vincolato o generico con is/ asoperatori:

    bool M<T>(T t) => t is int;
    int? M<T>(T t) => t as int?;
    IEquatable<T> M<T>(T t) => t as IEquatable<T>;
    M(42);
    

    nota: questo può essere ottimizzato da JIT in alcuni runtime .NET

Ci sono altre situazioni di boxe, magari nascoste, che conosci?


2
Ho affrontato questo problema qualche tempo fa e l'ho trovato piuttosto interessante: Rilevare la (dis) boxe usando FxCop
George Duckett

Modi di conversioni molto belli che hai mostrato. Infatti non conoscevo il secondo o forse non l'ho mai provato :) Grazie
Zenwalker

12
dovrebbe essere una domanda wiki della comunità
Sly

2
E i tipi nullable? private int? nullableInteger
Allansson

1
@allansson, i tipi nullable sono solo una sorta di tipi di valore
controlflow

Risposte:


42

È un'ottima domanda!

La boxe si verifica esattamente per una ragione: quando abbiamo bisogno di un riferimento a un tipo di valore . Tutto ciò che hai elencato rientra in questa regola.

Ad esempio, poiché object è un tipo di riferimento, il cast di un tipo di valore in object richiede un riferimento a un tipo di valore, il che causa il boxing.

Se desideri elencare tutti gli scenari possibili, dovresti includere anche derivati, come la restituzione di un tipo di valore da un metodo che restituisce un oggetto o un tipo di interfaccia, perché questo esegue automaticamente il cast del tipo di valore sull'oggetto / interfaccia.

A proposito, anche il caso di concatenazione di stringhe che hai astutamente identificato deriva dal casting all'oggetto. L'operatore + viene tradotto dal compilatore in una chiamata al metodo Concat di string, che accetta un oggetto per il tipo di valore passato, quindi si verifica il casting all'oggetto e quindi il boxing.

Negli anni ho sempre consigliato agli sviluppatori di ricordare l'unico motivo della boxe (che ho specificato sopra) invece di memorizzare ogni singolo caso, perché l'elenco è lungo e difficile da ricordare. Questo promuove anche la comprensione del codice IL che il compilatore genera per il nostro codice C # (ad esempio + su stringa restituisce una chiamata a String.Concat). In caso di dubbi su ciò che il compilatore genera e se si verifica il boxing, è possibile utilizzare IL Disassembler (ILDASM.exe). In genere dovresti cercare il codice operativo della casella (c'è solo un caso in cui potrebbe verificarsi la boxe anche se l'IL non include il codice operativo della scatola, maggiori dettagli di seguito).

Ma sono d'accordo sul fatto che alcuni eventi di boxe siano meno evidenti. Ne hai elencato uno: chiamare un metodo non sovrascritto di un tipo di valore. In effetti, questo è meno ovvio per un altro motivo: quando controlli il codice IL non vedi il box opcode, ma il vincolo opcode, quindi anche in IL non è ovvio che avvenga la boxe! Non entrerò nei dettagli esatti perché evitare che questa risposta si allunghi ancora ...

Un altro caso per boxe meno ovvio è quando si chiama un metodo di classe base da una struttura. Esempio:

struct MyValType
{
    public override string ToString()
    {
        return base.ToString();
    }
}

Qui ToString è sovrascritto, quindi la chiamata a ToString su MyValType non genererà boxing. Tuttavia, l'implementazione chiama la base ToString e ciò causa il boxing (controlla l'IL!).

A proposito, questi due scenari di boxe non ovvi derivano anche dall'unica regola sopra. Quando un metodo viene invocato sulla classe base di un tipo di valore, deve esserci qualcosa a cui fare riferimento per la parola chiave this . Poiché la classe base di un tipo di valore è (sempre) un tipo di riferimento, la parola chiave this deve fare riferimento a un tipo di riferimento, quindi abbiamo bisogno di un riferimento a un tipo di valore e quindi il boxing si verifica a causa della singola regola.

Ecco un collegamento diretto alla sezione del mio corso .NET online che tratta in dettaglio la boxe: http://motti.me/mq

Se sei interessato solo a scenari di boxe più avanzati, ecco un link diretto lì (anche se il link sopra ti porterà anche lì una volta discusse le cose più basilari): http://motti.me/mu

Spero che possa aiutare!

Motti


1
Se a ToString()viene chiamato su un particolare tipo di valore che non lo sovrascrive, il tipo di valore verrà boxato nel sito della chiamata, o il metodo verrà inviato (senza boxing) a un override generato automaticamente che non fa altro che concatenare (con boxe) al metodo di base?
supercat

@supercat La chiamata a qualsiasi metodo che chiama baseun tipo di valore causerà la boxe. Ciò include metodi virtuali che non sono sovrascritti dalla struttura e Objectmetodi che non sono affatto virtuali (come GetType()). Vedi questa domanda .
Şafak Gür

@ ŞafakGür: So che il risultato finale sarà la boxe. Mi chiedevo quale fosse il meccanismo esatto attraverso il quale avviene. Poiché il compilatore che genera IL potrebbe non sapere se il tipo è un tipo di valore o un riferimento (potrebbe essere generico), genererà un callvirt a prescindere. Il JITter saprebbe se il tipo è un tipo valore e se sovrascrive ToString, quindi potrebbe generare il codice del sito di chiamata per eseguire il boxing; in alternativa, potrebbe generare automaticamente per ogni struttura che non sovrascrive ToStringun mehtod public override void ToString() { return base.ToString(); }e ...
supercat

... fai in modo che la boxe avvenga all'interno di quel metodo. Poiché il metodo sarebbe molto breve, potrebbe essere allineato. Fare cose con il secondo approccio consentirebbe di ToString()accedere al metodo di una struttura tramite Reflection proprio come qualsiasi altro e di usarlo per creare un delegato statico che accetta il tipo di struttura come refparametro [una cosa del genere funziona con metodi struct non ereditati], ma io ho appena provato a creare un tale delegato e non ha funzionato. È possibile creare un delegato statico per il ToString()metodo di una struttura e, in tal caso, quale dovrebbe essere il tipo di parametro?
supercat

I collegamenti sono interrotti.
OfirD

5

Chiamata al metodo GetType () non virtuale sul tipo di valore:

struct S { };
S s = new S();
s.GetType();

2
GetTyperichiede boxing non solo perché non è virtuale, ma perché le posizioni di archiviazione di tipo valore, a differenza degli oggetti heap, non hanno un campo "nascosto" che GetType()può essere utilizzato per identificare il loro tipo.
supercat

@supercat Hmmm. 1. Boxe aggiunto dal compilatore e campo nascosto utilizzato dal runtime. Può essere il compilatore che aggiunge boxing perché conosce il runtime ... 2. Quando chiamiamo ToString (stringa) non virtuale sul valore enum, richiede anche il boxing e non credo che il compilatore lo aggiunga perché conosce i dettagli di implementazione di Enum.ToString (stringa) . Quindi, penso, posso dire che la boxe si verificava sempre quando veniva chiamato il metodo non virtuale sul "tipo di valore di base".
Viacheslav Ivanov

Non avevo considerato la possibilità di Enumavere alcun metodo non virtuale, sebbene un ToString()metodo per un Enumavrebbe bisogno di avere accesso alle informazioni sul tipo. Mi chiedo se Object, ValueTypeo Enumha metodi non virtuali che potrebbero svolgere il loro lavoro senza informazioni sul tipo.
supercat

3

Menzionato nella risposta di Motti, solo illustrando con esempi di codice:

Parametri coinvolti

public void Bla(object obj)
{

}

Bla(valueType)

public void Bla(IBla i) //where IBla is interface
{

}

Bla(valueType)

Ma questo è sicuro:

public void Bla<T>(T obj) where T : IBla
{

}

Bla(valueType)

Tipo di ritorno

public object Bla()
{
    return 1;
}

public IBla Bla() //IBla is an interface that 1 inherits
{
    return 1;
}

Controllo di T non vincolato rispetto a null

public void Bla<T>(T obj)
{
    if (obj == null) //boxes.
}

Uso della dinamica

dynamic x = 42; (boxes)

Un altro

enumValue.HasFlag


0
  • Utilizzando le raccolte non generiche in System.Collectionscome ArrayListo HashTable.

Certo, queste sono istanze specifiche del tuo primo caso, ma possono essere trucchi nascosti. È incredibile la quantità di codice che incontro ancora oggi che utilizza questi invece di List<T>e Dictionary<TKey,TValue>.


0

L'aggiunta di qualsiasi valore del tipo di valore nell'ArrayList provoca il boxing:

ArrayList items = ...
numbers.Add(1); // boxing to object
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.