terça-feira, 27 de fevereiro de 2007

Conhecendo C# - Tipos - Parte 6

Conhecendo C# - Tipos - Parte 6
Boxing e Unboxing

Diferentemente de algumas linguagens orientada à objetos, o C# não exige um wrapper(empacotador) para manipular objetos e tipos, uma técnica muito mais poderosa é utilizada neste caso boxing e unboxing.

Boxing é a conversão de um tipo valor para um tipo referência e unboxing é o processo contrário desta conversão, ou seja extrai-se o conteúdo do tipo valor de um tipo referência herdado de System.ValueType. Somente conversões explícitas são suportadas para unboxing.

//Boxing
int x = 12345;
object o = x;

//Unboxing
x = (int)o;


Os tipos de dados herdados por System.ValueTypes são alocados em stack, por serem implementados como estrutura(struct), vide documentação de System.Int32 no .NET Framework SDK, por exemplo. Quando uma variável do tipo valor é utilizada, apenas seu contéudo é alocado em memória, no entanto se essa variável sofrer uma operação de boxing, seu valor será copiado para a heap e será acrescentado o overhead(excesso) do objeto System.ValueTypes(objeto herdado de System.Object, raíz de todos objetos do .NET Framework). A figura 4 exibe o estado do exemplo anterior que manipula um inteiro nas duas formas: boxing e unboxing.


Figura 4: Representação em memória de uma variável int nas formas boxing e unboxing

O que deve ficar claro no processo de conversão, é que o boxing pode ser tratado automáticamente, ou seja implícito, por exemplo para um inteiro(int) tornar-se um objeto(object) poderemos encontrar object o = 12345, neste caso o valor 12345 é tratado como inteiro(int). No entanto na forma unboxing, o tratamento explícito é requerido int x = (int)o, assumindo que o representa um object. Se a partir da forma boxing de um valor, através de conversão explícita, deseja-se obter a forma unboxing do mesmo, o cast no processo de unboxing deverá ser coerente com o tipo específicado, caso contrário um excessão InvalidCastException será disparada, isto também é válido para casts inconsistenes. O código abaixo demonstra isso:

long l = 12345;
object o = (long)l;

//Unboxing inconsistente
int i = (int)o; //excessão InvalidCastException

Segue a correção para o exemplo acima:

long l = 12345;
object o = (long)l;

//Unboxing consistente
int i = (int)(long)o;

Uma outra forma de utilizar as conversões é atráves da operação através de seus membros, ou seja, a partir de um conteúdo ou de uma variável é possível especificar a função ou método de conversão desejado. Se uma variável ou valor é de um tipo determinado, por exemplo, de um tipo int é correto utilizar i.ToInt64() ou 12345.ToInt64() para obter-se um valor do tipo long. O código anterior pode ser reescrito para:

object o = 12345.ToInt64(); //Valor tratado na forma boxing
//Unboxing consistente
int i = (int)(long)o;

De acordo com documentação do .NET Framework, se utilizarmos o método WriteLine da classe Console com uma string formatadora e vários tipos de dados, esses tipos serão tratados como um vetor(array) de objetos, conforme figura 5. Então quantos boxings serão executados na linha abaixo?

long l = 1000;
int i = 500;
Console.WriteLine(“{0} {1} {2} {3} {4} {5}”,l,i,i,l,l,i);

Figura 5: Uma das possibilidades do método WriteLine da classe Console

Seriam necessários 6 boxings para última linha, um para cada variável a ser tratada como objeto. O processo de boxing consome memória e tempo de execução da CPU, portanto o código acima é eficientemente reescrito para:

long l = 1000;
int i = 500;
object ol = l;
object oi = i;
Console.WriteLine(“{0} {1} {2} {3} {4} {5}”,ol,oi,oi,ol,ol,oi);

Neste caso o número de boxings é reduzido 2. Este tipo de cuidado sempre deverá ser tomado para a obtenção da melhor performance do código, resultando menos tempo de processamento e recursos consumidos.

Ir para o índice do Manual.
Próximo artigo: .

2 comentários:

Unknown disse...

Olá, estive olhando e percebi que a inicialização dos valores int do c# é 0 correto, mas se ela deriva de System.Object que por padrão o valor é null, porque o tipo int o valor padrão é 0?
seria uma auto inicialização do tipo primitivo?
teria alguma forma de não deixar ele inicializar com 0 e sim com null?
Obrigado

Unknown disse...

Para isso você deve criar o tipo anulável.

{
int? contador = null;
if (contador == null)
// seu codigo
}

Mais a frente vou publicar um artigo explicando sobre os tipos anuláveis.

Neobux