Tipos e membros

Por ser uma linguagem orientada a objeto, o C# oferece suporte aos conceitos de encapsulamento, herança e polimorfismo. Uma classe pode herdar diretamente de uma classe pai e pode implementar qualquer número de interfaces. Métodos que substituem métodos virtuais em uma classe pai exigem a palavra-chave override como uma forma de evitar uma redefinição acidental. Em C#, um struct é como uma classe leve; é um tipo de pilha alocada que pode implementar interfaces, mas não dá suporte à herança. O C# fornece record class e record struct tipos que são tipos cuja finalidade é armazenar principalmente os valores de dados.

Classes e objetos

As classes são as mais fundamentais para os tipos do C#. Uma classe é uma estrutura de dados que combina ações (métodos e outros membros da função) e estado (campos) em uma única unidade. Uma classe fornece uma definição para instâncias da classe, também conhecida como objetos. As classes dão suporte à herança e polimorfismo, mecanismos nos quais classes derivadas podem estender e especializar classes base.

Novas classes são criadas usando declarações de classe. Uma declaração de classe começa com um cabeçalho. O cabeçalho especifica:

  • Os atributos e modificadores da classe
  • O nome da classe
  • A classe base (ao herdar de uma classe base)
  • As interfaces implementadas pela classe.

O cabeçalho é seguido pelo corpo da classe, que consiste em uma lista de declarações de membro escrita entre os delimitadores { e }.

O código a seguir mostra uma declaração de uma classe simples chamada Point :

public class Point
{
    public int X { get; }
    public int Y { get; }
    
    public Point(int x, int y) => (X, Y) = (x, y);
}

Instâncias de classes são criadas usando o operador new, que aloca memória para uma nova instância, chama um construtor para inicializar a instância e retorna uma referência à instância. As instruções a seguir criam dois Point objetos e armazenam referências a esses objetos em duas variáveis:

var p1 = new Point(0, 0);
var p2 = new Point(10, 20);

A memória ocupada por um objeto é recuperada automaticamente quando o objeto não está mais acessível. Não é necessário ou é possível desalocar explicitamente objetos em C#.

Parâmetros de tipo

Classes genéricas definem parâmetros de tipo. Parâmetros de tipo são uma lista de nomes de parâmetro de tipo entre colchetes angulares. Os parâmetros de tipo seguem o nome da classe. Em seguida, os parâmetros de tipo podem ser usados no corpo das declarações de classe para definir os membros da classe. No exemplo a seguir, os parâmetros de tipo de Pair são TFirst e TSecond:

public class Pair<TFirst, TSecond>
{
    public TFirst First { get; }
    public TSecond Second { get; }
    
    public Pair(TFirst first, TSecond second) => 
        (First, Second) = (first, second);
}

Um tipo de classe que é declarado para pegar parâmetros de tipo é chamado de tipo de classe genérica. Os tipos struct, interface e delegate também podem ser genéricos. Quando a classe genérica é usada, os argumentos de tipo devem ser fornecidos para cada um dos parâmetros de tipo:

var pair = new Pair<int, string>(1, "two");
int i = pair.First;     //TFirst int
string s = pair.Second; //TSecond string

Um tipo genérico com argumentos de tipo fornecidos, como Pair<int,string> acima, é chamado de tipo construído.

Classes base

Uma declaração de classe pode especificar uma classe base. Siga o nome da classe e os parâmetros de tipo com dois-pontos e o nome da classe base. Omitir uma especificação de classe base é o mesmo que derivar do object de tipo. No exemplo a seguir, a classe base de Point3D é Point . No primeiro exemplo, a classe base de Point é object :

public class Point3D : Point
{
    public int Z { get; set; }
    
    public Point3D(int x, int y, int z) : base(x, y)
    {
        Z = z;
    }
}

Uma classe herda os membros de sua classe base. Herança significa que uma classe contém implicitamente quase todos os membros de sua classe base. Uma classe não herda a instância e construtores estáticos e o finalizador. Uma classe derivada pode adicionar novos membros a esses membros que ele herda, mas não pode remover a definição de um membro herdado. No exemplo anterior, Point3D herda os X Membros e Y de Point , e cada Point3D instância contém três propriedades, X , Y e Z .

Existe uma conversão implícita de um tipo de classe para qualquer um de seus tipos de classe base. Uma variável de um tipo de classe pode referenciar uma instância dessa classe ou uma instância de qualquer classe derivada. Por exemplo, dadas as declarações de classe anteriores, uma variável do tipo Point podem referenciar um Point ou um Point3D:

Point a = new(10, 20);
Point b = new Point3D(10, 20, 30);

Estruturas

Classes definem tipos que dão suporte a herança e polimorfismo. Eles permitem que você crie comportamentos sofisticados com base em hierarquias de classes derivadas. Por outro lado, os tipos de struct são tipos mais simples cujo objetivo principal é armazenar valores de dados. Structs não podem declarar um tipo base; Eles derivam implicitamente de System.ValueType . Você não pode derivar outros struct tipos de um struct tipo. Eles são lacrados implicitamente.

public struct Point
{
    public double X { get; }
    public double Y { get; }
    
    public Point(double x, double y) => (X, Y) = (x, y);
}

Interfaces

Uma * interface _ define um contrato que pode ser implementado por classes e estruturas. Você define um _interface * para declarar recursos que são compartilhados entre tipos distintos. Por exemplo, a System.Collections.Generic.IEnumerable<T> interface define uma maneira consistente de percorrer todos os itens de uma coleção, como uma matriz. Uma interface pode conter métodos, propriedades, eventos e indexadores. Uma interface normalmente não fornece implementações dos membros que ele define — ela simplesmente especifica os membros que devem ser fornecidos por classes ou estruturas que implementam a interface.

As interfaces podem empregar a herança múltipla. No exemplo a seguir, a interface IComboBox herda de ITextBox e IListBox.

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

interface IComboBox : ITextBox, IListBox { }

Classes e structs podem implementar várias interfaces. No exemplo a seguir, a classe EditBox implementa IControl e IDataBound.

interface IDataBound
{
    void Bind(Binder b);
}

public class EditBox : IControl, IDataBound
{
    public void Paint() { }
    public void Bind(Binder b) { }
}

Quando uma classe ou struct implementa uma interface específica, as instâncias dessa classe ou struct podem ser convertidas implicitamente para esse tipo de interface. Por exemplo

EditBox editBox = new();
IControl control = editBox;
IDataBound dataBound = editBox;

Enumerações

Um tipo de Enumeração define um conjunto de valores constantes. A seguir, as seguintes enum constantes declarações que definem um ou diferente de raiz:

public enum SomeRootVegetable
{
    HorseRadish,
    Radish,
    Turnip
}

Você também pode definir um enum para ser usado em combinação como sinalizadores. A declaração a seguir declara um conjunto de sinalizadores para as quatro estações. Qualquer combinação das estações pode ser aplicada, incluindo um All valor que inclui todas as estações:

[Flags]
public enum Seasons
{
    None = 0,
    Summer = 1,
    Autumn = 2,
    Winter = 4,
    Spring = 8,
    All = Summer | Autumn | Winter | Spring
}

O exemplo a seguir mostra declarações de ambas as enumerações anteriores:

var turnip = SomeRootVegetable.Turnip;

var spring = Seasons.Spring;
var startingOnEquinox = Seasons.Spring | Seasons.Autumn;
var theYear = Seasons.All;

Tipos anuláveis

Variáveis de qualquer tipo podem ser declaradas como não anulável _ ou _anuláveis_. Uma variável anulável pode conter um null valor adicional, indicando que não há valor. Tipos de valores anuláveis (structs ou enums) são representados por System.Nullable<T> . Os tipos de referência não anuláveis e anuláveis são representados pelo tipo de referência subjacente. A distinção é representada por metadados lidos pelo compilador e por algumas bibliotecas. O compilador fornece avisos quando referências anuláveis são desreferenciadas sem primeiro verificar seu valor null . O compilador também fornece avisos quando referências não anuláveis são atribuídas a um valor que pode ser null . O exemplo a seguir declara um _int anulável*_, inicializando-o para null . Em seguida, ele define o valor como 5 . Ele demonstra o mesmo conceito com uma * cadeia de caracteres anulável *. Para obter mais informações, consulte tipos de valor anulável e tipos de referência anuláveis.

int? optionalInt = default; 
optionalInt = 5;
string? optionalText = default;
optionalText = "Hello World.";

Tuplas

O C# oferece suporte a tuplas, que fornece sintaxe concisa para agrupar vários elementos de dados em uma estrutura de dados leve. Você cria uma instância de uma tupla declarando os tipos e os nomes dos membros entre ( e ) , conforme mostrado no exemplo a seguir:

(double Sum, int Count) t2 = (4.5, 3);
Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
//Output:
//Sum of 3 elements is 4.5.

As tuplas fornecem uma alternativa para a estrutura de dados com vários membros, sem usar os blocos de construção descritos no próximo artigo.