Desenvolvimento - C#

Tipos de dados no .NET

Todas as linguagens da arquitetura .NET têm muita coisa em comum. Uma delas é o conjunto de tipos disponíveis. O sistema de tipos do .NET é bastante característico e substancialmente diferente de outras linguagens como C++ e Visual Basic. Nesta série de dois artigos, irei explorar o sistema de tipos e, em especial, as suas características únicas. Usarei a nomenclatura e exemplos em C#, mas os tipos estão também disponíveis em outras linguagens como o Visual Basic...

por Mauro Sant'Anna



Todas as linguagens da arquitetura .NET têm muita coisa em comum. Uma delas é o conjunto de tipos disponíveis. O sistema de tipos do .NET é bastante característico e substancialmente diferente de outras linguagens como C++ e Visual Basic. Nesta série de dois artigos, irei explorar o sistema de tipos e, em especial, as suas características únicas. Usarei a nomenclatura e exemplos em C#, mas os tipos estão também disponíveis em outras linguagens como o Visual Basic.

Semelhanças com o Delphi

Quem estiver acostumado com o Delphi verá muitas semelhanças, já que tanto o Delphi como o C# foram desenvolvidos pela mesma pessoa, Anders Hejlsberg:

  • O modelo de objeto é virtualmente o mesmo: todas as classes são derivadas de um ancestral comum chamado object; a herança é simples, mas permite a implementação de múltiplas interfaces; existe um suporte direto a conceitos de componentes como eventos e propriedades, inclusive as propriedades array.
  • Todos os objetos são ponteiros (ou referências).
  • Existe um tipo lógico forte e incompatível com inteiro, o bool.
  • Existem enumerações fortes e incompatíveis com inteiros e entre si.
  • As strings e os arrays dinâmicos (array of type) são muito semelhantes.
  • Existe um grande suporte a reflections, algo chamado no Delphi de RTTI (Runtime Type Information). Na verdade as reflections do C# vão além do RTTI do Delphi. Existem, contudo, algumas diferenças em relação ao Delphi:
  • Os conjuntos (sets) não são suportados no C#.
  • Não existem ponteiros diretamente, mas quando atribuirmos um valor qualquer a uma variável to tipo object, este vira implicitamente um ponteiro.
  • As variáveis alocadas dinamicamente não precisam ser liberadas, elas estão sujeitas à “coleta de lixo”.
  • Existem tipos inteiros de quatro tamanhos: 1, 2, 4 e 8 bytes. Este último não existe no Delphi.
  • Os caracteres e strings são no padrão Unicode e usam dois bytes por caractere.

Sistema de tipos

Dividirei os tipos em três categorias: “por valor”, “por referência” e o tipo “string”, que tem algumas características dos dois. Além dos tipos intrínsecos, é possível definir novos tipos, tanto por valor como por referência.

Tipos por valor

Os tipos por valor têm as seguintes características principais:

  • São alocados diretamente na pilha.
  • Não precisam ser inicializados com o operador new.
  • A variável armazena o valor diretamente.
  • A atribuição de uma variável a outra copia o conteúdo, criando efetivamente outra cópia da variável.
  • Normalmente usados com tipos de pequeno tamanho (menos que 16 bytes), onde o uso de referências traria um custo muito grande.
  • Podem ser automaticamente convertidos para referências em um processo chamado “boxing”, descrito na segunda parte deste artigo. Não podem conter o valor null.
Tipo Implementação
byte Inteiro de 8 bits sem sinal (0 a 255).
sbyte Inteiro de 8 bits com sinal (-127 a 128).
ushort Inteiro de 16 bits sem sinal (0 a 65 535).
short Inteiro de 16 bits com sinal (-32 768 a 32 767).
uint Inteiro de 32 bits sem sinal (0 a 4 294 967 295).
int Inteiro de 32 bits com sinal (-2 147 483 648 a 2 147 483 647).
ulong Inteiro de 64 bits sem sinal (0 a 18 446 744 073 709 551 615).
long Inteiro de 64 bits com sinal (-9 223 372 036 854 775 808 a 9 223 372 036 854 775 807).
double Ponto flutuante binário IEEE de 8 bytes (±5.0×10-324 a ±1.7×10308), 15 dígitos decimais de precisão.
float Ponto flutuante binário IEEE de 4 bytes (±1.5×10-45 a ±3.4×1038), 7 dígitos decimais de precisão.
decimal Ponto flutuante decimal de 128 bits. (1.0×10-28 a 7.9×1028), 28 dígitos decimais de precisão.
bool Pode ter os valores true e false. Não é compatível com inteiro.
char Um único caractere Unicode de 16 bits. Não é compatível com inteiro.

Tabela 1: Tipos de dados por valor

Os tipos inteiros vêm em quatro tamanhos: um, dois, quatro e oito bytes, com e sem sinal. Os dois tipos de ponto flutuante IEEE não oferecem nenhuma novidade.

O tipo decimal é uma novidade: ele tem 28 dígitos de precisão e as contas são menos propensas a erros de arredondamento comuns aos formatos IEEE e odiados por quem cria software de contabilidade. Por exemplo, com o decimal 14,2 mais 0,2 dá 14,4 ao invés de 14,399999999999999 que você obteria com o tipo double. Quem usou o Turbo Pascal 3.0 talvez se lembre de uma versão especial que suportava o tipo BCD, que tinha características semelhantes. Em uma conversa com Anders Hejlsberg, consegui arrancar dele algumas risadas com a comparação.

O tipo bool armazena os valores true e false. As expressões lógicas como if e while esperam sempre uma expressão do tipo bool.

O tipo char armazena caracteres no padrão Unicode. Este padrão inclui letras em diversos alfabetos além dos “romanos” usados no ocidente. Ele inclui os alfabetos Cirílico, Árabe, Hebraico, Chinês, Japoneses, Coreano e Sânscrito.

Tipos por valor definidos pelo usuário

No C# você pode declarar novos tipos, de maneira semelhante ao Delphi.

enum

Permite declarar uma seqüência de identificadores associados, mas incompatíveis com inteiros e com outras enumerações. Praticamente idêntico às enumerações do Delphi. Exemplo:

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

struct

Permite declarar tipos que contém vários valores, identificados por um nome. Semelhante ao record do Delphi. Exemplo:

Listagem 1: Declarando tipos de valores.

public struct Point { 
   public int x, y; 

   public Point(int p1, int p2) { 
      x = p1; 
      y = p2;    
   } 
} 

As structs possuem algumas características em comum com as classes:

  • Podem ter métodos.
  • Podem ter construtores. Entretanto, existem diferenças em relação às classes:
  • Elas são tipos por valor enquanto as classes são tipos por referência.
  • Não podemos declarar um construtor que não aceite argumentos.
  • Podemos atribuir à variável this, correspondente ao self no Delphi;
  • Não suportam herança; elas são implicitamente sealed.

As structs fornecem uma alternativa mais “leve e barata” às classes, onde o custo do uso das classes (alocação dinâmica de memória, métodos virtuais e uso de ponteiros) seria muito caro. Por exemplo, um ponto (coordenada X, Y).

Tipos por referência

Os tipos por referência têm as seguintes características principais:

  • São alocados em um heap e sujeitos à “coleta de lixo” (“garbage collection”) quando não forem mais usados.
  • Devem ser inicializados com o operador new.
  • A variável armazena uma “referência”, uma espécie de ponteiro; o conteúdo em si fica no heap.
  • A atribuição de uma variável a outra copia a referência; podemos ter muitas variáveis referindo-se ao mesmo valor.
  • Normalmente usados com tipos de grande tamanho (mais que 16 bytes), onde o custo da alocação dinâmica é relativamente pequeno frente a sua flexibilidade.
  • Podem conter o valor null, embora se usarmos uma variável com o valor null a exception NullReferenceException será gerada.

Object

Tipo intrínseco. É a classe base de todas as demais, como o TObject é base no Delphi. Uma variável do tipo object pode conter valores de qualquer tipo. Os tipos por referência são armazenados diretamente; os tipos por valor são alvo de “boxing”.

Arrays

Um array é sempre criado dinamicamente em tempo de execução. Podemos ter arrays de várias dimensões e arrays de arrays. Veja um exemplo de criação e inicialização de um array de inteiros de uma dimensão:

Listagem 2: Demosntração de um array

int[] myIntArray = new int[5] { 1, 2, 3, 4, 5 };

Class

Tipo definido pelo usuário e correspondem a uma class no Delphi As classes são sempre derivadas de object e podem conter campos, métodos e propriedades. Uma classe pode derivar de uma única outra classe, e também de várias interfaces. Veja um exemplo:

Listagem 3: Classe Tempo

public class Tempo {
protected int H; protected int M; protected int S;
public Tempo() {
Ajusta(0, 0, 0);
}
public Tempo(int _H, int _M, int _S) {
Ajusta(_H, _M, _S);
}
public void Ajusta(int _H, int _M, int _S) {
H = _H; M = _M; S = _S;
Normaliza();
}
public string ParaString() {
return string.Format("{0}:{1}:{2}", new object[] {H, M, S});
}
void Normaliza() {
M = M + S / 60;
S = S % 60;
H = H + M / 60;
M = M % 60;
}
public double Hora {
get {
return H + (M / 60.0) + (S / 3600.0);
}
set {
M = 0; S = 0;
H = (int) value;
double Sobra = value - H;
S = (int)(Sobra * 3600);
Normaliza();
}
}
override public string ToString() {
return ParaString();
}
}

Interface

Tipo definido pelo usuário. Uma interface é uma espécie de classe, mas contém apenas os “protótipos” dos métodos, sem a sua implementação. Corresponde no Delphi a uma interface. Uma classe, além de ser derivada de outra, pode implementar várias interfaces. Veja um exemplo:

Listagem 4: Interface derivada

// Declara a interface
interface IControl
{
void Paint();
}
// Cria um interface derivada
interface ITextBox: IControl
{
void SetText(string text);
}
// A classe implementa a interface
class TextBox: ITextBox
{
void IControl.Paint() {...}
void ITextBox.SetText(string text) {...}

Delegate

Tipo definido pelo usuário. É um “ponteiro de função orientado a objeto. Podemos atribuir uma lista de métodos a um delegate e chamá-los ao invocar o delegate. O delegate corresponde mais ou menos a um “procedure of object” do Delphi, mas pode também apontar para métodos static (métodos class no Delphi) e para uma lista de métodos.Veja um exemplo:

Listagem 5: Exemplo de delegate

// Declara um delegate. É um método que não aceita argumentos e retorna inteiros
delegate int MyDelegate();
// Declara uma classe. Note que os métodos tem a mesma “assinatura” do delegate acima:
// retornam um inteiro e não aceitam argumentos
public class MyClass
{
public int InstanceMethod ()
{
Console.WriteLine("A message from the instance method.");
return 0;
}
static public int StaticMethod ()
{
Console.WriteLine("A message from the static method.");
return 0;
}
}
public class MainClass
{
static public void Main ()
{
MyClass p = new MyClass();
// Mapeia o delegate ao método da classe criada acima
MyDelegate d = new MyDelegate(p.InstanceMethod);
// Chama o método via delegate
d();
// Mapeia outro método (agora é static)
d = new MyDelegate(MyClass.StaticMethod);
// Chama o método via delegate
d();
}
}

Tipo string

As strings são tecnicamente um tipo por referência, mas possuem algumas características especiais:

  • Não precisam ser inicializadas com o operador new.
  • A atribuição de uma variável a outra funciona como se copiasse o conteúdo, criando efetivamente outra cópia da variável.
  • Uma string contendo o valor null é uma string vazia; não é um erro usá-la.
  • Você não pode criar uma classe derivada de string.

As strings contêm caracteres Unicode e podem ter até 1G de comprimento. Veja um exemplo:

Listagem 6: Utilizando Strings

// Declara e inicializa uma string
string Name = "Mary";
// Copia para outra string. Se alterarmos uma delas, a outra manterá o seu valor
string NewName = Name;
// Atribui à string antiga
Name = "John";
// Exibe "John - Mary"
System.Console.WriteLine(Name + " - " + NewName);

Conclusão

O C# contém uma estrutura de tipos baseada no Delphi, mas com várias novidades que o tornam mais simples e ao mesmo tempo mais poderoso.

Mauro Sant'Anna

Mauro Sant'Anna - Mauro tem mais de 20 anos de experiência no desenvolvimento de software, com produtos publicados no Brasil, Portugal e Estados Unidos, além de extensa experiência em treinamento e consultoria no desenvolvimento de software, tanto criando material como ministrando cursos.
Mauro é um "Microsoft Most Valuable Professional" (MVP - www.microsoft.com/mvp), “Microsoft Regional Director” (RD - www.microsoft.com/rd), membro do INETA Speaker’s Bureau (www.ineta.org) e possui as certificações MCP, MCSA (Windows 2000/2003), MCAD (C# e VB), MCDBA, MCSE (Windows 2000/2003).
Sua empresa, a M. A. S Informática (www.mas.com.br), treinou centenas de turmas em desenvolvimento de software nos últimos anos.