Um tour pela linguagem C#

O C# (pronuncia-se "Veja nítido") é uma linguagem de programação moderna, orientada a objeto e de tipo seguro. O C# permite que os desenvolvedores criem muitos tipos de aplicativos seguros e robustos que são executados no .NET. O C# tem suas raízes na família de linguagens C e os programadores em C, C++, Java e JavaScript a reconhecerão imediatamente. Este tour fornece uma visão geral dos principais componentes do idioma no C# 8 e versões anteriores. Se você quiser explorar a linguagem por meio de exemplos interativos, experimente a introdução aos tutoriais do C#.

O C# é uma linguagem de programação _ orientada a objeto, Component-oriented. O c# fornece construções de linguagem para dar suporte direto a esses conceitos, tornando o C# uma linguagem natural para criar e usar componentes de software. Desde sua origem, o C# adicionou recursos para dar suporte a novas cargas de trabalho e práticas de design de software emergentes. Em seu núcleo, o C# é uma linguagem **orientada a objeto. Você define os tipos e seu comportamento.

Vários recursos do C# ajudam a criar aplicativos robustos e duráveis. A * coleta de lixo _ recupera automaticamente a memória ocupada por objetos não utilizados inacessíveis. Os tipos anuláveis protegem contra variáveis que não se referem a objetos alocados. A manipulação de exceção fornece uma abordagem estruturada e extensível para detecção e recuperação de erros. As expressões lambda dão suporte a técnicas de programação funcional. A sintaxe de linguagem de consulta integrada (LINQ) cria um padrão comum para trabalhar com dados de qualquer fonte. O suporte a idiomas para operações assíncronas fornece a sintaxe para a criação de sistemas distribuídos. O C# tem um sistema * de tipo _ unificado. Todos os tipos do C#, incluindo tipos primitivos, como int e double, herdam de um único tipo de object raiz. Todos os tipos compartilham um conjunto de operações comuns. Os valores de qualquer tipo podem ser armazenados, transportados e operados de maneira consistente. Além disso, o C# dá suporte a tipos de referência definidos pelo usuário e tipos de valor. O C# permite a alocação dinâmica de objetos e o armazenamento em linha de estruturas leves. O C# oferece suporte a tipos e métodos genéricos, que fornecem aumento na segurança e no desempenho do tipo. O C# fornece iteradores, que habilitam implementadores de classes de coleção para definir comportamentos personalizados para o código do cliente.

O C# enfatiza o controle de versão para garantir que programas e bibliotecas possam evoluir ao longo do tempo de maneira compatível. Aspectos do design do C# que foram influenciados diretamente pelas considerações de controle de versão incluem os virtual override modificadores and separados, as regras para resolução de sobrecarga de método e suporte para declarações de membro de interface explícitas.

Arquitetura do .NET

Os programas em C# são executados no .NET, um sistema de execução virtual chamado Common Language Runtime (CLR) e um conjunto de bibliotecas de classes. O CLR é a implementação da Microsoft da CLI (Common Language Infrastructure), um padrão internacional. A CLI é a base para a criação de ambientes de execução e desenvolvimento nos quais as linguagens e bibliotecas funcionam em conjunto diretamente.

O código-fonte escrito em C# é compilado em uma Il (linguagem intermediária) que está de acordo com a especificação da CLI. O código de IL e os recursos, como bitmaps e cadeias de caracteres, são armazenados em um assembly, normalmente com uma extensão de .dll. Um assembly contém um manifesto que fornece informações sobre os tipos, a versão e a cultura do assembly.

Quando o programa C# é executado, o assembly é carregado no CLR. O CLR executa a compilação JIT (just-in-time) para converter o código IL em instruções de máquina nativa. O CLR fornece outros serviços relacionados à coleta de lixo, manipulação de exceções e gerenciamento de recursos automáticos. O código executado pelo CLR, às vezes, é chamado de "código gerenciado". "Código não gerenciado" é compilado em linguagem de máquina nativa direcionada a uma plataforma específica.

A interoperabilidade de linguagem é um recurso fundamental do .NET. O código de IL produzido pelo compilador C# está de acordo com a CTS (especificação de tipo comum). O código IL gerado do C# pode interagir com o código gerado nas versões do .NET do F #, Visual Basic, C++. Há mais de 20 outros idiomas em conformidade com CTS. Um único assembly pode conter vários módulos escritos em diferentes linguagens .NET. Os tipos podem referenciar um ao outro como se fossem escritos no mesmo idioma.

Além dos serviços de tempo de execução, o .NET também inclui bibliotecas extensivas. Essas bibliotecas dão suporte a várias cargas de trabalho diferentes. Eles são organizados em namespaces que fornecem uma ampla variedade de funcionalidades úteis. as bibliotecas incluem tudo, desde entrada e saída de arquivo até a manipulação de cadeia de caracteres à análise XML, até estruturas de aplicativo web para Windows Forms controles. O aplicativo C# típico usa a biblioteca de classes .NET extensivamente para lidar com tarefas comuns de "encanamento".

Para obter mais informações sobre o .NET, consulte visão geral do .net.

Hello world

O programa "Hello, World" é usado tradicionalmente para introduzir uma linguagem de programação. Este é para C#:

using System;

class Hello
{
    static void Main()
    {
        Console.WriteLine("Hello, World");
    }
}

O programa "Hello, World" começa com uma diretiva using que faz referência ao namespace System. Namespaces fornecem um meio hierárquico de organizar bibliotecas e programas em C#. Os namespaces contêm tipos e outros namespaces — por exemplo, o namespace System contém uma quantidade de tipos, como a classe Console referenciada no programa e diversos outros namespaces, como IO e Collections. A diretiva using que faz referência a um determinado namespace permite o uso não qualificado dos tipos que são membros desse namespace. Devido à diretiva using, o programa pode usar Console.WriteLine como um atalho para System.Console.WriteLine.

A classe Hello declarada pelo programa "Hello, World" tem um único membro, o método chamado Main. O Main método é declarado com o static modificador. Embora os métodos de instância possam fazer referência a uma determinada instância de objeto delimitador usando a palavra-chave this, métodos estáticos operam sem referência a um objeto específico. Por convenção, um método estático chamado Main serve como o ponto de entrada de um programa em C#.

A saída do programa é produzida pelo método WriteLine da classe Console no namespace System. Essa classe é fornecida pelas bibliotecas de classe padrão, que, por padrão, são referenciadas automaticamente pelo compilador.

Tipos e variáveis

Um tipo define a estrutura e o comportamento de quaisquer dados em C#. A declaração de um tipo pode incluir seus membros, o tipo base, as interfaces que ele implementa e as operações permitidas para esse tipo. Uma variável é um rótulo que se refere a uma instância de um tipo específico.

Há dois tipos em C#: tipos de referência e tipos de valor. Variáveis de tipos de valor contêm diretamente seus dados. Variáveis de tipos de referência armazenam referências a seus dados, o último é conhecido como objetos. Com os tipos de referência, é possível que duas variáveis referenciem o mesmo objeto e possíveis operações em uma variável afetem o objeto referenciado pela outra variável. Com os tipos de valor, as variáveis têm sua própria cópia dos dados, e não é possível que as operações em um afetem a outra (exceto ref para out variáveis de parâmetro e).

Um identificador é um nome de variável. Um identificador é uma sequência de caracteres Unicode sem qualquer espaço em branco. Um identificador pode ser uma palavra reservada em C#, se for prefixada por @ . Usar uma palavra reservada como um identificador pode ser útil ao interagir com outras linguagens.

Os tipos de valor do C# são divididos em tipos simples, tipos de enumeração, tipos de struct, tipos de valor anulável e tipos de valor de tupla. Os tipos de referência do C# são divididos em tipos de classe, tipos de interface, tipos de matriz e tipos delegados.

A seguinte estrutura de tópicos fornece uma visão geral do sistema de tipos do C#.

Os programas em C# usam declarações de tipos para criar novos tipos. Uma declaração de tipo especifica o nome e os membros do novo tipo. Seis categorias de tipos do C# são definíveis pelo usuário: tipos de classe, tipos de struct, tipos de interface, tipos de enumeração, tipos de delegado e tipos de valor de tupla. Você também pode declarar record tipos, record struct ou record class . Os tipos de registro têm Membros sintetizados pelo compilador. Você usa registros principalmente para armazenar valores, com comportamento mínimo associado.

  • Um tipo class define uma estrutura de dados que contém membros de dados (campos) e membros de função (métodos, propriedades e outros). Os tipos de classe dão suporte à herança única e ao polimorfismo, mecanismos nos quais as classes derivadas podem estender e especializar as classes base.
  • Um tipo struct é semelhante a um tipo de classe que representa uma estrutura com membros de dados e membros da função. No entanto, ao contrário das classes, as structs são tipos de valor e normalmente não exigem alocação de heap. Tipos de struct não dão suporte à herança especificada pelo usuário e todos os tipos de struct herdam implicitamente do tipo object .
  • Um interface tipo define um contrato como um conjunto nomeado de membros públicos. Um class ou struct que implementa um interface deve fornecer implementações dos membros da interface. Um interface pode herdar de várias interfaces base e um class ou struct pode implementar várias interfaces.
  • Um tipo delegate representa referências aos métodos com uma lista de parâmetros e tipo de retorno específicos. Delegados possibilitam o tratamento de métodos como entidades que podem ser atribuídos a variáveis e passadas como parâmetros. Os delegados são análogos aos tipos de função fornecidos pelas linguagens funcionais. Eles também são semelhantes ao conceito de ponteiros de função encontrados em algumas outras linguagens. Diferentemente de ponteiros de função, os delegados são orientados a objeto e são de tipo seguro.

Os class struct tipos,, e interface delegate oferecem suporte a genéricos, no qual eles podem ser parametrizados com outros tipos.

O C# dá suporte a matrizes unidimensionais e multidimensionais de qualquer tipo. Ao contrário dos tipos listados acima, os tipos de matriz não precisam ser declarados antes que possam ser usados. Em vez disso, os tipos de matriz são construídos seguindo um nome de tipo entre colchetes. Por exemplo, int[] é uma matriz unidimensional de int , int[,] é uma matriz bidimensional de int e int[][] é uma matriz unidimensional de matrizes unidimensionais, ou uma matriz "denteada", de int .

Tipos anuláveis não exigem uma definição separada. Para cada tipo não anulável T , há um tipo anulável correspondente T? , que pode conter um valor adicional, null . Por exemplo, int? é um tipo que pode conter qualquer número inteiro de 32 bits ou o valor null e string? é um tipo que pode conter qualquer string ou o valor null .

O sistema de tipos do C# é unificado, de modo que um valor de qualquer tipo pode ser tratado como um object . Cada tipo no C#, direta ou indiretamente, deriva do tipo de classe object, e object é a classe base definitiva de todos os tipos. Os valores de tipos de referência são tratados como objetos simplesmente exibindo os valores como tipo object. Os valores de tipos de valor são tratados como objetos, executando conversão boxing e operações de conversão unboxing. No exemplo a seguir, um valor int é convertido em object e volta novamente ao int.

int i = 123;
object o = i;    // Boxing
int j = (int)o;  // Unboxing

Quando um valor de um tipo de valor é atribuído a uma object referência, uma "caixa" é alocada para conter o valor. Essa caixa é uma instância de um tipo de referência, e o valor é copiado para essa caixa. Por outro lado, quando uma object referência é convertida em um tipo de valor, é feita uma verificação de que a referência object é uma caixa do tipo de valor correto. Se a verificação for realizada com sucesso, o valor na caixa será copiado para o tipo de valor.

O sistema de tipos unificados do C# significa efetivamente que os tipos de valor são tratados como object referências "sob demanda". Devido à unificação, as bibliotecas de uso geral que usam object o tipo podem ser usadas com todos os tipos que derivam de object , incluindo tipos de referência e tipos de valor.

Existem vários tipos de variáveis no C#, incluindo campos, elementos de matriz, variáveis locais e parâmetros. As variáveis representam locais de armazenamento. Cada variável tem um tipo que determina quais valores podem ser armazenados na variável, como mostrado abaixo.

  • Tipo de valor não nulo
    • Um valor de tipo exato
  • Tipos de valor anulável
    • Um valor null ou um valor do tipo exato
  • objeto
    • Uma referência null, uma referência a um objeto de qualquer tipo de referência ou uma referência a um valor de qualquer tipo de valor demarcado
  • Tipo de classe
    • Uma referência null, uma referência a uma instância desse tipo de classe ou uma referência a uma instância de uma classe derivada desse tipo de classe
  • Tipo de interface
    • Uma referência null, uma referência a uma instância de um tipo de classe que implementa esse tipo de interface ou uma referência a um valor demarcado de um tipo de valor que implementa esse tipo de interface
  • Tipo de matriz
    • Uma referência null, uma referência a uma instância desse tipo de matriz ou uma referência a uma instância de um tipo de matriz compatível
  • Tipo delegado
    • Uma referência null ou uma referência a uma instância de um tipo de delegado compatível

Estrutura do programa

Os principais conceitos organizacionais do C# são os * programas _, namespaces, tipos, Membrose assemblies. Os programas declaram tipos que contêm membros e podem ser organizados em namespaces. Classes, estruturas e interfaces são exemplos de tipos. Campos, métodos, propriedades e eventos são exemplos de membros. Quando programas C# são compilados, eles são fisicamente empacotados em assemblies. Os assemblies normalmente têm a extensão de arquivo .exe ou .dll , dependendo se eles implementam aplicativos ou _ bibliotecas *, respectivamente.

Como um pequeno exemplo, considere um assembly que contém o código a seguir:

using System;

namespace Acme.Collections
{
    public class Stack<T>
    {
        Entry _top;
        
        public void Push(T data)
        {
            _top = new Entry(_top, data);
        }

        public T Pop()
        {
            if (_top == null)
            {
                throw new InvalidOperationException();
            }
            T result = _top.Data;
            _top = _top.Next;
            
            return result;
        }

        class Entry
        {
            public Entry Next { get; set; }
            public T Data { get; set; }
            
            public Entry(Entry next, T data)
            {
                Next = next;
                Data = data;
            }
        }
    }
}

O nome totalmente qualificado dessa classe é Acme.Collections.Stack. A classe contém vários membros: um campo chamado top, dois métodos chamados Push e Pop e uma classe aninhada chamada Entry. A classe Entry ainda contém três membros: um campo chamado next, um campo chamado datae um construtor. O Stack é uma classe genérica . Ele tem um parâmetro de tipo, T que é substituído por um tipo concreto quando usado.

Uma pilha é uma coleção de "primeiro a entrar no final" (filo). Novos elementos são adicionados à parte superior da pilha. Quando um elemento é removido, ele é removido da parte superior da pilha. O exemplo anterior declara o Stack tipo que define o armazenamento e o comportamento de uma pilha. Você pode declarar uma variável que se refere a uma instância do Stack tipo para usar essa funcionalidade.

Os assemblies contêm código executável na forma de instruções de IL (Linguagem Intermediária) e informações simbólicas na forma de metadados. Antes de ser executado, o compilador JIT (just-in-time) do .NET Common Language Runtime converte o código IL em um assembly para o código específico do processador.

Como um assembly é uma unidade de funcionalidade autodescreveda que contém o código e os metadados, não há necessidade de #include diretivas e arquivos de cabeçalho em C#. Os tipos públicos e os membros contidos em um assembly específico são disponibilizados em um programa C# simplesmente fazendo referência a esse assembly ao compilar o programa. Por exemplo, esse programa usa a classe Acme.Collections.Stack do assembly acme.dll:

using System;
using Acme.Collections;

class Example
{
    public static void Main()
    {
        var s = new Stack<int>();
        s.Push(1); // stack contains 1
        s.Push(10); // stack contains 1, 10
        s.Push(100); // stack contains 1, 10, 100
        Console.WriteLine(s.Pop()); // stack contains 1, 10
        Console.WriteLine(s.Pop()); // stack contains 1
        Console.WriteLine(s.Pop()); // stack is empty
    }
}

Para compilar esse programa, você precisa fazer referência ao assembly que contém a classe Stack definida no exemplo anterior.

Os programas em C# podem ser armazenados em vários arquivos de origem. Quando um programa em C# é compilado, todos os arquivos de origem são processados em conjunto e os arquivos de origem podem fazer referência livremente entre si. Conceitualmente, é como se todos os arquivos de origem fossem concatenados em um arquivo grande antes de serem processados. Declarações de encaminhamento nunca são necessárias em C# porque, com poucas exceções, a ordem de declaração é insignificante. O C# não limita um arquivo de origem a declarar apenas um tipo público nem exige o nome do arquivo de origem para corresponder a um tipo declarado no arquivo de origem.

Outros artigos neste tour explicam esses blocos organizacionais.