Compartilhar via


7 Conceitos básicos

7.1 Inicialização do aplicativo

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

Um programa compilado como um pedido deve conter, pelo menos, um método que possa servir de ponto de entrada, satisfazendo os seguintes requisitos:

  • Terá o nome Main.
  • Deve ser static.
  • Não deve ser genérico.
  • Deve ser declarado num tipo não genérico. Se o tipo que declara o método for um tipo aninhado, nenhum de seus tipos delimitadores poderá ser genérico.
  • Ele pode ter o modificador, async 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[].

Observação: 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 aguardável 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 em um programa, um mecanismo externo poderá ser usado para especificar qual método é considerado o ponto de entrada real para o aplicativo. Se um método de qualificação com um tipo de retorno ou int void for encontrado, qualquer método de qualificação com um tipo de retorno ou System.Threading.Tasks.Task<int> não será considerado um método de ponto de System.Threading.Tasks.Task entrada. É um erro de 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 ponto de entrada.

Normalmente, a acessibilidade declarada (§7.5.2) de um método é determinada pelos modificadores de acesso (§15.3.6) especificados em sua declaração e, da mesma forma, a acessibilidade declarada de um tipo é determinada pelos modificadores de acesso especificados em sua declaração. Para que um determinado método de um determinado tipo possa ser chamado, tanto o tipo quanto o membro devem estar 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 delimitadoras.

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 sintetiza um método de ponto de entrada síncrono que chama o método correspondente Main . O método sintetizado Main tem parâmetros e tipos de retorno com base no 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 será void
  • Se o tipo de retorno do Main método for System.Threading.Tasks.Task<int>, o tipo de retorno do método sintetizado será int

A execução do método sintetizado prossegue 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 esse parâmetro.
  • Se o Main método lançar uma exceção, a exceção será 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 no 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 no mesmo computador ao mesmo tempo, e cada uma tem seu próprio domínio de aplicativo. Um domínio de aplicativo permite o isolamento do aplicativo atuando como um contêiner para o estado do aplicativo. Um domínio de aplicativo atua como um contêiner e um limite para os tipos definidos no aplicativo e nas 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 os 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 deverá 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 de parâmetros de aplicativo, que recebem valores definidos pela implementação pelo ambiente de 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 é conhecido 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 chamada 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 aspectos. 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á tratamento especial do método: se houver um parâmetro, ele poderá ter um valor inicial de null, ou um valor não referentenull a uma matriz que contém referências nulas. Da mesma forma, o valor retornado do ponto de entrada não tem nenhum significado especial além da invocação do ambiente de execução.

7.2 Encerramento do aplicativo

O encerramento 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 int valor do retornado servirá como o código de status de encerramento do aplicativo. O objetivo deste código é permitir a comunicação de sucesso ou falha ao 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 encerramento será 0.

Se o método de ponto de entrada efetivo for encerrado 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.

Observação: 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 como lixo, a menos que essa limpeza tenha sido suprimida (por uma chamada para o método GC.SuppressFinalizelibrary, 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), enumerações (§19) e delegados (§20). Os tipos de membros permitidos em uma 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, essas declarações podem introduzir ambiguidades se incluídas no mesmo aplicativo. 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).
  • Duas ou mais declarações de tipo com o mesmo nome, mas números distintos de parâmetros de tipo, são permitidas 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). Nesse caso, as declarações de tipo contribuem para um único tipo e são 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).

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

  • Em todas as unidades de compilação de um programa, namespace_member_declarationsem namespace_declaration delimitadoras são membros de um único espaço de declaração combinado chamado espaço de declaração global.
  • Em 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 não parcial de classe, struct ou interface 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 por meio 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 esse membro esconde o membro herdado.
  • Cada declaração delegada cria um novo espaço de declaração. Os nomes são introduzidos nesse espaço de declaração por meio de 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 nesse espaço de declaração por meio 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 nesse espaço de declaração por meio de parâmetros (fixed_parameters e parameter_arrays) e type_parameters. O acessador set 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 de 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 é ocultado (§7.7.1) pelo nome local aninhado.
  • Espaços adicionais de declaração de variável local podem ocorrer em declarações de membro, funções anônimas e funções locais. Os nomes são introduzidos nesses espaços de declaração por meio de padrõess, declaration_expressions, declaration_statements e exception_specifiers. Os espaços de declaração de variável local podem ser 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 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 de um parâmetro, parâmetro de tipo, variável local, função local ou constante em um espaço de declaração delimitador. É 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 está aninhado em nenhum outro espaço de declaração de variável local.
    • O corpo de um membro de 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 no 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 delimitador, 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 nesse espaço de declaração por meio de labeled_statements e os nomes são referenciados por meio 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 delimitador.

Nota: O fato de que as variáveis declaradas diretamente dentro de um switch_section são 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 de 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 significado. 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 na qual 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 (ponto 7.7).
  • A ordem de declaração para declarações de membro enumerado (§19.4) é significativa quando constant_expression valores são omitidos.

Exemplo: o espaço de declaração de um namespace é "aberto" 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 de fim

Observação: conforme especificado acima, o espaço de declaração de um bloco inclui todos os blocos aninhados. Assim, no exemplo a seguir, os F métodos and G resultam em um erro de 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 and I são válidos, pois os dois isão declarados em blocos não aninhados separados.

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 Geral

Namespaces e tipos têm membros.

Observação: Os membros de uma entidade geralmente estão disponíveis por meio do uso de um nome qualificado que começa com uma referência à entidade, seguida 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 da 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.

Observação: no entanto, um membro herdado pode não estar acessível em um 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 delimitador são membros do namespace global. Isso corresponde diretamente aos nomes declarados no espaço de declaração global.

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

Namespaces não têm nenhuma restrição 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 de struct

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 alias 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 das classes System.ValueType base indiretas e object.

7.4.5 Membros da classe

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 sem levar em conta 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 eles 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 da classe object não são, estritamente falando, membros de nenhuma interface (§18.4). No entanto, os membros da classe object estão disponíveis por meio 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, ele contém um método nomeado Invoke com o mesmo tipo de retorno e lista de parâmetros especificados em sua declaração (§20.2). Uma invocação desse método deve se comportar 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 meio de herança ou diretamente no próprio delegado.

7.5 Acesso de membros

7.5.1 Geral

As declarações de membros permitem o controle 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 que o contém imediatamente, 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 o local textual no qual o acesso ocorre está incluído 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 incluindo um public modificador na declaração de membro. O significado intuitivo de public é "acesso não limitado".
  • Protected, que é selecionado incluindo um protected modificador na declaração de membro. O significado intuitivo de protected é "acesso limitado à classe ou tipos que contêm derivados da classe que os contém".
  • Internal, que é selecionado incluindo um internal modificador na declaração de membro. O significado intuitivo de internal é "acesso limitado a esta assembleia".
  • Interno protegido, que é selecionado incluindo um modificador e protected um internal modificador na declaração de membro. O significado intuitivo de protected internal é "acessível dentro deste assembly, bem como tipos derivados da classe que o contém".
  • Private protected, que é selecionado incluindo um modificador e private um protected modificador na declaração de membro. O significado intuitivo de private protected é "acessível dentro deste assembly 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 o contém".

Dependendo do contexto em que uma declaração de membro ocorre, apenas determinados 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 no qual a declaração ocorre determina a acessibilidade declarada padrão.

  • Os namespaces declararam public implicitamente a acessibilidade. Nenhum modificador de acesso é permitido em declarações de namespace.
  • Os tipos declarados diretamente em unidades de compilação ou namespaces (em vez de dentro de 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.

    Observação: 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 acessibilidade apenas public ou internal declarada. nota final

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

    Observação: um tipo declarado como membro de um struct pode ter public, internal, ou private acessibilidade declarada, enquanto um tipo declarado como membro de um namespace pode ter acessibilidade apenas public ou internal declarada. nota final

  • Os membros da interface declararam public implicitamente 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 nas quais o acesso ao membro é permitido. Para fins de definição do domínio de acessibilidade de um membro, um membro é considerado de nível superior se não for declarado em um tipo, e um membro é considerado aninhado se for declarado em 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_declarationdesse tipo (incluindo, possivelmente, tipos aninhados dentro do tipo).

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

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

  • Se a acessibilidade declarada de T for pública, o domínio de acessibilidade de T será o texto do programa e P qualquer programa que faça referência a P.
  • Se a acessibilidade declarada de T for interna, o domínio de acessibilidade de T será o texto do programa de P.

Nota: A partir dessas definições, segue-se que o domínio de acessibilidade de um tipo não associado 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 associado 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 maneira (observando que M ele próprio pode ser um tipo):

  • Se a acessibilidade declarada de M for public, o domínio de acessibilidade de M será o domínio de acessibilidade de T.
  • Se a acessibilidade declarada de M for , seja D a união do texto do programa de P e do texto do programa de qualquer tipo derivado de , que é declarado fora Pde Tprotected internal. 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 for , seja D a interseção do texto do programa de P e do texto do programa de T e qualquer tipo derivado de Tprivate protected. 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 for , seja D a união do texto do programa de Te do texto do programa de qualquer tipo derivado de Tprotected. 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 for internal, o domínio de acessibilidade de M será a interseção do domínio de acessibilidade de T com o texto de programa de P.
  • Se a acessibilidade declarada de M for private, o domínio de acessibilidade de M será o texto de 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 no qual o membro é declarado. nota final

Observação: em termos intuitivos, quando um tipo ou membro M é acessado, as seguintes etapas são avaliadas para garantir que o acesso seja 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 será permitido se ocorrer dentro do programa no qual M é declarado ou se ocorrer dentro de uma classe derivada da classe na qual M é declarado e ocorrer por meio do tipo de classe derivada (§7.5.4).
  • Caso contrário, se M for protected, o acesso será permitido se ocorrer dentro da classe na qual M é declarado ou se ocorrer dentro de uma classe derivada da classe na qual M é declarada e ocorrer por meio do tipo de classe derivada (§7.5.4).
  • Caso contrário, se M for internal, o acesso será permitido se ocorrer dentro do programa no qual M é declarado.
  • Caso contrário, se M for private, o acesso será permitido se ocorrer dentro do tipo no qual M é declarado.
  • Caso contrário, o tipo ou membro ficará inacessível e ocorrerá 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 os 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.XB.Y, B.C, B.C.X, e 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 que o contém. Por exemplo, embora todos os X membros tenham acessibilidade pública declarada, todos A.X têm domínios de acessibilidade restritos por um tipo de contenção.

exemplo de fim

Conforme descrito em §7.4, todos os membros de uma classe base, exceto construtores de instância, 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 no qual 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ó pode ser acessado dentro do class_body do A. Assim, o acesso a b.x é bem-sucedido no A.F método, mas falha no B.F método.

exemplo de fim

7.5.4 Acesso protegido

Quando um protected membro da instância ou private protected membro da instância é 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 por meio 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 declara um membro Mde instância protegida e seja D uma classe derivada de B. No class_body de D, o acesso a M pode assumir uma das seguintes formas:

  • Um type_name ou primary_expression não qualificado do formulário M.
  • Um primary_expression da forma E.M, desde que o tipo de E is T ou uma classe derivada de T, onde T é a classe D, ou um tipo de classe construído a partir de D .
  • Uma primary_expression do formulário base.M.
  • Uma primary_expression da formabase[ argument_list].

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 Ade , é possível acessar x através de instâncias de ambos A e B, uma vez que em ambos os casos o acesso ocorre através de uma instância de A ou uma classe derivada 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 de fim

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 a x são permitidas porque todas ocorrem por meio de instâncias de tipos de classe construídos a partir do tipo genérico.

exemplo de fim

Observação: 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 Restrições de acessibilidade

Vários constructos na linguagem C# exigem que um tipo seja pelo menos tão acessível quanto um membro ou outro tipo. Um tipo T é considerado pelo menos tão acessível quanto um membro ou tipo M se o domínio de acessibilidade de T for um superconjunto do domínio de acessibilidade de M. Em outras palavras, T é pelo menos tão acessível quanto 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 quanto 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âmetro 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 um campo deve ser pelo menos tão acessível quanto o próprio campo.
  • O tipo de retorno e os tipos de parâmetro de um método devem ser pelo menos tão acessíveis quanto o próprio método.
  • O tipo de propriedade deve ser pelo menos tão acessível quanto a própria propriedade.
  • O tipo de evento deve ser pelo menos tão acessível como o próprio evento.
  • Os tipos de tipo e de parâmetro de um indexador devem ser pelo menos tão acessíveis como o próprio indexador.
  • O tipo de retorno e os tipos de parâmetros de um operador devem ser pelo menos tão acessíveis quanto o próprio operador.
  • Os tipos de parâmetro 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 em um parâmetro de tipo deve ser pelo menos tão acessível quanto o membro que declara a restrição.

Exemplo: no código a seguir

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

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

exemplo de fim

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 IN B resulta em um erro de tempo de compilação porque o tipo A de retorno não é pelo menos tão acessível quanto o método.

exemplo de fim

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 no modo de passagem de parâmetro 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âmetro, nomes de parâmetro de tipo, restrições de parâmetro de tipo, modificadores de parâmetro orthis, nem se os params parâmetros são obrigatórios ou opcionais.
  • A assinatura de um construtor de instância consiste no tipo e no modo de passagem de parâmetro 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 obrigatórios ou opcionais.
  • A assinatura de um indexador consiste no tipo de cada um de 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 inclui o modificador que pode ser especificado para o parâmetro mais à direita, nem se os params parâmetros são obrigatórios ou opcionais.
  • A assinatura de um operador consiste no nome do operador e no tipo de cada um de 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âmetro, e existe uma conversão de identidade entre os tipos de seus parâmetros correspondentes (§10.2.2).

As assinaturas são o mecanismo de habilitação para sobrecarga de membros em classes, structs 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 struct.
  • 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 inos modificadores de parâmetro , out, e ref sejam considerados parte de uma assinatura, os membros declarados em um único tipo não podem diferir na assinatura apenas por in, out, e ref. Um erro em tempo de compilação ocorrerá se dois membros forem declarados no mesmo tipo com assinaturas que seriam as mesmas se todos os parâmetros em ambos os métodos com out modificadores or in 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.

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

Os tipos object e dynamic não são distinguidos ao comparar assinaturas. Portanto, membros declarados em um único tipo cujas assinaturas diferem apenas por substituir object por dynamic não são permitidos.

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 todos os inmodificadores de parâmetro , oute ref (§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), , e F(ref int) não podem ser declarados na mesma interface porque suas assinaturas diferem apenas por in, out, e refF(out int) . 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. Dessa forma, as declarações dos métodos F(int) e F(params string[]) identificadas acima resultam em um erro em tempo de compilação. exemplo de fim

7.7 Escopos

7.7.1 Geral

O escopo de um nome é a região do texto do programa dentro da qual é possível se referir à 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. (No entanto, isso não remove 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 de uma variável local ou constante local em um bloco delimitador.) O nome do escopo externo é então dito estar 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 do namespace declarado por um namespace_member_declaration (§14.6) sem namespace_declaration delimitador é todo o texto do programa.

  • O escopo de um membro do 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 escopo de um nome definido por um extern_alias_directive (§14.4) se estende pelos using_directives, global_attributes e namespace_member_declarations de seu compilation_unit ou namespace_body imediatamente contido. Um extern_alias_directive não contribui com novos membros para o espaço de declaração subjacente. Em outras palavras, um extern_alias_directive não é transitivo, mas, ao contrário, afeta apenas o compilation_unit ou namespace_body em que ocorre.

  • O escopo de um nome definido ou importado por um using_directive (§14.5) se estende pelos 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 compilation_unit ou namespace_body específico, 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, o type_parameter_constraints_clauses e o class_body desse class_declaration.

    Observação: ao contrário dos membros de uma classe, esse escopo não se estende a classes derivadas. nota final

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

  • O escopo de um parâmetro de tipo declarado por um type_parameter_list em um 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 escopo de um parâmetro de tipo declarado por um type_parameter_list em um method_declaration (§15.6.1) é o method_declaration.

  • O escopo 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 escopo de um membro de classe se estende ao class_body das classes derivadas incluídas no domínio de acessibilidade (§7.5.3) do membro.

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

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

  • O escopo de um parâmetro declarado em um method_declaration (§15.6) é o method_body ou ref_method_body desse method_declaration.

  • O escopo de um parâmetro declarado em um indexer_declaration (§15.9) é o indexer_body desse indexer_declaration.

  • O escopo de um parâmetro declarado em um operator_declaration (§15.10) é o operator_body desse operator_declaration.

  • O escopo de um parâmetro declarado em um constructor_declaration (§15.11) é o constructor_initializer e o bloco desse constructor_declaration.

  • O escopo de um parâmetro declarado em um lambda_expression (§12.19) é o lambda_expression_body desse lambda_expression.

  • O escopo de um parâmetro declarado em um anonymous_method_expression (§12.19) é o bloco desse anonymous_method_expression.

  • O escopo de um rótulo declarado em um labeled_statement (§13.5) é o bloco no qual 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 escopo de uma variável local declarada em uma switch_block de uma switch instrução (§13.8.3) é o switch_block.

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

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

  • O escopo de uma variável declarada como parte de um foreach_statement, using_statement, lock_statement ou query_expression é determinado pela expansão do construto dado.

Dentro do escopo de um namespace, classe, struct ou membro de enumeração, é possível fazer referência 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 para F se referir a i antes de ser declarado.

exemplo de fim

Dentro do escopo de uma variável local, é um erro de 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
    }
}

F No método acima, a 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 de 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 de fim

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, no exemplo acima, a primeira atribuição seria atribuída à variável de instância e a segunda atribuição seria atribuída à variável local, possivelmente levando a erros de tempo de compilação se as instruções do bloco fossem reorganizadas posteriormente.)

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 Geral

O escopo de uma entidade normalmente abrange mais texto do programa do que o espaço de declaração da entidade. Em particular, o escopo de uma entidade pode incluir declarações que introduzem 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á oculta.

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

7.7.2.2 Ocultando através do aninhamento

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

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, oculta float i o exterior imediato i. O parâmetro i lambda oculta o float i interior do corpo lambda.

exemplo de fim

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 declarado F in Inner porque todas as ocorrências externas de F estão ocultas pela declaração interna. Pelo mesmo motivo, a chamada F("Hello") resulta em um erro de tempo de compilação.

exemplo de fim

7.7.2.3 Ocultação por herança

A ocultação de nomes por meio de herança ocorre quando classes ou structs redeclaram nomes que foram herdados de classes base. Esse tipo de ocultação de nome 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 que não são do método com o mesmo nome e todos os métodos da classe base com a mesma assinatura (§7.6).
  • Um indexador introduzido em uma 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 (§15.10) tornam impossível para uma classe derivada declarar um operador com a mesma assinatura que um operador em uma classe base. Assim, os operadores nunca se escondem.

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 um aviso seja relatado. Ocultar um nome herdado não é especificamente um erro, pois isso impediria a evolução separada das classes base. Por exemplo, a situação acima pode ter ocorrido porque uma versão posterior do introduziu Base um F método que não estava presente em uma versão anterior da classe.

exemplo de fim

O aviso causado pela ocultação de um nome herdado pode ser eliminado por meio 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 se destina a ocultar o membro herdado.

exemplo de fim

Uma declaração de um novo membro oculta um membro herdado somente dentro do escopo 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 oculta o F que foi herdado de Base, mas como o novo F in Derived tem acesso privado, seu escopo não se estende a MoreDerivedDerived . Assim, a chamada F() é MoreDerived.G válida e invocará Base.F.

exemplo de fim

7.8 Namespace e nomes de tipo

7.8.1 Geral

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 descrita abaixo, o namespace_or_type_name de um namespace_name deve se referir 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 (somente 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 descrita abaixo, o namespace_or_type_name de um type_name deve se referir a um tipo ou, caso contrário, ocorrerá um erro em tempo de compilação.

Se o namespace_or_type_name for um qualified_alias_member seu significado é o descrito em §14.8.1. Caso contrário, um namespace_or_type_name tem uma das 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 será o especificado em §14.8.1.
  • Caso contrário, se a 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 em 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 name I, o namespace_or_type_name se refere a esse parâmetro de tipo.
    • Caso contrário, se o namespace_or_type_name aparecer em uma declaração de tipo, 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 delimitadora ou declaração de struct (se houver):
      • Se x for zero e a declaração de incluir um parâmetro de T tipo com name I, o namespace_or_type_name se refere a esse parâmetro de tipo.
      • Caso contrário, se o namespace_or_type_name aparecer no corpo da declaração de tipo e T /ou qualquer um de seus tipos base contiver um tipo acessível aninhado com parâmetros name I e x type, o namespace_or_type_name se referirá a esse tipo construído com os argumentos de tipo fornecidos. Se houver mais de um tipo desse tipo, o tipo declarado dentro do tipo mais derivado será selecionado.

      Observação: membros que não são de 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 delimitador (se houver) e terminando com o namespace global, as seguintes etapas serão avaliadas até que uma entidade seja localizada:
      • Se x for zero e I for o nome de um namespace em N, então:
        • Se o local em que o namespace_or_type_name ocorre for delimitado por uma declaração de namespace for 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, a namespace_or_type_name será ambígua e ocorrerá um erro em tempo de compilação.
        • Caso contrário, o namespace_or_type_name se refere 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 em que a namespace_or_type_name ocorre for delimitado por uma declaração de namespace for N e a declaração de namespace contiver um extern_alias_directive ou using_alias_directive que associa o nome I a um namespace ou tipo, a namespace_or_type_name será ambígua 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 ocorre a namespace_or_type_name estiver entre 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 referirá 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 referirá 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 será indefinido e ocorrerá um erro em tempo de compilação.
  • Caso contrário, o namespace_or_type_name é da forma N.I ou da forma N.I<A₁, ..., Aₓ>. N é resolvido pela primeira vez como um namespace_or_type_name. Se a resolução de não for bem-sucedida, ocorrerá um erro em N tempo de compilação. Caso contrário, N.I ou N.I<A₁, ..., Aₓ> é resolvido da seguinte maneira:
    • Se x for zero e N se referir a um namespace e N contiver um namespace aninhado com name I, o namespace_or_type_name se referirá a esse namespace aninhado.
    • Caso contrário, if N se refere a um namespace e N contém um tipo acessível 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 N se referir a uma classe (possivelmente construída) ou tipo de struct e N /ou qualquer uma de suas classes 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 tipo desse tipo, o tipo declarado dentro do tipo mais derivado será selecionado.

      Nota: Se o significado de N.I estiver sendo determinado como parte da resolução da especificação da classe base de N , então a classe base direta de N será considerada object (§15.2.4.2). nota final

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

Um namespace_or_type_name tem permissão para fazer referência a uma classe estática (§15.2.2.4) somente se

  • O namespace_or_type_name é o T em um namespace_or_type_name da forma T.I, ou
  • O namespace_or_type_name é o T em um typeof_expression (§12.8.17) do formulário 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 especificado na declaração, seguido pelo generic_dimension_specifier (§12.8.17) 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 a 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 será 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 se referir a duas entidades distintas. Especialmente:

  • É 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 de struct e uma declaração de classe tiverem o mesmo nome totalmente qualificado).
  • É um erro para uma declaração de tipo sem o modificador parcial ter o mesmo nome totalmente qualificado que outra declaração de tipo (§15.2.7).

Exemplo: o exemplo a seguir 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 de fim

7.9 Gerenciamento automático de memória

O C# emprega o gerenciamento automático de memória, o 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 ativo.
  2. Se nem o objeto nem qualquer um de seus campos de instância puderem ser acessados por qualquer continuação possível da execução, que não seja a execução de finalizadores, o objeto será considerado não mais em uso e se tornará elegível para finalização.

    Observação: 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 é referenciada em qualquer continuação possível 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. Depois que o finalizador de um objeto é executado, se nem o objeto nem qualquer um de seus campos de instância puderem ser acessados por qualquer continuação possível da execução, incluindo a execução de finalizadores, o objeto será considerado inacessível e o objeto se tornará elegível para coleta.

    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 localizar um objeto recém-criado na memória, 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 variedade de políticas de gerenciamento de memória. O C# não especifica uma restrição de tempo dentro desse intervalo, 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, por meio 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 daquela 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. Esses objetos se tornam elegíveis para coleta de lixo quando a variável b recebe o valor null, pois após esse tempo é impossível para qualquer código escrito pelo usuário acessá-los. A saída pode ser

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 na ordem em que os objetos são coletados como lixo.

Em casos sutis, a distinção entre "elegível para finalização" e "elegível para coleta" 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 A antes do finalizador de B, a saída desse programa poderá 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 estivesse em uso e Ao finalizador do tenha sido 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. Nesse caso, a execução do Bfinalizador do fez com que uma instância que A não estava em uso anteriormente se tornasse acessível a partir da 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 de fim

7.10 Ordem de execução

A execução de um programa C# prossegue de modo que os efeitos colaterais de cada thread em execução sejam 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 nos quais a ordem desses efeitos colaterais deve ser preservada são referências a campos voláteis (§15.5.4), lock instruções (§13.13) e criação e encerramento 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 em um thread de execução. Ou seja, o valor de cada variável é calculado como se todas as instruções no thread tivessem sido executadas na ordem original do programa.
  • As regras de ordenação de inicialização são preservadas (§15.5.5, §15.5.6).
  • A ordem dos efeitos colaterais é preservada em relação 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.