Partilhar via


7 Conceitos básicos

7.1 Inicialização do aplicativo

Um programa pode ser compilado como uma biblioteca de classes para ser usado como parte de outros aplicativos ou como um aplicativo que pode ser iniciado diretamente. O mecanismo para determinar este modo de compilação é definido pela implementação e externo a esta especificação.

Um programa compilado como uma aplicação deve conter pelo menos um método que possa ser qualificado como ponto de entrada, satisfazendo os seguintes requisitos:

  • Deve ter o nome Main.
  • Deve ser static.
  • Não deve ser genérico.
  • Deve ser declarada num tipo não genérico. Se o tipo que declara o método for um tipo aninhado, nenhum dos tipos que o encerram pode ser genérico.
  • Ele pode ter o async modificador desde que o tipo de retorno do método seja System.Threading.Tasks.Task ou System.Threading.Tasks.Task<int>.
  • O tipo de retorno deve ser void, int, System.Threading.Tasks.Task, ou System.Threading.Tasks.Task<int>.
  • Não deve ser um método parcial (§15.6.9) sem uma implementação.
  • A lista de parâmetros deve estar vazia ou ter um único parâmetro de valor do tipo string[].

Nota: Os métodos com o async modificador devem ter exatamente um dos dois tipos de retorno especificados acima para se qualificar como um ponto de entrada. Um async void método, ou um async método que retorna um tipo aguardado diferente, como ValueTask ou ValueTask<int> não se qualifica como um ponto de entrada. Nota final

Se mais de um método qualificado como ponto de entrada for declarado dentro de um programa, um mecanismo externo pode ser usado para especificar qual método é considerado o ponto de entrada real para o aplicativo. Se for encontrado um método de qualificação com um tipo de retorno de ou int qualquer método de qualificação com um tipo de retorno de ou void não é considerado um método de ponto de System.Threading.Tasks.TaskSystem.Threading.Tasks.Task<int> entrada. É um erro em tempo de compilação para um programa ser compilado como um aplicativo sem exatamente um ponto de entrada. Um programa compilado como uma biblioteca de classes pode conter métodos que se qualificariam como pontos de entrada do aplicativo, mas a biblioteca resultante não tem nenhum ponto de entrada.

Normalmente, a acessibilidade declarada (§7.5.2) de um método é determinada pelos modificadores de acesso (§15.3.6) especificados na sua declaração e, da mesma forma, a acessibilidade declarada de um tipo é determinada pelos modificadores de acesso especificados na sua declaração. Para que um determinado método de um determinado tipo seja exigível, tanto o tipo como o membro devem ser acessíveis. No entanto, o ponto de entrada do aplicativo é um caso especial. Especificamente, o ambiente de execução pode acessar o ponto de entrada do aplicativo independentemente de sua acessibilidade declarada e independentemente da acessibilidade declarada de suas declarações de tipo anexadas.

Quando o método de ponto de entrada tem um tipo de retorno de System.Threading.Tasks.Task ou System.Threading.Tasks.Task<int>, o compilador deverá sintetizar um método de ponto de entrada síncrono que chama o método Main correspondente. O método sintetizado tem parâmetros e tipos de retorno com base no Main método:

  • A lista de parâmetros do método sintetizado é a mesma que a lista de parâmetros do Main método
  • Se o tipo de retorno do Main método for System.Threading.Tasks.Task, o tipo de retorno do método sintetizado for void
  • Se o tipo de retorno do Main método for System.Threading.Tasks.Task<int>, o tipo de retorno do método sintetizado for int

A execução do método sintetizado procede da seguinte forma:

  • O método sintetizado chama o Main método, passando seu string[] valor de parâmetro como um argumento se o Main método tiver tal parâmetro.
  • Se o Main método lança uma exceção, a exceção é propagada pelo método sintetizado.
  • Caso contrário, o ponto de entrada sintetizado aguarda a conclusão da tarefa retornada, chamando GetAwaiter().GetResult() a tarefa, usando o método de instância sem parâmetros ou o método de extensão descrito por §C.3. Se a tarefa falhar, GetResult() lançará uma exceção, e essa exceção será propagada pelo método sintetizado.
  • Para um Main método com um tipo de retorno de System.Threading.Tasks.Task<int>, se a tarefa for concluída com êxito, o int valor retornado por GetResult() será retornado do método sintetizado.

O ponto de entrada efetivo de um aplicativo é o ponto de entrada declarado dentro do programa, ou o método sintetizado, se necessário, conforme descrito acima. O tipo de retorno do ponto de entrada efetivo é, portanto, sempre void ou int.

Quando um aplicativo é executado, um novo domínio de aplicativo é criado. Várias instanciações diferentes de um aplicativo podem existir na mesma máquina ao mesmo tempo, e cada uma tem seu próprio domínio de aplicativo. Um domínio de aplicativo permite o isolamento do aplicativo agindo como um contêiner para o estado do aplicativo. Um domínio de aplicativo atua como um contêiner e limite para os tipos definidos no aplicativo e as bibliotecas de classes que ele usa. Os tipos carregados em um domínio de aplicativo são distintos dos mesmos tipos carregados em outro domínio de aplicativo, e as instâncias de objetos não são compartilhadas diretamente entre domínios de aplicativo. Por exemplo, cada domínio de aplicativo tem sua própria cópia de variáveis estáticas para esses tipos, e um construtor estático para um tipo é executado no máximo uma vez por domínio de aplicativo. As implementações são livres para fornecer políticas ou mecanismos definidos pela implementação para a criação e destruição de domínios de aplicativos.

A inicialização do aplicativo ocorre quando o ambiente de execução chama o ponto de entrada efetivo do aplicativo. Se o ponto de entrada efetivo declarar um parâmetro, durante a inicialização do aplicativo, a implementação deve garantir que o valor inicial desse parâmetro seja uma referência não nula a uma matriz de cadeia de caracteres. Essa matriz deve consistir em referências não nulas a cadeias de caracteres, chamadas parâmetros de aplicativo, que recebem valores definidos pela implementação pelo ambiente host antes da inicialização do aplicativo. A intenção é fornecer ao aplicativo informações determinadas antes da inicialização do aplicativo de outro lugar no ambiente hospedado.

Nota: Em sistemas que suportam uma linha de comando, os parâmetros do aplicativo correspondem ao que geralmente são conhecidos como argumentos de linha de comando. Nota final

Se o tipo de retorno do ponto de entrada efetivo for int, o valor de retorno da invocação do método pelo ambiente de execução será usado no encerramento do aplicativo (§7.2).

Além das situações listadas acima, os métodos de ponto de entrada se comportam como aqueles que não são pontos de entrada em todos os aspetos. Em particular, se o ponto de entrada for invocado em qualquer outro ponto durante o tempo de vida do aplicativo, como por invocação de método regular, não haverá manipulação especial do método: se houver um parâmetro, ele pode ter um valor inicial de null, ou um não-valornull referente a uma matriz que contém referências nulas. Da mesma forma, o valor de retorno do ponto de entrada não tem significado especial além da invocação do ambiente de execução.

7.2 Cessação da candidatura

A terminação do aplicativo retorna o controle para o ambiente de execução.

Se o tipo de retorno do método de ponto de entrada efetivo do aplicativo for int e a execução for concluída sem resultar em uma exceção, o valor do retornado servirá como o código de status de término do int aplicativo. O objetivo deste código é permitir a comunicação de sucesso ou falha para o ambiente de execução. Se o tipo de retorno do método de ponto de entrada efetivo for void e a execução for concluída sem resultar em uma exceção, o código de status de término será 0.

Se o método de ponto de entrada efetivo terminar devido a uma exceção (§21.4), o código de saída será definido pela implementação. Além disso, a implementação pode fornecer APIs alternativas para especificar o código de saída.

Se os finalizadores (§15.13) são ou não executados como parte do encerramento do aplicativo é definido pela implementação.

Nota: A implementação do .NET Framework faz todos os esforços razoáveis para chamar finalizadores (§15.13) para todos os seus objetos que ainda não foram coletados lixo, a menos que essa limpeza tenha sido suprimida (por uma chamada para o método GC.SuppressFinalizede biblioteca, por exemplo). Nota final

7.3 Declarações

As declarações em um programa C# definem os elementos constituintes do programa. Os programas C# são organizados usando namespaces. Eles são introduzidos usando declarações de namespace (§14), que podem conter declarações de tipo e declarações de namespace aninhadas. As declarações de tipo (§14.7) são usadas para definir classes (§15), structs (§16), interfaces (§18), enums (§19) e delegados (§20). Os tipos de membros permitidos numa declaração de tipo dependem da forma da declaração de tipo. Por exemplo, as declarações de classe podem conter declarações para constantes (§15.4), campos (§15.5), métodos (§15.6), propriedades (§15.7), eventos (§15.8), indexadores (§15.9), operadores (§15.10), construtores de instância (§15.11), construtores estáticos (§15.12), finalizadores (§15.13) e tipos aninhados (§15.3.9).

Uma declaração define um nome no espaço de declaração ao qual a declaração pertence. É um erro em tempo de compilação ter duas ou mais declarações que introduzem membros com o mesmo nome em um espaço de declaração, exceto nos seguintes casos:

  • Duas ou mais declarações de namespace com o mesmo nome são permitidas no mesmo espaço de declaração. Essas declarações de namespace são agregadas para formar um único namespace lógico e compartilhar um único espaço de declaração.
  • Declarações em programas separados, mas no mesmo espaço de declaração de namespace, podem compartilhar o mesmo nome.

    Nota: No entanto, estas declarações podem introduzir ambiguidades se incluídas no mesmo pedido. Nota final

  • Dois ou mais métodos com o mesmo nome, mas assinaturas distintas, são permitidos no mesmo espaço de declaração (§7.6).
  • São permitidas duas ou mais declarações de tipo com o mesmo nome, mas números distintos de parâmetros de tipo no mesmo espaço de declaração (§7.8.2).
  • Duas ou mais declarações de tipo com o modificador parcial no mesmo espaço de declaração podem compartilhar o mesmo nome, o mesmo número de parâmetros de tipo e a mesma classificação (classe, struct ou interface). Neste caso, as declarações de tipo contribuem para um único tipo e são elas próprias agregadas para formar um único espaço de declaração (§15.2.7).
  • Uma declaração de namespace e uma declaração de tipo no mesmo espaço de declaração podem compartilhar o mesmo nome, desde que a declaração de tipo tenha pelo menos um parâmetro de tipo (§7.8.2).

Existem vários tipos diferentes de espaços de declaração, conforme descrito a seguir.

  • Dentro de todas as unidades de compilação de um programa, namespace_member_declarations sem namespace_declaration de inclusão são membros de um único espaço de declaração combinado chamado espaço de declaração global.
  • Dentro de todas as unidades de compilação de um programa, namespace_member_declarations dentro de namespace_declarations que têm o mesmo nome de namespace totalmente qualificado são membros de um único espaço de declaração combinado.
  • Cada compilation_unit e namespace_body tem um espaço de declaração de alias. Cada extern_alias_directive e using_alias_directive do compilation_unit ou namespace_body contribui com um membro para o espaço de declaração de alias (§14.5.2).
  • Cada declaração de classe, struct ou interface não parcial cria um novo espaço de declaração. Cada declaração parcial de classe, struct ou interface contribui para um espaço de declaração compartilhado por todas as partes correspondentes no mesmo programa (§16.2.4). Os nomes são introduzidos neste espaço de declaração através de class_member_declarations, struct_member_declarations, interface_member_declarations ou type_parameters. Exceto para declarações de construtor de instância sobrecarregadas e declarações de construtor estático, uma classe ou struct não pode conter uma declaração de membro com o mesmo nome que a classe ou struct. Uma classe, struct ou interface permite a declaração de métodos e indexadores sobrecarregados. Além disso, uma classe ou struct permite a declaração de construtores e operadores de instância sobrecarregados. Por exemplo, uma classe, struct ou interface pode conter várias declarações de método com o mesmo nome, desde que essas declarações de método sejam diferentes em sua assinatura (§7.6). Observe que as classes base não contribuem para o espaço de declaração de uma classe e as interfaces base não contribuem para o espaço de declaração de uma interface. Assim, uma classe ou interface derivada tem permissão para declarar um membro com o mesmo nome de um membro herdado. Diz-se que tal membro esconde o membro herdado.
  • Cada declaração de delegado cria um novo espaço de declaração. Os nomes são introduzidos neste espaço de declaração através dos parâmetros (fixed_parameters e parameter_arrays) e type_parameters.
  • Cada declaração de enumeração cria um novo espaço de declaração. Os nomes são introduzidos neste espaço de declaração através de enum_member_declarations.
  • Cada declaração de método, declaração de propriedade, declaração de acessador de propriedade, declaração de indexador, declaração de acessador de indexador, declaração de operador, declaração de construtor de instância, função anônima e função local cria um novo espaço de declaração chamado espaço de declaração de variável local. Os nomes são introduzidos neste espaço de declaração através dos parâmetros (fixed_parameters e parameter_arrays) e type_parameters. O acessador definido para uma propriedade ou um indexador introduz o nome value como um parâmetro. O corpo do membro da função, função anônima ou função local, se houver, é considerado aninhado dentro do espaço de declaração da variável local. Quando um espaço de declaração de variável local e um espaço de declaração de variável local aninhado contêm elementos com o mesmo nome, dentro do escopo do nome local aninhado, o nome local externo é oculto (§7.7.1) pelo nome local aninhado.
  • Espaços adicionais de declaração de variável local podem ocorrer dentro de declarações de membro, funções anônimas e funções locais. Os nomes são introduzidos nesses espaços de declaração através de padrõess, declaration_expressions, declaration_statements e exception_specifiers. Os espaços de declaração de variável local podem estar aninhados, mas é um erro para um espaço de declaração de variável local e um espaço de declaração de variável local aninhado para conter elementos com o mesmo nome. Assim, dentro de um espaço de declaração aninhado, não é possível declarar uma variável local, função local ou constante com o mesmo nome que um parâmetro, parâmetro de tipo, variável local, função local ou constante em um espaço de declaração anexo. É possível que dois espaços de declaração contenham elementos com o mesmo nome, desde que nenhum espaço de declaração contenha o outro. Os espaços de declaração local são criados pelas seguintes construções:
    • Cada variable_initializer em uma declaração de campo e propriedade introduz seu próprio espaço de declaração de variável local, que não é aninhado em nenhum outro espaço de declaração de variável local.
    • O corpo de um membro da função, função anônima ou função local, se houver, cria um espaço de declaração de variável local que é considerado aninhado dentro do espaço de declaração de variável local da função.
    • Cada constructor_initializer cria um espaço de declaração de variável local aninhado na declaração do construtor da instância. O espaço de declaração de variável local para o corpo do construtor é, por sua vez, aninhado dentro desse espaço de declaração de variável local.
    • Cada bloco, switch_block, specific_catch_clause, iteration_statement e using_statement cria um espaço de declaração de variável local aninhado.
    • Cada embedded_statement que não faz parte diretamente de um statement_list cria um espaço de declaração de variável local aninhado.
    • Cada switch_section cria um espaço de declaração de variável local aninhado. No entanto, as variáveis declaradas diretamente dentro do statement_list do switch_section (mas não dentro de um espaço de declaração de variável local aninhado dentro do statement_list) são adicionadas diretamente ao espaço de declaração de variável local do switch_block que o inclui, em vez do espaço do switch_section.
    • A tradução sintática de um query_expression (§12.20.3) pode introduzir uma ou mais expressões lambda. Como funções anônimas, cada uma delas cria um espaço de declaração de variável local, conforme descrito acima.
  • Cada bloco ou switch_block cria um espaço de declaração separado para rótulos. Os nomes são introduzidos neste espaço de declaração através de labeled_statements, e os nomes são referenciados através de goto_statements. O espaço de declaração de rótulo de um bloco inclui todos os blocos aninhados. Assim, dentro de um bloco aninhado não é possível declarar um rótulo com o mesmo nome de um rótulo em um bloco anexo.

Nota: O fato de variáveis declaradas diretamente dentro de um switch_section serem adicionadas ao espaço de declaração de variável local do switch_block em vez do switch_section pode levar a um código surpreendente. No exemplo abaixo, a variável y local está no escopo dentro da seção switch para o caso padrão, apesar da declaração aparecer na seção switch para o caso 0. A variável z local não está no escopo dentro da seção switch para o caso padrão, pois é introduzida no espaço de declaração da variável local para a seção switch na qual a declaração ocorre.

int x = 1;
switch (x)
{
    case 0:
        int y;
        break;
    case var z when z < 10:
        break;
    default:
        y = 10;
        // Valid: y is in scope
        Console.WriteLine(x + y);
        // Invalid: z is not scope
        Console.WriteLine(x + z);
        break;
}

Nota final

A ordem textual em que os nomes são declarados geralmente não tem importância. Em particular, a ordem textual não é significativa para a declaração e o uso de namespaces, constantes, métodos, propriedades, eventos, indexadores, operadores, construtores de instância, finalizadores, construtores estáticos e tipos. A ordem de declaração é significativa das seguintes maneiras:

  • A ordem de declaração para declarações de campo determina a ordem em que seus inicializadores (se houver) são executados (§15.5.6.2, §15.5.6.3).
  • As variáveis locais devem ser definidas antes de serem utilizadas (§7.7).
  • A ordem de declaração para declarações de membro enum (§19.4) é significativa quando constant_expression valores são omitidos.

Exemplo: O espaço de declaração de um namespace é "open ended" e duas declarações de namespace com o mesmo nome totalmente qualificado contribuem para o mesmo espaço de declaração. Por exemplo

namespace Megacorp.Data
{
    class Customer
    {
        ...
    }
}

namespace Megacorp.Data
{
    class Order
    {
        ...
    }
}

As duas declarações de namespace acima contribuem para o mesmo espaço de declaração, neste caso declarando duas classes com os nomes Megacorp.Data.Customer totalmente qualificados e Megacorp.Data.Order. Como as duas declarações contribuem para o mesmo espaço de declaração, isso teria causado um erro em tempo de compilação se cada uma contivesse uma declaração de uma classe com o mesmo nome.

Exemplo final

Nota: Conforme especificado acima, o espaço de declaração de um bloco inclui quaisquer blocos aninhados. Assim, no exemplo a seguir, os F métodos e G resultam em um erro em tempo de compilação porque o nome i é declarado no bloco externo e não pode ser redeclarado no bloco interno. No entanto, os H métodos e I são válidos, uma vez que os dois i's são declarados em blocos separados não aninhados.

class A
{
    void F()
    {
        int i = 0;
        if (true)
        {
            int i = 1;
        }
    }

    void G()
    {
        if (true)
        {
            int i = 0;
        }
        int i = 1;
    }

    void H()
    {
        if (true)
        {
            int i = 0;
        }
        if (true)
        {
            int i = 1;
        }
    }

    void I()
    {
        for (int i = 0; i < 10; i++)
        {
            H();
        }
        for (int i = 0; i < 10; i++)
        {
            H();
        }
    }
}

Nota final

7.4 Membros

7.4.1 Generalidades

Namespaces e tipos têm membros.

Nota: Os membros de uma entidade estão geralmente disponíveis através do uso de um nome qualificado que começa com uma referência à entidade, seguido por um token ".", seguido pelo nome do membro. Nota final

Os membros de um tipo são declarados na declaração de tipo ou herdados da classe base do tipo. Quando um tipo herda de uma classe base, todos os membros da classe base, exceto construtores de instância, finalizadores e construtores estáticos tornam-se membros do tipo derivado. A acessibilidade declarada de um membro de classe base não controla se o membro é herdado — a herança se estende a qualquer membro que não seja um construtor de instância, construtor estático ou finalizador.

Nota: No entanto, um membro herdado pode não estar acessível num tipo derivado, por exemplo, devido à sua acessibilidade declarada (§7.5.2). Nota final

7.4.2 Membros do namespace

Namespaces e tipos que não têm namespace de inclusão são membros do namespace global. Isso corresponde diretamente aos nomes declarados no espaço de declaração global.

Namespaces e tipos declarados dentro de um namespace são membros desse namespace. Isso corresponde diretamente aos nomes declarados no espaço de declaração do namespace.

Os namespaces não têm restrições de acesso. Não é possível declarar namespaces privados, protegidos ou internos, e os nomes de namespace são sempre acessíveis publicamente.

7.4.3 Membros da estrutura

Os membros de um struct são os membros declarados no struct e os membros herdados da classe System.ValueType base direta do struct e da classe objectbase indireta.

Os membros de um tipo simples correspondem diretamente aos membros do tipo struct aliased pelo tipo simples (§8.3.5).

7.4.4 Membros da enumeração

Os membros de uma enumeração são as constantes declaradas na enumeração e os membros herdados da classe System.Enum base direta da enumeração e as classes System.ValueType base indiretas e object.

7.4.5 Membros da turma

Os membros de uma classe são os membros declarados na classe e os membros herdados da classe base (exceto para a classe object que não tem classe base). Os membros herdados da classe base incluem as constantes, campos, métodos, propriedades, eventos, indexadores, operadores e tipos da classe base, mas não os construtores de instância, finalizadores e construtores estáticos da classe base. Os membros da classe base são herdados independentemente da sua acessibilidade.

Uma declaração de classe pode conter declarações de constantes, campos, métodos, propriedades, eventos, indexadores, operadores, construtores de instância, finalizadores, construtores estáticos e tipos.

Os membros de object (§8.2.3) e string (§8.2.5) correspondem diretamente aos membros dos tipos de classe que alias.

7.4.6 Membros da interface

Os membros de uma interface são os membros declarados na interface e em todas as interfaces base da interface.

Nota: Os membros em classe object não são, a rigor, membros de qualquer interface (§18.4). No entanto, os membros em classe object estão disponíveis através da pesquisa de membros em qualquer tipo de interface (§12.5). Nota final

7.4.7 Membros da matriz

Os membros de uma matriz são os membros herdados da classe System.Array.

7.4.8 Membros delegados

Um delegado herda membros da classe System.Delegate. Além disso, contém um método nomeado Invoke com o mesmo tipo de retorno e lista de parâmetros especificados na sua declaração (§20.2). Uma invocação deste método deve comportar-se de forma idêntica a uma invocação delegada (§20.6) na mesma instância delegada.

Uma implementação pode fornecer membros adicionais, seja por herança ou diretamente no próprio delegado.

7.5 Acesso dos membros

7.5.1 Generalidades

As declarações dos membros permitem o controlo sobre o acesso dos membros. A acessibilidade de um membro é estabelecida pela acessibilidade declarada (§7.5.2) do membro combinada com a acessibilidade do tipo imediatamente contido, se houver.

Quando o acesso a um determinado membro é permitido, diz-se que o membro é acessível. Por outro lado, quando o acesso a um determinado membro é proibido, o membro é considerado inacessível. O acesso a um membro é permitido quando a localização textual em que o acesso ocorre está incluída no domínio de acessibilidade (§7.5.3) do membro.

7.5.2 Acessibilidade declarada

A acessibilidade declarada de um membro pode ser uma das seguintes:

  • Public, que é selecionado pela inclusão de um public modificador na declaração de membro. O significado intuitivo de public é "acesso não limitado".
  • Protegido, que é selecionado pela inclusão de um protected modificador na declaração de membro. O significado intuitivo de protected é "acesso limitado à classe que contém ou tipos derivados da classe que contém".
  • Interno, que é selecionado pela inclusão de um internal modificador na declaração de membro. O significado intuitivo de internal é "acesso limitado a esta assembleia".
  • Protegido interno, que é selecionado incluindo um protected e um internal modificador na declaração de membro. O significado intuitivo de protected internal é "acessível dentro desta assembléia, bem como tipos derivados da classe que contém".
  • Proteção privada, que é selecionada incluindo um private e um protected modificador na declaração de membro. O significado intuitivo de private protected é "acessível dentro desta assembléia pela classe que contém e tipos derivados da classe que contém".
  • Private, que é selecionado incluindo um private modificador na declaração de membro. O significado intuitivo de private é "acesso limitado ao tipo que contém".

Dependendo do contexto em que uma declaração de membro ocorre, apenas certos tipos de acessibilidade declarada são permitidos. Além disso, quando uma declaração de membro não inclui nenhum modificador de acesso, o contexto em que a declaração ocorre determina a acessibilidade declarada padrão.

  • Os namespaces implicitamente declararam public acessibilidade. Nenhum modificador de acesso é permitido em declarações de namespace.
  • Os tipos declarados diretamente em unidades de compilação ou namespaces (em oposição a outros tipos) podem ter public ou internal declarado acessibilidade e padrão para internal acessibilidade declarada.
  • Os membros da classe podem ter qualquer um dos tipos permitidos de acessibilidade declarada e padrão para private acessibilidade declarada.

    Nota: Um tipo declarado como membro de uma classe pode ter qualquer um dos tipos permitidos de acessibilidade declarada, enquanto um tipo declarado como membro de um namespace pode ter apenas public ou internal declarada acessibilidade. Nota final

  • Os membros Struct podem ter public, internalou private acessibilidade declarada e padrão para private acessibilidade declarada porque structs são implicitamente selados. Os membros Struct introduzidos em um struct (ou seja, não herdados por esse struct) não podem ter protected, protected internalou private protected acessibilidade declarada.

    Nota: Um tipo declarado como membro de uma struct pode ter public, internalou private acessibilidade declarada, enquanto um tipo declarado como membro de um namespace pode ter apenas public ou internal declarada acessibilidade. Nota final

  • Os membros da interface declararam public implicitamente a acessibilidade. Nenhum modificador de acesso é permitido em declarações de membro da interface.
  • Os membros da enumeração declararam public implicitamente a acessibilidade. Nenhum modificador de acesso é permitido em declarações de membro de enumeração.

7.5.3 Domínios de acessibilidade

O domínio de acessibilidade de um membro consiste nas seções (possivelmente disjuntas) do texto do programa em que o acesso ao membro é permitido. Para fins de definição do domínio de acessibilidade de um membro, um membro é dito de nível superior se não for declarado dentro de um tipo, e um membro é dito ser aninhado se for declarado dentro de outro tipo. Além disso, o texto do programa de um programa é definido como todo o texto contido em todas as unidades de compilação do programa, e o texto do programa de um tipo é definido como todo o texto contido nos type_declarations desse tipo (incluindo, possivelmente, os tipos que estão aninhados dentro do tipo).

O domínio de acessibilidade de um tipo predefinido (como object, intou double) é ilimitado.

O domínio de acessibilidade de um tipo T não vinculado de nível superior (§8.4.4) que é declarado em um programa P é definido da seguinte forma:

  • Se a acessibilidade declarada de é pública, o domínio de T acessibilidade de T é o texto do programa de P e qualquer programa que faz referência P.
  • Se a acessibilidade declarada de é interna, o domínio de T acessibilidade de é o texto do T programa de P.

Nota: A partir dessas definições, segue-se que o domínio de acessibilidade de um tipo não vinculado de nível superior é sempre pelo menos o texto do programa no qual esse tipo é declarado. Nota final

O domínio de acessibilidade para um tipo T<A₁, ..., Aₑ> construído é a interseção do domínio de acessibilidade do tipo T genérico não acoplado e os domínios de acessibilidade dos argumentos A₁, ..., Aₑde tipo .

O domínio de acessibilidade de um membro M aninhado declarado em um tipo T dentro de um programa P, é definido da seguinte forma (observando que M ele mesmo pode ser um tipo):

  • Se a acessibilidade declarada de é M, o domínio de public acessibilidade de M é o domínio de acessibilidade de T.
  • Se a acessibilidade declarada de é , vamos M ser a união do texto do programa de protected internal e o texto do programa de qualquer tipo derivado de D, que é declarado P fora T.P O domínio de acessibilidade de M é a interseção do domínio de acessibilidade de T com D.
  • Se a acessibilidade declarada de M é , vamos private protected ser a interseção do texto do programa de D e o texto do programa de P e qualquer tipo derivado de TT. O domínio de acessibilidade de M é a interseção do domínio de acessibilidade de T com D.
  • Se a acessibilidade declarada de M é , vamos protected ser a união do texto do programa de De o texto do programa de qualquer tipo derivado de TT. O domínio de acessibilidade de M é a interseção do domínio de acessibilidade de T com D.
  • Se a acessibilidade declarada de M é internal, o domínio de acessibilidade de M é a interseção do domínio de acessibilidade de T com o texto do programa de P.
  • Se a acessibilidade declarada de é M, o domínio de private acessibilidade de é o texto do M programa de T.

Nota: A partir dessas definições, segue-se que o domínio de acessibilidade de um membro aninhado é sempre pelo menos o texto do programa do tipo no qual o membro é declarado. Além disso, segue-se que o domínio de acessibilidade de um membro nunca é mais inclusivo do que o domínio de acessibilidade do tipo em que o membro é declarado. Nota final

Nota: Em termos intuitivos, quando um tipo ou membro M é acedido, são avaliados os seguintes passos para garantir que o acesso é permitido:

  • Primeiro, se M for declarado dentro de um tipo (em oposição a uma unidade de compilação ou um namespace), ocorrerá um erro em tempo de compilação se esse tipo não estiver acessível.
  • Então, se M for public, o acesso é permitido.
  • Caso contrário, se M for protected internal, o acesso é permitido se ocorrer dentro do programa no qual M é declarado, ou se ocorrer dentro de uma classe derivada da classe na qual M é declarada e ocorre através do tipo de classe derivada (§7.5.4).
  • Caso contrário, se M for protected, o acesso é permitido se ocorrer dentro da classe em que M é declarado, ou se ocorrer dentro de uma classe derivada da classe em que M é declarada e ocorrer através do tipo de classe derivado (§7.5.4).
  • Caso contrário, se M for internal, o acesso é permitido se ocorrer dentro do programa em que M é declarado.
  • Caso contrário, se M for private, o acesso é permitido se ocorrer dentro do tipo em que M é declarado.
  • Caso contrário, o tipo ou membro fica inacessível e ocorre um erro em tempo de compilação. Nota final

Exemplo: No código a seguir

public class A
{
    public static int X;
    internal static int Y;
    private static int Z;
}

internal class B
{
    public static int X;
    internal static int Y;
    private static int Z;

    public class C
    {
        public static int X;
        internal static int Y;
        private static int Z;
    }

    private class D
    {
        public static int X;
        internal static int Y;
        private static int Z;
    }
}

As classes e membros têm os seguintes domínios de acessibilidade:

  • O domínio de acessibilidade de A e A.X é ilimitado.
  • O domínio de acessibilidade de A.Y, B, B.X, , B.YB.C, , B.C.Xe B.C.Y é o texto do programa que o contém.
  • O domínio de acessibilidade de A.Z é o texto do programa de A.
  • O domínio de acessibilidade de B.Z e B.D é o texto do programa de B, incluindo o texto do programa de B.C e B.D.
  • O domínio de acessibilidade de B.C.Z é o texto do programa de B.C.
  • O domínio de acessibilidade de B.D.X e B.D.Y é o texto do programa de B, incluindo o texto do programa de B.C e B.D.
  • O domínio de acessibilidade de B.D.Z é o texto do programa de B.D. Como o exemplo ilustra, o domínio de acessibilidade de um membro nunca é maior do que o de um tipo de contenção. Por exemplo, embora todos os X membros tenham acessibilidade pública declarada, todos, exceto A.X têm domínios de acessibilidade que são limitados por um tipo de contenção.

Exemplo final

Conforme descrito no §7.4, todos os membros de uma classe base, exceto construtores, finalizadores e construtores estáticos, são herdados por tipos derivados. Isso inclui até mesmo membros privados de uma classe base. No entanto, o domínio de acessibilidade de um membro privado inclui apenas o texto do programa do tipo em que o membro é declarado.

Exemplo: No código a seguir

class A
{
    int x;

    static void F(B b)
    {
        b.x = 1;         // Ok
    }
}

class B : A
{
    static void F(B b)
    {
        b.x = 1;         // Error, x not accessible
    }
}

A B classe herda o membro x privado da A classe. Como o membro é privado, ele só é acessível dentro do class_body de A. Assim, o acesso a b.x é bem-sucedido no A.F método, mas falha no B.F método.

Exemplo final

7.5.4 Acesso protegido

Quando um protected membro da instância ou private protected é acessado fora do texto do programa da classe na qual é declarado, e quando um protected internal membro da instância é acessado fora do texto do programa no qual é declarado, o acesso deve ocorrer dentro de uma declaração de classe que deriva da classe na qual é declarado. Além disso, o acesso deve ocorrer através de uma instância desse tipo de classe derivado ou de um tipo de classe construído a partir dele. Essa restrição impede que uma classe derivada acesse membros protegidos de outras classes derivadas, mesmo quando os membros são herdados da mesma classe base.

Seja B uma classe base que declare um membro Mde instância protegido e seja D uma classe derivada de B. No class_bodyDacesso pode M assumir uma das seguintes formas:

  • Uma type_name ou primary_expression não qualificada M
  • Um D
  • Uma primary_expression do formulário base.M.
  • Uma ]

Além dessas formas de acesso, uma classe derivada pode acessar um construtor de instância protegida de uma classe base em um constructor_initializer (§15.11.2).

Exemplo: No código a seguir

public class A
{
    protected int x;

    static void F(A a, B b)
    {
        a.x = 1; // Ok
        b.x = 1; // Ok
    }
}

public class B : A
{
    static void F(A a, B b)
    {
        a.x = 1; // Error, must access through instance of B
        b.x = 1; // Ok
    }
}

Dentro Ado , é possível acessar x através de instâncias de ambos e AB , uma vez que em ambos os casos o acesso ocorre através de uma instância de ou uma classe derivada A de A. No entanto, dentro Bde , não é possível acessar x através de uma instância de A, uma vez que A não deriva de B.

Exemplo final

Exemplo:

class C<T>
{
    protected T x;
}

class D<T> : C<T>
{
    static void F()
    {
        D<T> dt = new D<T>();
        D<int> di = new D<int>();
        D<string> ds = new D<string>();
        dt.x = default(T);
        di.x = 123;
        ds.x = "test";
    }
}

Aqui, as três atribuições são x permitidas porque todas elas ocorrem através de instâncias de tipos de classe construídos a partir do tipo genérico.

Exemplo final

Nota: O domínio de acessibilidade (§7.5.3) de um membro protegido declarado em uma classe genérica inclui o texto do programa de todas as declarações de classe derivadas de qualquer tipo construído a partir dessa classe genérica. No exemplo:

class C<T>
{
    protected static T x;
}

class D : C<string>
{
    static void Main()
    {
        C<int>.x = 5;
    }
}

A referência a protected Member C<int>.x In D é válida mesmo que a classe D derive de C<string>. Nota final

7.5.5 Limitações de acessibilidade

Várias construções na linguagem C# exigem que um tipo seja pelo menos tão acessível quanto um membro ou outro tipo. Diz-se que um tipo T é pelo menos tão acessível quanto um membro ou tipo M se o domínio de acessibilidade de T é um superconjunto do domínio de acessibilidade de M. Por outras palavras, T é pelo menos tão acessível como M se T fosse acessível em todos os contextos em que M é acessível.

Existem as seguintes restrições de acessibilidade:

  • A classe de base direta de um tipo de classe deve ser pelo menos tão acessível como o próprio tipo de classe.
  • As interfaces de base explícitas de um tipo de interface devem ser pelo menos tão acessíveis como o próprio tipo de interface.
  • O tipo de retorno e os tipos de parâmetros de um tipo delegado devem ser pelo menos tão acessíveis quanto o próprio tipo de delegado.
  • O tipo de constante deve ser pelo menos tão acessível como a própria constante.
  • O tipo de campo deve ser pelo menos tão acessível como o próprio campo.
  • O tipo de retorno e os tipos de parâmetros de um método devem ser pelo menos tão acessíveis como o próprio método.
  • O tipo de propriedade deve ser, pelo menos, tão acessível como o próprio imóvel.
  • O tipo de evento deve ser pelo menos tão acessível como o próprio evento.
  • O tipo e os tipos de parâmetros de um indexador devem ser pelo menos tão acessíveis como o próprio indexador.
  • Os tipos de retorno e os tipos de parâmetros de um operador devem ser pelo menos tão acessíveis como o próprio operador.
  • Os tipos de parâmetros de um construtor de instância devem ser pelo menos tão acessíveis quanto o próprio construtor de instância.
  • Uma restrição de tipo de interface ou classe relativa a um parâmetro de tipo deve ser pelo menos tão acessível como o membro que declara essa restrição.

Exemplo: No código a seguir

class A {...}
public class B: A {...}

A B classe resulta em um erro em tempo de compilação porque A não é pelo menos tão acessível quanto B.

Exemplo final

Exemplo: Da mesma forma, no código a seguir

class A {...}

public class B
{
    A F() {...}
    internal A G() {...}
    public A H() {...}
}

O H método em B resulta em um erro em tempo de compilação porque o tipo A de retorno não é pelo menos tão acessível quanto o método.

Exemplo final

7.6 Assinaturas e sobrecarga

Métodos, construtores de instância, indexadores e operadores são caracterizados por suas assinaturas:

  • A assinatura de um método consiste no nome do método, no número de parâmetros de tipo e no tipo e modo de passagem de parâmetros de cada um de seus parâmetros, considerados na ordem da esquerda para a direita. Para esses fins, qualquer parâmetro de tipo do método que ocorre no tipo de um parâmetro é identificado não por seu nome, mas por sua posição ordinal na lista de parâmetros de tipo do método. A assinatura de um método especificamente não inclui o tipo de retorno, nomes de parâmetros, nomes de parâmetros de tipo, restrições de parâmetros de tipo, modificadores de parâmetros ou params nem se os this parâmetros são necessários ou opcionais.
  • A assinatura de um construtor de instância consiste no tipo e no modo de passagem de parâmetros de cada um de seus parâmetros, considerados na ordem da esquerda para a direita. A assinatura de um construtor de instância especificamente não inclui o params modificador que pode ser especificado para o parâmetro mais à direita, nem se os parâmetros são necessários ou opcionais.
  • A assinatura de um indexador consiste no tipo de cada um dos seus parâmetros, considerados na ordem da esquerda para a direita. A assinatura de um indexador especificamente não inclui o tipo de elemento, nem o params modificador que pode ser especificado para o parâmetro mais à direita, nem se os parâmetros são obrigatórios ou opcionais.
  • A assinatura de um operador consiste no nome do operador e no tipo de cada um dos seus parâmetros, considerados na ordem da esquerda para a direita. A assinatura de um operador especificamente não inclui o tipo de resultado.
  • A assinatura de um operador de conversão consiste no tipo de origem e no tipo de destino. A classificação implícita ou explícita de um operador de conversão não faz parte da assinatura.
  • Duas assinaturas do mesmo tipo de membro (método, construtor de instância, indexador ou operador) são consideradas as mesmas assinaturas se tiverem o mesmo nome, número de parâmetros de tipo, número de parâmetros e modos de passagem de parâmetros, e existir uma conversão de identidade entre os tipos de seus parâmetros correspondentes (§10.2.2).

As assinaturas são o mecanismo habilitador para a sobrecarga de membros em classes, estruturas e interfaces:

  • A sobrecarga de métodos permite que uma classe, struct ou interface declare vários métodos com o mesmo nome, desde que suas assinaturas sejam exclusivas dentro dessa classe, struct ou interface.
  • A sobrecarga de construtores de instância permite que uma classe ou struct declare vários construtores de instância, desde que suas assinaturas sejam exclusivas dentro dessa classe ou estrutura.
  • A sobrecarga de indexadores permite que uma classe, struct ou interface declare vários indexadores, desde que suas assinaturas sejam exclusivas dentro dessa classe, struct ou interface.
  • A sobrecarga de operadores permite que uma classe ou struct declare vários operadores com o mesmo nome, desde que suas assinaturas sejam exclusivas dentro dessa classe ou struct.

Embora in, oute ref modificadores de parâmetros sejam considerados parte de uma assinatura, os membros declarados em um único tipo não podem diferir na assinatura apenas por in, oute ref. Um erro em tempo de compilação ocorre se dois membros são declarados no mesmo tipo com assinaturas que seriam as mesmas se todos os parâmetros em ambos os métodos com out ou in modificadores fossem alterados para ref modificadores. Para outros fins de correspondência de assinatura (por exemplo, ocultar ou substituir), in, out, e ref são considerados parte da assinatura e não correspondem entre si.

Nota: Esta restrição é para permitir que programas C# sejam facilmente traduzidos para serem executados na Common Language Infrastructure (CLI), que não fornece uma maneira de definir métodos que diferem apenas em in, oute ref. Nota final

Os tipos object e dynamic não são distinguidos quando se comparam assinaturas. Por conseguinte, não são permitidos membros declarados num único tipo cujas assinaturas diferem apenas por substituição object por dynamic por.

Exemplo: O exemplo a seguir mostra um conjunto de declarações de método sobrecarregadas junto com suas assinaturas.

interface ITest
{
    void F();                   // F()
    void F(int x);              // F(int)
    void F(ref int x);          // F(ref int)
    void F(out int x);          // F(out int) error
    void F(object o);           // F(object)
    void F(dynamic d);          // error.
    void F(int x, int y);       // F(int, int)
    int F(string s);            // F(string)
    int F(int x);               // F(int) error
    void F(string[] a);         // F(string[])
    void F(params string[] a);  // F(string[]) error
    void F<S>(S s);             // F<0>(0)
    void F<T>(T t);             // F<0>(0) error
    void F<S,T>(S s);           // F<0,1>(0)
    void F<T,S>(S s);           // F<0,1>(1) ok
}

Observe que qualquer inmodificador , oute ref parâmetro (§15.6.2) fazem parte de uma assinatura. Assim, F(int), F(in int), F(out int) , e F(ref int) são todas assinaturas únicas. No entanto, F(in int), F(out int) e F(ref int) não podem ser declarados dentro da mesma interface porque suas assinaturas diferem apenas por in, oute ref. Além disso, observe que o tipo de retorno e o params modificador não fazem parte de uma assinatura, portanto, não é possível sobrecarregar apenas com base no tipo de retorno ou na inclusão ou exclusão do params modificador. Como tal, as declarações dos métodos F(int) e F(params string[]) identificadas acima, resultam em um erro em tempo de compilação. Exemplo final

7.7 Âmbitos de aplicação

7.7.1 Generalidades

O escopo de um nome é a região do texto do programa dentro da qual é possível referir-se à entidade declarada pelo nome sem qualificação do nome. Os escopos podem ser aninhados e um escopo interno pode redeclarar o significado de um nome de um escopo externo. (Isso não elimina, no entanto, a restrição imposta pelo §7.3 de que dentro de um bloco aninhado não é possível declarar uma variável local ou constante local com o mesmo nome que uma variável local ou constante local em um bloco fechado.) Diz-se então que o nome do escopo externo está oculto na região do texto do programa coberta pelo escopo interno, e o acesso ao nome externo só é possível qualificando o nome.

  • O escopo de um membro de namespace declarado por um namespace_member_declaration (§14.6) sem namespace_declaration de inclusão é todo o texto do programa.

  • O escopo de um membro de namespace declarado por um namespace_member_declaration dentro de um namespace_declaration cujo nome totalmente qualificado é N, é o namespace_body de cada namespace_declaration cujo nome totalmente qualificado é N ou começa com N, seguido por um ponto.

  • O âmbito de um nome definido por um extern_alias_directive (§14.4) estende-se ao using_directives, global_attributes e namespace_member_declarations dos seus compilation_unit ou namespace_body que contenham imediatamente. Um extern_alias_directive não contribui com novos membros para o espaço de declaração subjacente. Em outras palavras, uma extern_alias_directive não é transitiva, mas, sim, afeta apenas a compilation_unit ou namespace_body em que ocorre.

  • O âmbito de um nome definido ou importado por um using_directive (§14.5) estende-se ao global_attributes e namespace_member_declarations do compilation_unit ou namespace_body em que o using_directive ocorre. Um using_directive pode disponibilizar zero ou mais nomes de namespace ou tipo em um determinado compilation_unit ou namespace_body, mas não contribui com novos membros para o espaço de declaração subjacente. Em outras palavras, um using_directive não é transitivo, mas afeta apenas o compilation_unit ou namespace_body em que ocorre.

  • O escopo de um parâmetro de tipo declarado por um type_parameter_list em um class_declaration (§15.2) é o class_base, type_parameter_constraints_clauses e class_body desse class_declaration.

    Nota: Ao contrário dos membros de uma classe, este âmbito não se estende às classes derivadas. Nota final

  • O âmbito de um parâmetro de tipo declarado por um type_parameter_list num struct_declaration (§16.2) é o struct_interfaces, type_parameter_constraints_clauses e struct_body desse struct_declaration.

  • O âmbito de um parâmetro de tipo declarado por um type_parameter_list num interface_declaration (§18.2) é o interface_base, type_parameter_constraints_clauses e interface_body desse interface_declaration.

  • O escopo de um parâmetro de tipo declarado por um type_parameter_list em um delegate_declaration (§20.2) é o return_type, parameter_list e type_parameter_constraints_clauses desse delegate_declaration.

  • O âmbito de um parâmetro de tipo declarado por um type_parameter_list num method_declaration (§15.6.1) é o method_declaration.

  • O âmbito de um membro declarado por um class_member_declaration (§15.3.1) é o class_body em que a declaração ocorre. Além disso, o âmbito de um membro de classe estende-se à class_body das classes derivadas que estão incluídas no domínio de acessibilidade (§7.5.3) do membro.

  • O âmbito de um membro declarado por um struct_member_declaration (§16.3) é o struct_body em que a declaração ocorre.

  • O âmbito de um membro declarado por um enum_member_declaration (§19.4) é o enum_body em que a declaração ocorre.

  • O âmbito de aplicação de um parâmetro declarado num method_declaration (§15.6) é o method_body ou ref_method_body desse method_declaration.

  • O âmbito de aplicação de um parâmetro declarado numa indexer_declaration (§15.9) é o indexer_body dessa indexer_declaration.

  • O âmbito de aplicação de um parâmetro declarado numa operator_declaration (§15.10) é o operator_body dessa operator_declaration.

  • O âmbito de um parâmetro declarado numa constructor_declaration (§15.11) é o constructor_initializer e o bloco dessa constructor_declaration.

  • O âmbito de aplicação de um parâmetro declarado numa lambda_expression (§12.19) é o lambda_expression_body dessa lambda_expression.

  • O âmbito de um parâmetro declarado num anonymous_method_expression (§12.19) é o bloco dessa anonymous_method_expression.

  • O âmbito de aplicação de um rótulo declarado numa labeled_statement (§13.5) é o bloco em que a declaração ocorre.

  • O escopo de uma variável local declarada em um local_variable_declaration (§13.6.2) é o bloco no qual a declaração ocorre.

  • O âmbito de uma variável local declarada numa switch_block de uma declaração (switch) é o switch_block.

  • O escopo de uma variável local declarada em uma for_initializer de uma for declaração (§13.9.4) é a for_initializer, for_condition, for_iterator e embedded_statement da for declaração.

  • O âmbito de uma constante local declarada num local_constant_declaration (§13.6.3) é o bloco em que a declaração ocorre. É um erro em tempo de compilação referir-se a uma constante local em uma posição textual que precede sua constant_declarator.

  • O escopo de uma variável declarada como parte de uma foreach_statement, using_statement, lock_statement ou query_expression é determinado pela expansão da construção dada.

Dentro do escopo de um namespace, classe, struct ou membro de enumeração, é possível referir-se ao membro em uma posição textual que precede a declaração do membro.

Exemplo:

class A
{
    void F()
    {
        i = 1;
    }

    int i = 0;
}

Aqui, é válido referir-se F antes i de ser declarado.

Exemplo final

Dentro do escopo de uma variável local, é um erro em tempo de compilação referir-se à variável local em uma posição textual que precede seu declarador.

Exemplo:

class A
{
    int i = 0;

    void F()
    {
        i = 1;                // Error, use precedes declaration
        int i;
        i = 2;
    }

    void G()
    {
        int j = (j = 1);     // Valid
    }

    void H()
    {
        int a = 1, b = ++a; // Valid
    }
}

No método acima, a F primeira atribuição a i especificamente não se refere ao campo declarado no escopo externo. Em vez disso, refere-se à variável local e resulta em um erro em tempo de compilação porque precede textualmente a declaração da variável. G No método, o uso de j no inicializador para a declaração de j é válido porque o uso não precede o declarador. H No método, um declarador subsequente refere-se corretamente a uma variável local declarada em um declarador anterior dentro do mesmo local_variable_declaration.

Exemplo final

Nota: As regras de escopo para variáveis locais e constantes locais são projetadas para garantir que o significado de um nome usado em um contexto de expressão seja sempre o mesmo dentro de um bloco. Se o escopo de uma variável local se estendesse apenas de sua declaração até o final do bloco, então, no exemplo acima, a primeira atribuição seria atribuída à variável de instância e a segunda atribuição atribuiria à variável local, possivelmente levando a erros em tempo de compilação se as instruções do bloco fossem posteriormente reorganizadas.)

O significado de um nome dentro de um bloco pode diferir com base no contexto em que o nome é usado. No exemplo

class A {}

class Test
{
    static void Main()
    {
        string A = "hello, world";
        string s = A;                      // expression context
        Type t = typeof(A);                // type context
        Console.WriteLine(s);              // writes "hello, world"
        Console.WriteLine(t);              // writes "A"
    }
}

O nome A é usado em um contexto de expressão para se referir à variável A local e em um contexto de tipo para se referir à classe A.

Nota final

7.7.2 Ocultação de nomes

7.7.2.1 Generalidades

O escopo de uma entidade normalmente engloba mais texto de programa do que o espaço de declaração da entidade. Em particular, o âmbito de uma entidade pode incluir declarações que introduzam novos espaços de declaração contendo entidades com o mesmo nome. Tais declarações fazem com que a entidade original fique oculta. Por outro lado, diz-se que uma entidade é visível quando não está escondida.

A ocultação de nomes ocorre quando os escopos se sobrepõem por meio de aninhamento e quando os escopos se sobrepõem por meio de herança. As características dos dois tipos de ocultação são descritas nas subcláusulas seguintes.

7.7.2.2 Ocultando-se através do nesting

A ocultação de nomes por meio do aninhamento pode ocorrer como resultado do aninhamento de namespaces ou tipos dentro de namespaces, como resultado de tipos de aninhamento dentro de classes ou estruturas, como resultado de uma função local ou lambda e como resultado de parâmetro, variável local e declarações constantes locais.

Exemplo: No código a seguir

class A
{
    int i = 0;
    void F()
    {
        int i = 1;

        void M1()
        {
            float i = 1.0f;
            Func<double, double> doubler = (double i) => i * 2.0;
        }
    }

    void G()
    {
        i = 1;
    }
}

Dentro do F método, a variável i de instância é ocultada pela variável ilocal , mas dentro do G método, i ainda se refere à variável de instância. Dentro da função M1 local o float i esconde o imediato-exterior i. O parâmetro i lambda oculta o float i interior do corpo lambda.

Exemplo final

Quando um nome em um escopo interno oculta um nome em um escopo externo, ele oculta todas as ocorrências sobrecarregadas desse nome.

Exemplo: No código a seguir

class Outer
{
    static void F(int i) {}
    static void F(string s) {}

    class Inner
    {
        static void F(long l) {}

        void G()
        {
            F(1); // Invokes Outer.Inner.F
            F("Hello"); // Error
        }
    }
}

A chamada F(1) invoca o F declarado em Inner porque todas as ocorrências externas de F são ocultadas pela declaração interna. Pelo mesmo motivo, a chamada F("Hello") resulta em um erro em tempo de compilação.

Exemplo final

7.7.2.3 Ocultação por herança

A ocultação de nomes por herança ocorre quando classes ou estruturas redeclaram nomes que foram herdados de classes base. Este tipo de ocultação de nomes assume uma das seguintes formas:

  • Uma constante, campo, propriedade, evento ou tipo introduzido em uma classe ou struct oculta todos os membros da classe base com o mesmo nome.
  • Um método introduzido em uma classe ou struct oculta todos os membros da classe base não-método com o mesmo nome e todos os métodos de classe base com a mesma assinatura (§7.6).
  • Um indexador introduzido numa classe ou struct oculta todos os indexadores de classe base com a mesma assinatura (§7.6).

As regras que regem as declarações de operador (ponto 15.10) tornam impossível a uma classe derivada declarar um operador com a mesma assinatura que um operador de uma classe de base. Assim, os operadores nunca se escondem uns aos outros.

Ao contrário de ocultar um nome de um escopo externo, ocultar um nome visível de um escopo herdado faz com que um aviso seja relatado.

Exemplo: No código a seguir

class Base
{
    public void F() {}
}

class Derived : Base
{
    public void F() {} // Warning, hiding an inherited name
}

a declaração de F in Derived faz com que uma advertência seja comunicada. Ocultar um nome herdado não é especificamente um erro, uma vez que isso impediria a evolução separada das classes base. Por exemplo, a situação acima pode ter surgido porque uma versão posterior do Base introduziu um F método que não estava presente em uma versão anterior da classe.

Exemplo final

O aviso causado pela ocultação de um nome herdado pode ser eliminado através do uso do new modificador:

Exemplo:

class Base
{
    public void F() {}
}

class Derived : Base
{
    public new void F() {}
}

O new modificador indica que o F in Derived é "novo", e que ele é realmente destinado a esconder o membro herdado.

Exemplo final

Uma declaração de um novo membro oculta um membro herdado apenas no âmbito do novo membro.

Exemplo:

class Base
{
    public static void F() {}
}

class Derived : Base
{
    private new static void F() {} // Hides Base.F in Derived only
}

class MoreDerived : Derived
{
    static void G()
    {
        F();                       // Invokes Base.F
    }
}

No exemplo acima, a declaração de F in esconde o Derived que foi herdado de F, mas como o novo Base in F tem acesso privado, seu escopo não se estende a DerivedMoreDerived . Assim, a chamada F()MoreDerived.G é válida e invocará Base.F.

Exemplo final

7.8 Nomes de namespace e tipo

7.8.1 Generalidades

Vários contextos em um programa C# exigem que um namespace_name ou um type_name sejam especificados.

namespace_name
    : namespace_or_type_name
    ;

type_name
    : namespace_or_type_name
    ;
    
namespace_or_type_name
    : identifier type_argument_list?
    | namespace_or_type_name '.' identifier type_argument_list?
    | qualified_alias_member
    ;

Um namespace_name é um namespace_or_type_name que se refere a um namespace.

Após a resolução, conforme descrito abaixo, o namespace_or_type_name de um namespace_name deve referir-se a um namespace ou, caso contrário, ocorrerá um erro em tempo de compilação. Nenhum argumento de tipo (§8.4.2) pode estar presente em um namespace_name (apenas tipos podem ter argumentos de tipo).

Um type_name é um namespace_or_type_name que se refere a um tipo. Após a resolução, conforme descrito abaixo, o namespace_or_type_name de um type_name deve referir-se a um tipo, ou de outra forma ocorre um erro em tempo de compilação.

Se o namespace_or_type_name for um qualified_alias_member o seu significado é o descrito no §14.8.1. Caso contrário, um namespace_or_type_name tem uma de quatro formas:

  • I
  • I<A₁, ..., Aₓ>
  • N.I
  • N.I<A₁, ..., Aₓ>

onde I é um identificador único, N é um namespace_or_type_name e <A₁, ..., Aₓ> é um type_argument_list opcional. Quando nenhum type_argument_list for especificado, considere x zero.

O significado de um namespace_or_type_name é determinado da seguinte forma:

  • Se o namespace_or_type_name for um qualified_alias_member, o significado é o especificado no §14.8.1.
  • Caso contrário, se o namespace_or_type_name for da forma I ou da forma I<A₁, ..., Aₓ>:
    • Se x for zero e o namespace_or_type_name aparecer dentro de uma declaração de método genérico (§15.6), mas fora dos atributos de seu cabeçalho de método, e se essa declaração incluir um parâmetro de tipo (§15.2.3) com nome I, então o namespace_or_type_name refere-se a esse parâmetro de tipo.
    • Caso contrário, se o namespace_or_type_name aparecer dentro de uma declaração de tipo, então para cada tipo T de instância (§15.3.2), começando com o tipo de instância dessa declaração de tipo e continuando com o tipo de instância de cada classe anexa ou declaração struct (se houver):
      • Se x for zero e a declaração de incluir um parâmetro type com name T, então o I refere-se a esse parâmetro type.
      • Caso contrário, se o namespace_or_type_name aparecer dentro do corpo da declaração de tipo, e T ou qualquer um de seus tipos base contiver um tipo acessível aninhado com parâmetros de nome I e x tipo, o namespace_or_type_name se refere a esse tipo construído com os argumentos de tipo fornecidos. Se houver mais de um desses tipos, o tipo declarado dentro do tipo mais derivado será selecionado.

      Nota: Membros não-tipo (constantes, campos, métodos, propriedades, indexadores, operadores, construtores de instância, finalizadores e construtores estáticos) e membros de tipo com um número diferente de parâmetros de tipo são ignorados ao determinar o significado do namespace_or_type_name. Nota final

    • Caso contrário, para cada namespace N, começando com o namespace no qual o namespace_or_type_name ocorre, continuando com cada namespace de inclusão (se houver) e terminando com o namespace global, as seguintes etapas são avaliadas até que uma entidade seja localizada:
      • Se x for zero e I for o nome de um namespace no N, então:
        • Se o local onde o namespace_or_type_name ocorre estiver incluído por uma declaração de namespace para N e a declaração de namespace contiver um extern_alias_directive ou using_alias_directive que associe o nome I a um namespace ou tipo, o namespace_or_type_name será ambíguo e ocorrerá um erro em tempo de compilação.
        • Caso contrário, o namespace_or_type_name refere-se ao namespace nomeado I em N.
      • Caso contrário, se N contiver um tipo acessível com parâmetros name I e x type, então:
        • Se x for zero e o local onde o namespace_or_type_name ocorre estiver incluído por uma declaração de namespace para N e a declaração de namespace contiver um extern_alias_directive ou using_alias_directive que associe o nome I a um namespace ou tipo, o namespace_or_type_name será ambíguo e ocorrerá um erro em tempo de compilação.
        • Caso contrário, o namespace_or_type_name refere-se ao tipo construído com os argumentos de tipo fornecidos.
      • Caso contrário, se o local onde o namespace_or_type_name ocorre estiver incluído por uma declaração de namespace para N:
        • Se x for zero e a declaração de namespace contiver um extern_alias_directive ou using_alias_directive que associe o nome I a um namespace ou tipo importado, o namespace_or_type_name se refere a esse namespace ou tipo.
        • Caso contrário, se os namespaces importados pelos using_namespace_directives da declaração de namespace contiverem exatamente um tipo com parâmetros name I e x type, o namespace_or_type_name se refere a esse tipo construído com os argumentos de tipo fornecidos.
        • Caso contrário, se os namespaces importados pelos using_namespace_directives da declaração de namespace contiverem mais de um tipo com parâmetros name I e x type, o namespace_or_type_name será ambíguo e ocorrerá um erro.
    • Caso contrário, o namespace_or_type_name é indefinido e ocorre um erro em tempo de compilação.
  • Caso contrário, a namespace_or_type_name é da forma N.I ou da forma N.I<A₁, ..., Aₓ>. N é primeiramente resolvido como um namespace_or_type_name. Se a resolução do não for bem-sucedida, ocorrerá um erro em tempo de N compilação. Caso contrário, N.I ou N.I<A₁, ..., Aₓ> é resolvido da seguinte forma:
    • Se x for zero e N se referir a um namespace e N contiver um namespace aninhado com nome I, o namespace_or_type_name se refere a esse namespace aninhado.
    • Caso contrário, se N se refere a um namespace e N contém um tipo acessível com parâmetros name I e x type, então o namespace_or_type_name refere-se a esse tipo construído com os argumentos de tipo fornecidos.
    • Caso contrário, se N se refere a uma classe (possivelmente construída) ou tipo struct e N ou qualquer uma de suas classes base contém um tipo acessível aninhado com parâmetros de nome I e x tipo, então o namespace_or_type_name refere-se a esse tipo construído com os argumentos de tipo fornecidos. Se houver mais de um desses tipos, o tipo declarado dentro do tipo mais derivado será selecionado.

      Nota: Se o significado de N.I está a ser determinado como parte da resolução da especificação da classe de N base, então considera-se que a classe de base direta de N é (object§15.2.4.2). Nota final

    • Caso contrário, N.I é um namespace_or_type_name inválido e ocorre um erro em tempo de compilação.

Um namespace_or_type_name só pode fazer referência a uma classe estática (§15.2.2.4) se:

  • A namespace_or_type_name é a T namespace_or_type_name da formaT.I, ou
  • O namespace_or_type_name é o T numa typeof_expression (§12.8.18) da forma typeof(T)

7.8.2 Nomes não qualificados

Cada declaração de namespace e declaração de tipo tem um nome não qualificado determinado da seguinte maneira:

  • Para uma declaração de namespace, o nome não qualificado é o qualified_identifier especificado na declaração.
  • Para uma declaração de tipo sem type_parameter_list, o nome não qualificado é o identificador especificado na declaração.
  • Para uma declaração de tipo com parâmetros de tipo K, o nome não qualificado é o identificador de especificado na declaração, seguido pelo generic_dimension_specifier (§12.8.18) para parâmetros de tipo K.

7.8.3 Nomes totalmente qualificados

Cada namespace e declaração de tipo tem um nome totalmente qualificado, que identifica exclusivamente o namespace ou declaração de tipo entre todos os outros dentro do programa. O nome totalmente qualificado de um namespace ou declaração de tipo com nome N não qualificado é determinado da seguinte maneira:

  • Se N for um membro do namespace global, seu nome totalmente qualificado será N.
  • Caso contrário, seu nome totalmente qualificado é S.N, onde S é o nome totalmente qualificado do namespace ou declaração de tipo na qual N é declarado.

Em outras palavras, o nome totalmente qualificado de N é o caminho hierárquico completo de identificadores e generic_dimension_specifiers que levam a N, a partir do namespace global. Como cada membro de um namespace ou tipo deve ter um nome exclusivo, segue-se que o nome totalmente qualificado de um namespace ou declaração de tipo é sempre exclusivo. É um erro em tempo de compilação para o mesmo nome totalmente qualificado referir-se a duas entidades distintas. Em particular:

  • É um erro para uma declaração de namespace e uma declaração de tipo ter o mesmo nome totalmente qualificado.
  • É um erro que dois tipos diferentes de declarações de tipo tenham o mesmo nome totalmente qualificado (por exemplo, se uma declaração struct e uma declaração de classe tiverem o mesmo nome totalmente qualificado).
  • É um erro que uma declaração de tipo sem o modificador parcial tenha o mesmo nome totalmente qualificado que outra declaração de tipo (§15.2.7).

Exemplo: O exemplo abaixo mostra várias declarações de namespace e tipo, juntamente com seus nomes totalmente qualificados associados.

class A {}                 // A
namespace X                // X
{
    class B                // X.B
    {
        class C {}         // X.B.C
    }
    namespace Y            // X.Y
    {
        class D {}         // X.Y.D
    }
}
namespace X.Y              // X.Y
{
    class E {}             // X.Y.E
    class G<T>             // X.Y.G<>
    {           
        class H {}         // X.Y.G<>.H
    }
    class G<S,T>           // X.Y.G<,>
    {         
        class H<U> {}      // X.Y.G<,>.H<>
    }
}

Exemplo final

7.9 Gestão automática de memória

O C# emprega o gerenciamento automático de memória, que libera os desenvolvedores de alocar e liberar manualmente a memória ocupada por objetos. As políticas de gerenciamento automático de memória são implementadas por um coletor de lixo. O ciclo de vida de gerenciamento de memória de um objeto é o seguinte:

  1. Quando o objeto é criado, a memória é alocada para ele, o construtor é executado e o objeto é considerado dinâmico.
  2. Se nem o objeto nem qualquer um de seus campos de instância podem ser acessados por qualquer possível continuação da execução, além da execução de finalizadores, o objeto é considerado não mais em uso e torna-se elegível para finalização.

    Nota: O compilador C# e o coletor de lixo podem optar por analisar o código para determinar quais referências a um objeto podem ser usadas no futuro. Por exemplo, se uma variável local que está no escopo é a única referência existente a um objeto, mas essa variável local nunca é referida em qualquer possível continuação da execução a partir do ponto de execução atual no procedimento, o coletor de lixo pode (mas não é obrigado a) tratar o objeto como não mais em uso. Nota final

  3. Uma vez que o objeto é elegível para finalização, em algum momento posterior não especificado o finalizador (§15.13) (se houver) para o objeto é executado. Em circunstâncias normais, o finalizador do objeto é executado apenas uma vez, embora as APIs definidas pela implementação possam permitir que esse comportamento seja substituído.
  4. Uma vez que o finalizador de um objeto é executado, se nem o objeto nem qualquer um de seus campos de instância podem ser acessados por qualquer possível continuação da execução, incluindo a execução de finalizadores, o objeto é considerado inacessível e o objeto se torna elegível para coleção.

    Nota: Um objeto que anteriormente não podia ser acessado pode se tornar acessível novamente devido ao seu finalizador. Um exemplo disso é fornecido abaixo. Nota final

  5. Finalmente, em algum momento depois que o objeto se torna elegível para coleta, o coletor de lixo libera a memória associada a esse objeto.

O coletor de lixo mantém informações sobre o uso do objeto e usa essas informações para tomar decisões de gerenciamento de memória, como onde na memória localizar um objeto recém-criado, quando realocar um objeto e quando um objeto não está mais em uso ou inacessível.

Como outras linguagens que pressupõem a existência de um coletor de lixo, o C# foi projetado para que o coletor de lixo possa implementar uma ampla gama de políticas de gerenciamento de memória. C# não especifica uma restrição de tempo dentro desse período, nem uma ordem na qual os finalizadores são executados. Se os finalizadores são ou não executados como parte do encerramento do aplicativo é definido pela implementação (§7.2).

O comportamento do coletor de lixo pode ser controlado, até certo ponto, através de métodos estáticos na classe System.GC. Essa classe pode ser usada para solicitar que uma coleção ocorra, finalizadores sejam executados (ou não executados) e assim por diante.

Exemplo: Como o coletor de lixo tem ampla latitude para decidir quando coletar objetos e executar finalizadores, uma implementação em conformidade pode produzir uma saída diferente da mostrada pelo código a seguir. O programa

class A
{
    ~A()
    {
        Console.WriteLine("Finalize instance of A");
    }
}

class B
{
    object Ref;
    public B(object o)
    {
        Ref = o;
    }

    ~B()
    {
        Console.WriteLine("Finalize instance of B");
    }
}

class Test
{
    static void Main()
    {
        B b = new B(new A());
        b = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

Cria uma instância de classe A e uma instância de classe B. Estes objetos tornam-se elegíveis para a recolha de lixo quando é atribuído o valor bà variávelnull, uma vez que após este tempo é impossível para qualquer código escrito pelo utilizador aceder aos mesmos. A saída pode ser qualquer

Finalize instance of A
Finalize instance of B

ou

Finalize instance of B
Finalize instance of A

porque a linguagem não impõe restrições à ordem em que os objetos são recolhidos.

Em casos sutis, a distinção entre "elegível para finalização" e "elegível para cobrança" pode ser importante. Por exemplo,

class A
{
    ~A()
    {
        Console.WriteLine("Finalize instance of A");
    }

    public void F()
    {
        Console.WriteLine("A.F");
        Test.RefA = this;
    }
}

class B
{
    public A Ref;

    ~B()
    {
        Console.WriteLine("Finalize instance of B");
        Ref.F();
    }
}

class Test
{
    public static A RefA;
    public static B RefB;

    static void Main()
    {
        RefB = new B();
        RefA = new A();
        RefB.Ref = RefA;
        RefB = null;
        RefA = null;
        // A and B now eligible for finalization
        GC.Collect();
        GC.WaitForPendingFinalizers();
        // B now eligible for collection, but A is not
        if (RefA != null)
        {
            Console.WriteLine("RefA is not null");
        }
    }
}

No programa acima, se o coletor de lixo optar por executar o finalizador de antes do A finalizador de B, então a saída deste programa pode ser:

Finalize instance of A
Finalize instance of B
A.F
RefA is not null

Observe que, embora a instância de A não estava em uso e Ao finalizador do foi executado, ainda é possível que os métodos de A (neste caso, F) sejam chamados de outro finalizador. Além disso, observe que a execução de um finalizador pode fazer com que um objeto se torne utilizável a partir do programa principal novamente. Neste caso, a execução do finalizador do Bfez com que uma instância que anteriormente não estava em uso, se tornasse acessível a partir da A referência Test.RefAao vivo. Após a chamada para WaitForPendingFinalizers, a instância de B é elegível para coleta, mas a instância de A não é, devido à referência Test.RefA.

Exemplo final

7.10 Ordem de execução

A execução de um programa em C# prossegue de tal forma que os efeitos colaterais de cada thread de execução são preservados em pontos críticos de execução. Um efeito colateral é definido como uma leitura ou gravação de um campo volátil, uma gravação em uma variável não volátil, uma gravação em um recurso externo e o lançamento de uma exceção. Os pontos críticos de execução em que a ordem desses efeitos colaterais deve ser preservada são referências a campos voláteis (§15.5.4), lock declarações (§13.13) e criação e terminação de threads. O ambiente de execução é livre para alterar a ordem de execução de um programa C#, sujeito às seguintes restrições:

  • A dependência de dados é preservada dentro de um thread de execução. Ou seja, o valor de cada variável é calculado como se todas as instruções no thread fossem executadas na ordem original do programa.
  • As regras de ordenação de inicialização são preservadas (§15.5.5, §15.5.6).
  • A ordenação dos efeitos secundários é preservada no que diz respeito a leituras e gravações voláteis (§15.5.4). Além disso, o ambiente de execução não precisa avaliar parte de uma expressão se puder deduzir que o valor dessa expressão não é usado e que nenhum efeito colateral necessário é produzido (incluindo qualquer causado pela chamada de um método ou acesso a um campo volátil). Quando a execução do programa é interrompida por um evento assíncrono (como uma exceção lançada por outro thread), não é garantido que os efeitos colaterais observáveis sejam visíveis na ordem original do programa.