Sunday, July 01, 2007

MAIS UMA INTRO À .NET FRAMEWORK 2.0 - Parte IV

Artigo anterior: MAIS UMA INTRO À .NET FRAMEWORK 2.0 - Parte III

4. Conversões entre tipos

Conversões entre tipos é uma das tarefas mais comuns em programação orientada a objectos. Várias vezes é necessário converter uma variável de um tipo para outro antes de a passar como parâmetro de um método ou para executar determinado cálculo.
Existem dois tipos de conversões: implícitas e explícitas. Em C# só é possível efectuar conversões implícitas entre tipos se não existir perda de precisão. Isto significa que é possível efectuar conversão implícita em C# apenas e só se o tipo de destino puder aceitar todos os valores possíveis para o tipo de origem. Isto é denominado de conversão generalista (widening conversion):

int i = 1;
double d = 1.0001;
d = i; // Conversão implícita permitida

Se o espectro de conversão ou precisão do tipo de origem excede o do tipo de destino, a operação denomina-se de conversão de estreitamento (narrowing conversion) e normalmente necessita que a conversão seja explícita. Existem várias formas de realizar uma conversão explícita:
System.Convert: Converte entre tipos que implementem o Interface System.IConvertible;
(tipo) cast: Converte entre tipos que definem operadores de conversão;
tipo.ToString e tipo.Parse: Converte entre objectos do tipo string e objectos de tipos base. Se a conversão não for possível, a operação resulta numa excepção;
tipo.TryParse e tipo.TryParseExact: Converte entre um objecto do tipo string e um objecto de um tipo base. Se a conversão não for possível, devolve um valor false;
Os métodos TryParse, TryParseExact e TryCast são novos na .NET Framework 2.0. Conversões explícitas falham se o valor de origem exceder o espectro de valores aceites pelo tipo de destino ou se a conversão entre os tipos não estiver definida, por isso este tipo de conversões deve ser incluído entre blocos Try ou utilizando os métodos TryCast ou TryParse para ser possível verificar o valor de retorno.

4.1. O que é Boxing e Unboxing?

Boxing acontece quando se converte um tipo de valor para um tipo por referência e Unboxing é, naturalmente, o processo inverso, ou seja, a conversão de um tipo de referência para um tipo de valor. No exemplo seguinte podemos ver um exemplo de Boxing através da conversão de um Integer (tipo de valor) para um Object (tipo por referência):

int i = 123;
object o = (object)i; // Conversão através da utilização de um type cast
Da mesma forma, se pode exemplificar o processo inverso, ou seja, Unboxing:
object o = 123;
int i = (int)o;
As operações de Boxing e Unboxing implicam um acréscimo de processamento (overhead), por isso devem ser evitados quando se programam tarefas que se repetem de uma forma intensa. Boxing também ocorre quando são chamados métodos virtuais de uma estrutura que herde de System.Object como, por exemplo, o método ToString(). Assim, para evitar procedimentos de Boxing desnecessários deve ter-se as seguintes sugestões em consideração:

• Implementar versões específicas por tipo (overloads) de métodos que aceitem vários tipos de valor. É uma boa prática criar várias versões do mesmo método com parâmetros diferentes e definitivamente é melhor solução do que implementar apenas uma versão desse método que aceite um parâmetro do tipo Object;
• É preferível utilizar genéricos sempre que possível em vez de utilizar argumentos do tipo Object;
• Quando estamos a criar as nossas próprias estruturas, devemos fazer as nossas próprias versões (override) dos métodos virtuais ToString, Equals e GetHash.

4.2. Como implementar conversão em tipos customizados?

Existem várias formas de definir conversões entre tipos definidos por nós. A técnica a escolher depende do tipo de conversão que pretendemos executar:

• Deve definir-se operadores de conversão para simplificar conversões implícitas e explícitas entre tipos numéricos. De notar que os operadores de conversão são uma novidade na .NET Framework 2.0;
• Deve implementar-se versões customizadas (override) do método ToString para conversões onde o objecto de destino é do tipo string e do método Parse para conversões em que o objecto de origem é do tipo string;
• Deve implementar-se o interface IConvertible para permitir conversões utilizando System.Convert, especialmente quando se implementam conversões específicas de uma cultura (Culture);
• Deve implementar-se uma classe TypeConverter para permitir conversão em Design Time, que será utilizada na janela de propriedades do Visual Studio (para mais informações, visitem http://msdn2.microsoft.com/en-us/library/37899azc.aspx).
A definição de operadores de conversão permitem a atribuição directa de um tipo de valor para um tipo customizado. Deve usar-se a instrução implicit para conversões que não impliquem perda de precisão e a instrução explicit para conversões que estão sujeitas a perda de precisão:
struct TipoA
{
public int Value;

// Permite conversão implícita de um inteiro
public static implicit operator TipoA(int arg)
{
TipoA res = new TipoA();
res.Value = arg;
return res;
}

// Permite conversão explícita para um inteiro
public static explicit operator int(TipoA arg)
{
return arg.Value;
}

// Permite conversão para String (evita Boxing)
public override string ToString()
{
return this.Value.ToString();
}
}

O exemplo anterior também efectua uma costumização do método virtual ToString par evitar o processo de Boxing. Agora seria possível atribuir inteiros a este tipo costumizado, como se pode ver abaixo:
TipoA a;
int i;
// Conversão implícita é aceite
a = 42; // Em vez de se utilizar a.Value = 42
// Conversão explícita
i = (int)a; // Em vez de i = a.Value
Console.WriteLine(“a = {0}, i = {1}”, a.ToString(), i.ToString());

Para se implementar o interface System.IConvertible é necessário especificar isso na definição do tipo costumizado. A implementação deste interface implica definir implementação para 17 métodos, incluindo GetTypeCode, ChangeType e ToType para cada tipo base com o qual se pretenda efectuar operações de conversão. Não é necessário implementar todos os métodos e alguns, como por exemplo ToDateTime, serão provavelmente inválidos. Para métodos inválidos, basta criar uma excepção. Após a implementação do interface IConvertible, pode-se converter a nossa classe utilizando System.Convert:

TipoA a;
bool b;
a = 1;
// Conversão utilizando ToBoolean
b = Convert.ToBoolean(a);
Console.WriteLine(“a = {0}, b = {1}”, a.ToString(), b.ToString());