Estou tentando coletar todas as situações em que o boxe ocorre em C #:
Convertendo tipo de valor em
System.Object
tipo:struct S { } object box = new S();
Convertendo tipo de valor em
System.ValueType
tipo:struct S { } System.ValueType box = new S();
Convertendo o valor do tipo de enumeração em
System.Enum
tipo:enum E { A } System.Enum box = E.A;
Convertendo o tipo de valor em referência de interface:
interface I { } struct S : I { } I box = new S();
Usando tipos de valor na concatenação de string C #:
char c = F(); string s1 = "char value will box" + c;
nota: constantes de
char
tipo são concatenadas em tempo de compilaçãonota: desde a versão 6.0 C # compilador otimiza concatenação envolvendo
bool
,char
,IntPtr
,UIntPtr
tiposCriando delegado a partir do método de instância do tipo de valor:
struct S { public void M() {} } Action box = new S().M;
Chamando métodos virtuais não substituídos em tipos de valor:
enum E { A } E.A.GetHashCode();
Usando padrões de constantes C # 7.0 sob
is
expressão:int x = …; if (x is 42) { … } // boxes both 'x' and '42'!
Encaixe em conversões de tipos de tupla C #:
(int, byte) _tuple; public (object, object) M() { return _tuple; // 2x boxing }
Parâmetros opcionais de
object
tipo com valores padrão de tipo de valor:void M([Optional, DefaultParameterValue(42)] object o); M(); // boxing at call-site
Verificando o valor do tipo genérico irrestrito para
null
:bool M<T>(T t) => t != null; string M<T>(T t) => t?.ToString(); // ?. checks for null M(42);
observação: isso pode ser otimizado por JIT em alguns tempos de execução .NET
Valor de teste de
struct
tipo de tipo irrestrito ou genérico comis
/as
operadores: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);
observação: isso pode ser otimizado por JIT em alguns tempos de execução .NET
Existem mais situações de boxe, talvez escondidas, que você conheça?
fonte
private int? nullableInteger
Respostas:
Essa é uma ótima pergunta!
O boxing ocorre exatamente por um motivo: quando precisamos de uma referência a um tipo de valor . Tudo o que você listou se enquadra nesta regra.
Por exemplo, uma vez que objeto é um tipo de referência, converter um tipo de valor para objeto requer uma referência a um tipo de valor, o que causa boxe.
Se você deseja listar todos os cenários possíveis, você também deve incluir derivados, como retornar um tipo de valor de um método que retorna um objeto ou um tipo de interface, porque isso converte automaticamente o tipo de valor para o objeto / interface.
A propósito, o caso de concatenação de string que você identificou astutamente também deriva da conversão para o objeto. O operador + é traduzido pelo compilador para uma chamada ao método Concat de string, que aceita um objeto para o tipo de valor que você passa, portanto, a conversão para o objeto e, portanto, ocorre o boxing.
Com o passar dos anos, sempre aconselhei os desenvolvedores a lembrar o único motivo do boxe (eu especifiquei acima) em vez de memorizar cada caso, porque a lista é longa e difícil de lembrar. Isso também promove a compreensão de qual código IL o compilador gera para nosso código C # (por exemplo, + em string produz uma chamada para String.Concat). Quando você estiver em dúvida sobre o que o compilador gera e se ocorrer um boxing, você pode usar o IL Disassembler (ILDASM.exe). Normalmente, você deve procurar o opcode da caixa (há apenas um caso em que o boxing pode ocorrer mesmo que o IL não inclua o opcode da caixa, mais detalhes abaixo).
Mas concordo que algumas ocorrências de boxe são menos óbvias. Você listou um deles: chamar um método não substituído de um tipo de valor. Na verdade, isso é menos óbvio por outro motivo: quando você verifica o código IL, você não vê o opcode da caixa, mas o opcode da restrição, então mesmo no IL não é óbvio que o boxing aconteça! Não vou entrar em detalhes exatos por que evitar que esta resposta se torne ainda mais longa ...
Outro caso de boxe menos óbvio é ao chamar um método de classe base de uma estrutura. Exemplo:
struct MyValType { public override string ToString() { return base.ToString(); } }
Aqui, ToString é substituído, portanto, chamar ToString em MyValType não gerará boxing. No entanto, a implementação chama o ToString base e isso causa boxe (verifique o IL!).
A propósito, esses dois cenários de boxe não óbvios também derivam da única regra acima. Quando um método é chamado na classe base de um tipo de valor, deve haver algo para a palavra-chave this se referir. Visto que a classe base de um tipo de valor é (sempre) um tipo de referência, a palavra - chave this deve se referir a um tipo de referência e, portanto, precisamos de uma referência a um tipo de valor e, portanto, o boxing ocorre devido à regra única.
Aqui está um link direto para a seção do meu curso online .NET que discute boxe em detalhes: http://motti.me/mq
Se você está interessado apenas em cenários de boxe mais avançados, aqui está um link direto (embora o link acima o leve até lá também, uma vez que discute o material mais básico): http://motti.me/mu
Eu espero que isso ajude!
Motti
fonte
ToString()
for chamado em um tipo de valor específico que não o substitui, o tipo de valor será encaixotado no local da chamada ou o método será despachado (sem encaixotamento) para uma substituição gerada automaticamente que não faz nada além de encadear (com boxe) para o método básico?base
um tipo de valor causará boxing. Isso inclui métodos virtuais que não são substituídos pela estrutura e osObject
métodos que não são virtuais (comoGetType()
). Veja esta pergunta .ToString
um mehtodpublic override void ToString() { return base.ToString(); }
e ...ToString()
método de uma estrutura fosse acessado via Reflection como qualquer outro e usado para criar um delegado estático que toma o tipo de estrutura comoref
parâmetro [tal coisa funciona com métodos de estrutura não herdados], mas eu apenas tentei criar um delegado e não funcionou. É possível criar um delegado estático para oToString()
método de uma estrutura e, em caso afirmativo, qual deve ser o tipo de parâmetro?Chamando o método GetType () não virtual no tipo de valor:
struct S { }; S s = new S(); s.GetType();
fonte
GetType
requer boxing não apenas porque não é virtual, mas porque os locais de armazenamento de tipo de valor, ao contrário dos objetos de heap, não têm um campo "oculto" queGetType()
pode ser usado para identificar seu tipo.Enum
ter nenhum método não virtual próprio, embora umToString()
método para umEnum
precise ter acesso para digitar informações. Eu me pergunto seObject
,ValueType
ouEnum
tem algum método não virtual que possa executar seus trabalhos sem informações de tipo.Mencionado na resposta de Motti, apenas ilustrando com exemplos de código:
Parâmetros envolvidos
public void Bla(object obj) { } Bla(valueType) public void Bla(IBla i) //where IBla is interface { } Bla(valueType)
Mas isso é seguro:
public void Bla<T>(T obj) where T : IBla { } Bla(valueType)
Tipo de retorno
public object Bla() { return 1; } public IBla Bla() //IBla is an interface that 1 inherits { return 1; }
Verificando T irrestrito contra nulo
public void Bla<T>(T obj) { if (obj == null) //boxes. }
Uso de dinâmica
dynamic x = 42; (boxes)
Outro
enumValue.HasFlag
fonte
System.Collections
comoArrayList
ouHashTable
.Concedido, essas são instâncias específicas do seu primeiro caso, mas podem ser pegadinhas ocultas. É incrível a quantidade de código que ainda encontro hoje que usa esses em vez de
List<T>
eDictionary<TKey,TValue>
.fonte
Adicionar qualquer valor de tipo de valor ao ArrayList causa boxe:
ArrayList items = ... numbers.Add(1); // boxing to object
fonte