Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
C# é uma linguagem fortemente tipada. Cada variável e constante tem um tipo, assim como todas as expressões que são avaliadas como um valor. Cada declaração de método especifica um nome, o tipo e a forma (valor, referência ou saída) de cada parâmetro de entrada e do valor de retorno. A biblioteca de classes .NET define tipos numéricos internos e tipos complexos que representam uma ampla variedade de constructos. Isso inclui o sistema de arquivos, conexões de rede, coleções e matrizes de objetos e datas. Um programa C# típico usa tipos da biblioteca de classes e tipos definidos pelo usuário que modelam os conceitos específicos para o domínio de problema do programa.
As informações armazenadas em um tipo podem incluir os seguintes itens:
- O espaço de armazenamento que uma variável do tipo requer.
- Os valores máximos e mínimos que ele pode representar.
- Os membros (métodos, campos, eventos e assim por diante) que ele contém.
- O tipo base do qual ele herda.
- As interfaces que ele implementa.
- As operações permitidas.
O compilador usa informações de tipo para garantir que todas as operações executadas em seu código sejam de tipo seguro. Por exemplo, se você declarar uma variável de tipo int
, o compilador permitirá que você use a variável em operações de adição e subtração. Se você tentar executar essas mesmas operações em uma variável de tipo bool
, o compilador gerará um erro, conforme mostrado no exemplo a seguir:
int a = 5;
int b = a + 2; //OK
bool test = true;
// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;
Observação
Os desenvolvedores C e C++, observem que, em C#, bool
não é conversível para int
.
O compilador insira as informações de tipo no arquivo executável como metadados. O CLR (Common Language Runtime) usa esses metadados em tempo de execução para garantir ainda mais a segurança do tipo quando aloca e recupera a memória.
Especificando tipos em declarações de variável
Ao declarar uma variável ou constante em um programa, você deve especificar seu tipo ou usar a var
palavra-chave para permitir que o compilador infera o tipo. O exemplo a seguir mostra algumas declarações variáveis que usam tipos numéricos internos e tipos complexos definidos pelo usuário:
// Declaration only:
float temperature;
string name;
MyClass myClass;
// Declaration with initializers (four examples):
char firstLetter = 'C';
var limit = 3;
int[] source = [0, 1, 2, 3, 4, 5];
var query = from item in source
where item <= limit
select item;
Os tipos de parâmetros de método e valores retornados são especificados na declaração do método. A assinatura a seguir mostra um método que requer um int
argumento de entrada e retorna uma cadeia de caracteres:
public string GetName(int ID)
{
if (ID < names.Length)
return names[ID];
else
return String.Empty;
}
private string[] names = ["Spencer", "Sally", "Doug"];
Depois de declarar uma variável, você não poderá reenviá-la com um novo tipo e não poderá atribuir um valor não compatível com seu tipo declarado. Por exemplo, você não pode declarar um int
e, em seguida, atribuir a ele um valor booliano de true
. No entanto, os valores podem ser convertidos em outros tipos, por exemplo, quando são atribuídos a novas variáveis ou passados como argumentos de método. Uma conversão de tipo que não causa perda de dados é executada automaticamente pelo compilador. Uma conversão que pode causar perda de dados requer um cast no código-fonte.
Para obter mais informações, consulte Conversões Cast e Conversões de Tipo.
Tipos internos
O C# fornece um conjunto padrão de tipos internos. Eles representam inteiros, valores de ponto flutuante, expressões boolianas, caracteres de texto, valores decimais e outros tipos de dados. Também há tipos string
e object
internos. Esses tipos estão disponíveis para você usar em qualquer programa C#. Para obter a lista completa dos tipos internos, consulte tipos internos.
Tipos personalizados
Use as construções struct
, class
, interface
, enum
e record
para criar seus próprios tipos personalizados. A própria biblioteca de classes do .NET é uma coleção de tipos personalizados que você pode usar em seus próprios aplicativos. Por padrão, os tipos usados com mais frequência na biblioteca de classes estão disponíveis em qualquer programa C#. Outras pessoas ficam disponíveis somente quando você adiciona explicitamente uma referência de projeto ao assembly que as define. Depois que o compilador tiver uma referência ao assembly, você poderá declarar variáveis (e constantes) dos tipos declarados nesse assembly no código-fonte. Para obter mais informações, consulte a Biblioteca de Classes do .NET.
Uma das primeiras decisões tomadas ao definir um tipo é decidir qual construção usar para seu tipo. A lista a seguir ajuda a tomar essa decisão inicial. Há sobreposição nas escolhas. Na maioria dos cenários, mais de uma opção é uma opção razoável.
- Se o tamanho do armazenamento de dados for pequeno, não mais do que 64 bytes, escolha um
struct
ourecord struct
. - Se o tipo for imutável ou se você quiser mutação não estruturativa, escolha um
struct
ourecord struct
. - Se o seu tipo deve ter semântica de valor para igualdade, escolha um
record class
ourecord struct
. - Se o tipo for usado principalmente para armazenar dados, não para comportamento, escolha um
record class
ourecord struct
. - Se o tipo fizer parte de uma hierarquia de herança, escolha um
record class
ou umclass
. - Se o tipo usar polimorfismo, escolha um
class
. - Se a finalidade primária for o comportamento, escolha um
class
.
O sistema de tipo comum
É importante entender dois pontos fundamentais sobre o sistema de tipos no .NET:
- Ele dá suporte ao princípio da herança. Os tipos podem derivar de outros tipos, chamados de tipos base. O tipo derivado herda (com algumas restrições) os métodos, as propriedades e outros membros do tipo base. O tipo base pode, por sua vez, derivar de algum outro tipo, nesse caso, o tipo derivado herda os membros de ambos os tipos base em sua hierarquia de herança. Todos os tipos, incluindo tipos numéricos internos, como System.Int32 (palavra-chave C#:
int
), derivam, em última análise, de um único tipo base, que é System.Object (palavra-chave C#:object
). Essa hierarquia de tipo unificado é chamada de CTS ( Common Type System ). Para obter mais informações sobre herança em C#, consulte Herança. - Cada tipo no CTS é definido como um tipo de valor ou um tipo de referência. Esses tipos incluem todos os tipos personalizados na biblioteca de classes do .NET e também seus próprios tipos definidos pelo usuário. Os tipos que você define usando a
struct
palavra-chave são tipos de valor; todos os tipos numéricos internos sãostructs
. Os tipos que você define usando a palavra-chaveclass
ou a palavra-chaverecord
são tipos de referência. Tipos de referência e tipos de valor têm regras de tempo de compilação diferentes e comportamento de tempo de execução diferente.
A ilustração a seguir mostra a relação entre tipos de valor e tipos de referência no CTS.
Observação
Você pode ver que os tipos mais usados são todos organizados no System namespace. No entanto, o namespace no qual um tipo está contido não tem relação com se é um tipo de valor ou tipo de referência.
Classes e structs são duas das construções básicas do sistema de tipo comum no .NET. Cada uma é essencialmente uma estrutura de dados que encapsula um conjunto de dados e comportamentos que pertencem juntos como uma unidade lógica. Os dados e comportamentos são os membros da classe, struct ou registro. Os membros incluem seus métodos, propriedades, eventos e assim por diante, conforme listado posteriormente neste artigo.
Uma declaração de classe, struct ou registro é como um blueprint usado para criar instâncias ou objetos em tempo de execução. Se você definir uma classe, struct ou registro nomeado Person
, Person
será o nome do tipo. Se você declarar e inicializar uma variável p
do tipo Person
, p
será considerado um objeto ou instância de Person
. Várias instâncias do mesmo tipo de Person
podem ser criadas e cada instância pode ter valores diferentes em suas propriedades e campos.
Uma classe é um tipo de referência. Quando um objeto do tipo é criado, a variável à qual o objeto é atribuído contém apenas uma referência a essa memória. Quando a referência de objeto é atribuída a uma nova variável, a nova variável se refere ao objeto original. As alterações feitas por meio de uma variável são refletidas na outra variável porque ambas se referem aos mesmos dados.
Um struct é um tipo de valor. Quando um struct é criado, a variável à qual o struct é atribuído contém os dados reais do struct. Quando o struct é atribuído a uma nova variável, ele é copiado. A nova variável e a variável original, portanto, contêm duas cópias separadas dos mesmos dados. As alterações feitas em uma cópia não afetam a outra cópia.
Os tipos de registro podem ser tipos de referência (record class
) ou tipos de valor (record struct
). Os tipos de registro contêm métodos que dão suporte à igualdade de valor.
Em geral, as classes são usadas para modelar um comportamento mais complexo. As classes normalmente armazenam dados que devem ser modificados após a criação de um objeto de classe. Structs são mais adequados para estruturas de dados pequenas. Os structs normalmente armazenam dados que não devem ser modificados após a criação do struct. Os tipos de registro são estruturas de dados com membros adicionais sintetizados pelo compilador. Normalmente, os registros armazenam dados que não devem ser modificados após a criação do objeto.
Tipos de valor
Os tipos de valor derivam de System.ValueType, que deriva de System.Object. Os tipos que derivam de System.ValueType têm um comportamento especial no CLR. As variáveis de tipo de valor contêm diretamente seus valores. A memória de um struct é embutida em qualquer contexto em que a variável seja declarada. Não há nenhuma alocação de heap separada ou sobrecarga de coleta de lixo para variáveis de tipo de valor. É possível declarar tipos record struct
que são tipos de valor e incluir os membros sintetizados para registros.
Há duas categorias de tipos de valor: struct
e enum
.
Os tipos numéricos internos são structs e têm campos e métodos que você pode acessar:
// constant field on type byte.
byte b = byte.MaxValue;
Mas você declara e atribui valores a eles como se fossem tipos simples de não agregação:
byte num = 0xA;
int i = 5;
char c = 'Z';
Os tipos de valor são lacrados. Você não pode derivar um tipo de qualquer tipo de valor, por exemplo, System.Int32. Você não pode definir um struct para herdar de qualquer classe ou struct definido pelo usuário porque um struct só pode herdar de System.ValueType. No entanto, um struct pode implementar uma ou mais interfaces. É possível converter um tipo struct em qualquer tipo de interface que ele implementa. Essa conversão faz com que uma operação de conversão boxing encapsule o struct dentro de um objeto de tipo de referência no heap gerenciado. As operações de conversão boxing ocorrem quando você passa um tipo de valor para um método que usa um System.Object ou qualquer tipo de interface como parâmetro de entrada. Para obter mais informações, consulte Boxing e Unboxing.
Use a palavra-chave struct para criar seus próprios tipos de valor personalizados. Normalmente, um struct é usado como um contêiner para um pequeno conjunto de variáveis relacionadas, conforme mostrado no exemplo a seguir:
public struct Coords
{
public int x, y;
public Coords(int p1, int p2)
{
x = p1;
y = p2;
}
}
Para obter mais informações sobre structs, consulte Tipos de estrutura. Para obter mais informações sobre tipos de valor, consulte Tipos de valor.
A outra categoria de tipos de valor é enum
. Uma enumeração define um conjunto de constantes integrais nomeadas. Por exemplo, a enumeração System.IO.FileMode na biblioteca de classes .NET contém um conjunto de inteiros constantes nomeados que especificam como um arquivo deve ser aberto. Ele é definido conforme mostrado no exemplo a seguir:
public enum FileMode
{
CreateNew = 1,
Create = 2,
Open = 3,
OpenOrCreate = 4,
Truncate = 5,
Append = 6,
}
A constante System.IO.FileMode.Create tem um valor de 2. No entanto, o nome é muito mais significativo para humanos que leem o código-fonte e, por esse motivo, é melhor usar enumerações em vez de números literais constantes. Para obter mais informações, consulte System.IO.FileMode.
Todas as enumerações herdam de System.Enum, que herda de System.ValueType. Todas as regras que se aplicam a structs também se aplicam a enumerações. Para obter mais informações sobre enumerações, consulte tipos de enumeração.
Tipos de referência
Um tipo que é definido como class
, record
, delegate
, matriz ou interface
é um reference type
.
Quando você declara uma variável de um reference type
, ela contém o valor null
até que você o atribua com uma instância desse tipo ou crie uma usando o new
operador. A criação e a atribuição de uma classe são demonstradas no exemplo a seguir:
MyClass myClass = new MyClass();
MyClass myClass2 = myClass;
Um interface
não pode ser instanciado diretamente usando o operador new
. Em vez disso, crie e atribua uma instância de uma classe que implementa a interface. Considere o seguinte exemplo:
MyClass myClass = new MyClass();
// Declare and assign using an existing value.
IMyInterface myInterface = myClass;
// Or create and assign a value in a single statement.
IMyInterface myInterface2 = new MyClass();
Quando o objeto é criado, a memória é alocada no heap gerenciado. A variável contém apenas uma referência ao local do objeto. Os tipos no heap gerenciado exigem sobrecarga quando são alocados e recuperados. A coleta de lixo é a funcionalidade de gerenciamento automático de memória do CLR, que executa o reaproveitamento. No entanto, a coleta de lixo também é altamente otimizada e, na maioria dos cenários, não cria um problema de desempenho. Para obter mais informações sobre a coleta de lixo, consulte Gerenciamento Automático de Memória.
Todas as matrizes são tipos de referência, mesmo que seus elementos sejam tipos de valor. As matrizes derivam implicitamente da classe System.Array. Declare e use-os com a sintaxe simplificada fornecida pelo C#, conforme mostrado no exemplo a seguir:
// Declare and initialize an array of integers.
int[] nums = [1, 2, 3, 4, 5];
// Access an instance property of System.Array.
int len = nums.Length;
Os tipos de referência dão suporte total à herança. Quando você cria uma classe, é possível herdar de qualquer outra interface ou classe que não esteja definida como selada. Outras classes podem herdar de sua classe e substituir seus métodos virtuais. Para obter mais informações sobre como criar suas próprias classes, consulte Classes, structs e registros. Para obter mais informações sobre herança e métodos virtuais, consulte Herança.
Tipos de valores literais
Em C#, os valores literais recebem um tipo do compilador. Você pode especificar como um literal numérico deve ser digitado acrescentando uma letra ao final do número. Por exemplo, para especificar que o valor 4.56
deve ser tratado como um float
, acrescente um "f" ou "F" após o número: 4.56f
. Se nenhuma letra for anexada, o compilador inferirá um tipo para o literal. Para obter mais informações sobre quais tipos podem ser especificados com sufixos de letra, consulte tipos numéricos integrais e tipos numéricos de ponto flutuante.
Como os literais são tipados e todos os tipos derivam basicamente de System.Object, você pode escrever e compilar o código como o seguinte:
string s = "The answer is " + 5.ToString();
// Outputs: "The answer is 5"
Console.WriteLine(s);
Type type = 12345.GetType();
// Outputs: "System.Int32"
Console.WriteLine(type);
Tipos genéricos
Um tipo pode ser declarado com um ou mais parâmetros de tipo que servem como um espaço reservado para o tipo real (o tipo concreto). O código do cliente fornece o tipo concreto quando cria uma instância do tipo. Esses tipos são chamados de tipos genéricos. Por exemplo, o tipo System.Collections.Generic.List<T> .NET tem um parâmetro de tipo que, por convenção, recebe o nome T
. Ao criar uma instância do tipo, especifique o tipo dos objetos que a lista contém, por exemplo: string
List<string> stringList = new List<string>();
stringList.Add("String example");
// compile time error adding a type other than a string:
stringList.Add(4);
O uso do parâmetro de tipo possibilita reutilizar a mesma classe para manter qualquer tipo de elemento, sem precisar converter cada elemento em objeto. Classes de coleção genérica são chamadas de coleções fortemente tipadas porque o compilador conhece o tipo específico dos elementos da coleção e pode gerar um erro no momento da compilação se, por exemplo, você tentar adicionar um inteiro ao stringList
objeto no exemplo anterior. Para obter mais informações, consulte Genéricos.
Tipos implícitos, tipos anônimos e tipos de valor anuláveis
Você pode digitar implicitamente uma variável local (mas não membros de classe) usando a var
palavra-chave. A variável ainda recebe um tipo em tempo de compilação, mas o tipo é fornecido pelo compilador. Para obter mais informações, consulte Variáveis locais de tipo implícito.
Pode ser inconveniente criar um tipo nomeado para conjuntos simples de valores relacionados que você não pretende armazenar ou passar fora dos limites do método. Você pode criar tipos anônimos para essa finalidade. Para obter mais informações, consulte Tipos Anônimos.
Tipos de valor comuns não podem ter um valor de null
. No entanto, você pode criar tipos de valor anuláveis acrescentando um ?
após o tipo. Por exemplo, int?
é um int
tipo que também pode ter o valor null
. Os tipos que permitem valor nulo são instâncias do tipo struct genérico System.Nullable<T>. Tipos de valor anuláveis são especialmente úteis quando você está passando dados de e para bancos de dados nos quais valores numéricos podem ser null
. Para obter mais informações, consulte tipos de valor anuláveis.
Tipo de tempo de compilação e tipo de tempo de execução
Uma variável pode ter diferentes tipos de tempo de compilação e tempo de execução. O tipo de tempo de compilação é o tipo declarado ou inferido da variável no código-fonte. O tipo de tempo de execução é o tipo da instância referenciada por essa variável. Geralmente, esses dois tipos são os mesmos, como no exemplo a seguir:
string message = "This is a string of characters";
Em outros casos, o tipo de tempo de compilação é diferente, conforme mostrado nos dois exemplos a seguir:
object anotherMessage = "This is another string of characters";
IEnumerable<char> someCharacters = "abcdefghijklmnopqrstuvwxyz";
Em ambos os exemplos acima, o tipo de tempo de execução é um string
. O tipo de tempo de compilação está object
na primeira linha e IEnumerable<char>
na segunda.
Se os dois tipos forem diferentes para uma variável, é importante entender quando o tipo de tempo de compilação e o tipo de tempo de execução se aplicam. O tipo de tempo de compilação determina todas as ações executadas pelo compilador. Essas ações do compilador incluem resolução de chamada de método, resolução de sobrecarga e conversões implícitas e explícitas disponíveis. O tipo de tempo de execução determina todas as ações resolvidas em tempo de execução. Essas ações em tempo de execução incluem o despacho de chamadas de método virtual, a avaliação das expressões is
e switch
, e outras APIs de teste de tipo. Para entender melhor como seu código interage com tipos, reconheça qual ação se aplica a qual tipo.
Seções relacionadas
Para obter mais informações, consulte os seguintes artigos:
Especificação da linguagem C#
Para obter mais informações, consulte a Especificação da Linguagem C# . A especificação de idioma é a fonte definitiva para a sintaxe e o uso de C#.