Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
7.1 Inicialização e encerramento do aplicativo
7.1.1 Geral
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.
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.
Uma execução de aplicativo é iniciada invocando um ponto de entrada que é selecionado em tempo de compilação de pontos de entrada candidatos especificados de qualquer uma das seguintes maneiras:
- Explicitamente, declarando um método com características apropriadas (§7.1.2).
- Implicitamente, usando instruções de nível superior (§7.1.3).
- Definido pela implementação, usando um mecanismo externo a essa especificação para designar o ponto de entrada (§7.1.4).
Observação: com exceção dos candidatos de instrução de nível superior (§7.1.3) todos os pontos de entrada candidatos são métodos padrão; ser um candidato ou o ponto de entrada selecionado não impede que esses métodos sejam invocados normalmente. nota final
É um erro de tempo de compilação se não houver pontos de entrada candidatos.
Caso contrário, um candidato será selecionado em tempo de compilação como o ponto de entrada após o processo em §7.1.5.
Um aplicativo é executado invocando o ponto de entrada selecionado, conforme descrito em §7.1.6.
O código de status de terminação, que tem como finalidade permitir a comunicação de êxito ou falha com o ambiente do host, é determinado com base no resultado da invocação do ponto de entrada (§7.1.6).
7.1.2 Pontos de entrada nomeados
Um método se qualifica como um ponto de entrada de canditate atendendo aos 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.
- O tipo de retorno deve ser
void,int,System.Threading.Tasks.Task, ouSystem.Threading.Tasks.Task<int>. - Ele só poderá ter o
asyncmodificador se o tipo de retorno do método forSystem.Threading.Tasks.TaskouSystem.Threading.Tasks.Task<int>. - Não deve ser um método parcial opcional (§15.6.9) sem uma declaração de implementação.
- A lista de parâmetros deve estar vazia ou ter um único parâmetro de valor do tipo
string[].
A acessibilidade declarada (§7.4.2) de um método é ignorada para fins de qualificação como um ponto de entrada candidato. Na inicialização do aplicativo, o ponto de entrada selecionado é invocado independentemente de sua acessibilidade declarada. No entanto, qualquer acessibilidade declarada continuará a ser aplicada se o ponto de entrada do candidato selecionado for invocado após a inicialização do aplicativo.
Observação: os requisitos aqui não especificam que
Maindeve ser um membro de classe, ele pode ser um membro struct. nota final
7.1.3 Usando instruções de nível superior
Uma única unidade de compilação (§14.2 em um aplicativo pode conter um statement_list; chamado de instruções de nível superior. O significado das instruções de nível superior é semanticamente equivalente a declarar o seguinte no namespace global:
partial class Program
{
static «AsyncAndReturnType» «Main»(string[] args)
{
«statement_list»
}
}
A classe Program é uma declaração de tipo parcial (§15.2.7) que é combinada com quaisquer outras declarações de classe parciais para Program dentro do aplicativo.
Observação: isso significa que uma instrução de nível superior tem os mesmos direitos de acesso que uma instrução no corpo de um método estático definido na classe
Programno namespace global. nota final
O nome «Main» do método é um espaço reservado para um nome fornecido pela implementação não especificada, que é diretamente acessível apenas para o ambiente de execução e não pode ser referenciado diretamente de dentro do aplicativo. Além disso, não há nenhum requisito de que o nome usado seja válido como um nome de método C#.
Observação: no entanto, uma representação do nome usado poderá ser obtida como um
stringdurante a execução de uma instrução de nível superior se ela invocar um método que usa oCallerMemberNameatributo (§23.5.6.4). nota final
É «statement_list» um espaço reservado para statement_list da unidade de compilação.
O parâmetro args está no escopo dentro das instruções de nível superior e não de outra forma. As regras de conflito/sombreamento de nome regular se aplicam. Esse parâmetro recebe os parâmetros do aplicativo (§7.1.6).
As operações assíncronas são permitidas em instruções de nível superior até o grau em que são permitidas em instruções dentro de um método de ponto de entrada assíncrono nomeado.
A substituição do «AsyncAndReturnType» espaço reservado na assinatura do método é determinada com base no conteúdo das instruções de nível superior, da seguinte maneira:
| As instruções de nível superior contêm | Assinatura gerada |
|---|---|
Não await ou return com valor |
static void «Main»(string[] args) |
return somente com valor |
static int «Main»(string[] args) |
await somente |
static async Task «Main»(string[] args) |
await e return com valor |
static async Task<int> «Main»(string[] args) |
Essas assinaturas geradas atendem aos requisitos de pontos de entrada nomeados (§7.1.2) e, portanto, «Main» são um ponto de entrada candidato.
Observação: essas assinaturas não incluem acessibilidade declarada (§7.4.2). No entanto, uma implementação pode incluir acessibilidade declarada, pois a acessibilidade é ignorada para métodos de ponto de entrada (§7.1.2) o método ainda seria semanticamente equivalente.
7.1.4 Ponto de entrada definido externamente
Uma implementação pode fornecer um mecanismo externo a essa especificação para especificar um ponto de entrada candidato.
Se esse mecanismo identificar um tipo dentro do programa, o ponto de entrada do candidato será determinado como especificado para pontos de entrada nomeados (§7.1.2).
Se esse mecanismo identificar um método dentro do programa, o método poderá ter qualquer nome, mas, caso contrário, deverá atender aos requisitos especificados para pontos de entrada nomeados (§7.1.2).
O mecanismo externo pode especificar apenas um único ponto de entrada candidato.
Um ponto de entrada candidato especificado por um mecanismo externo tem precedência na seleção de ponto de entrada (§7.1.5).
7.1.5 Seleção de ponto de entrada
É um erro de tempo de compilação se não houver pontos de entrada candidatos.
Caso contrário, o ponto de entrada será selecionado entre os candidatos:
- Se um dos candidatos for especificado por um mecanismo externo (§7.1.4), ele será selecionado como o ponto de entrada;
- Caso contrário, se um dos candidatos for definido por instruções de nível superior (§7.1.3), ele será selecionado como o ponto de entrada;
- Caso contrário:
- Se algum dos candidatos tiver um tipo de retorno ou
intvoid, em seguida, qualquer candidato tiver um tipo de retorno ouSystem.Threading.Tasks.Task<int>System.Threading.Tasks.Taskfor removido do pool de candidatos. - Se houver um único candidato restante, ele será selecionado como o ponto de entrada.
- Caso contrário, o ponto de entrada não poderá ser determinado e um erro de tempo de compilação será relatado.
- Se algum dos candidatos tiver um tipo de retorno ou
O ponto de entrada selecionado no tempo de compilação é invocado no runtime como parte da inicialização do aplicativo (§7.1.6).
7.1.6 Invocação de ponto de entrada
Se o ponto de entrada declarar um parâmetro, a implementação deverá, como o valor inicial desse parâmetro, fornecer uma referência não nula a uma matriz de cadeia de caracteres. Essa matriz deve consistir em referências não nulas a zero ou mais 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.
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
O processo de inicialização e encerramento do aplicativo é semanticamente equivalente às seguintes etapas:
- Uma execução de aplicativo é iniciada por:
- Invocando (§12.8.10) o método de ponto de entrada, se o tipo de retorno for
voidouint; ou - Aguardando (§12.9.9) o resultado da invocação do método de ponto de entrada, se o tipo de retorno for um
Tasktipo. - Em ambos os casos, se o ponto de entrada exigir um argumento, a matriz de parâmetros do aplicativo será fornecida como seu valor.
- Invocando (§12.8.10) o método de ponto de entrada, se o tipo de retorno for
Observação: invocar o método de ponto de entrada fará com que o construtor estático, se houver, do tipo delimitador seja executado primeiro (§15.12, §16.8.10). nota final
- O aplicativo é encerrado
- Se a execução resultar em um
intvalor, ela servirá como o código de status de encerramento; - Caso contrário, se a execução resultar em nenhum valor retornado, o código de status de terminação será
0; - Caso contrário, se a execução for encerrada devido a uma exceção (§22.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 a execução resultar em um
Se os finalizadores (§15.13) são ou não executados como parte do encerramento do aplicativo é definido pela implementação.
Note: 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, a menos que essa limpeza tenha sido suprimida (por uma chamada para o método de biblioteca
GC.SuppressFinalize, por exemplo). nota final
7.2 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.8) são usadas para definir classes (§15), structs (§16), interfaces (§19), enumerações (§20) e delegados (§21). 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,5).
- Duas ou mais declarações de tipo com o mesmo nome, mas números distintos de parâmetros de tipo são permitidos no mesmo espaço de declaração (§7.7.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.7.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. Por §14.3, isso inclui file_scoped_namespace_declarations.
- 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.6.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 por declarações de construtor de instância sobrecarregadas e declarações de construtor estático, uma classe, struct ou interface não pode conter uma declaração de membro com o mesmo nome da classe, struct ou interface. 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 diferem em sua assinatura (§7,5). 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_collections) 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_collections) e type_parameters. O acessador set e init para uma propriedade ou um indexador introduz o nome
valuecomo 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 fica oculto (§7.6.1) pelo nome local aninhado.Observação: descartar parâmetros de funções anônimas (§12.22.2) não introduz nomes em nenhum espaço de declaração. nota final
- 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 sintactica de um query_expression (§12.23.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
ylocal 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ávelzlocal 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 usadas (§7,6).
- A ordem de declaração para declarações de membro de enumeração (§20.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.Customertotalmente qualificados eMegacorp.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
Fmétodos andGresultam em um erro de tempo de compilação porque o nomeié declarado no bloco externo e não pode ser redeclarado no bloco interno. No entanto, osHmétodos andIsão válidos, pois os doisisã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.3 Membros
7.3.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 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.
Observação: no entanto, um membro herdado pode não estar acessível em um tipo derivado, por exemplo, devido à sua acessibilidade declarada (§7.4.2). nota final
7.3.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.3.3 Membros do 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.3.4 Membros de 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.3.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.3.6 Membros da interface
Os membros de uma interface são os membros declarados na interface e em todas as interfaces base da interface.
Observação: os membros da classe
objectnão são, estritamente falando, membros de qualquer interface (§19.4). No entanto, os membros da classeobjectestão disponíveis por meio da pesquisa de membros em qualquer tipo de interface (§12.5). nota final
7.3.7 Membros da matriz
Os membros de uma matriz são os membros herdados da classe System.Array.
7.3.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 (§21.2). Uma invocação desse método deve se comportar de forma idêntica a uma invocação delegada (§21.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.4 Acesso de membro
7.4.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.4.2) do membro combinado com a acessibilidade do tipo que 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 é incluído no domínio de acessibilidade (§7.4.3) do membro.
7.4.2 Acesso declarado
A acessibilidade declarada de um membro pode ser uma das seguintes:
- Public, que é selecionado incluindo um
publicmodificador na declaração de membro. O significado intuitivo depublicé "acesso não limitado". - Protected, que é selecionado incluindo um
protectedmodificador na declaração de membro. O significadoprotectedintuitivo é "acesso limitado à classe ou interface que contém, ou classes ou interfaces derivadas do tipo que contém". - Internal, que é selecionado incluindo um
internalmodificador na declaração de membro. O significado intuitivo deinternalé "acesso limitado a esta assembleia". - Interno protegido, que é selecionado incluindo um modificador e
protecteduminternalmodificador na declaração de membro. O significado intuitivo deprotected internalé "acessível dentro deste assembly, bem como tipos derivados da classe que o contém". - Private protected, que é selecionado incluindo um modificador e
privateumprotectedmodificador na declaração de membro. O significado intuitivo deprivate protectedé "acessível dentro deste assembly pela classe que contém e tipos derivados da classe que contém". - Private, que é selecionado incluindo um
privatemodificador na declaração de membro. O significado intuitivo deprivateé "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
publicimplicitamente 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
publicouinternaldeclarado acessibilidade e padrão parainternalacessibilidade declarada. - Os membros da classe podem ter qualquer um dos tipos permitidos de acessibilidade declarada e padrão para
privateacessibilidade 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
publicouinternaldeclarada. nota final - Os membros do struct podem ter
public,internal, ouprivateacessibilidade declarada e o padrão é aprivateacessibilidade declarada porque os structs são selados implicitamente. Os membros struct introduzidos em umstruct(ou seja, não herdados por esse struct) não podem terprotected,protected internal, ouprivate protectedacessibilidade declarada.Observação: um tipo declarado como membro de um struct pode ter
public,internal, ouprivateacessibilidade declarada, enquanto um tipo declarado como membro de um namespace pode ter acessibilidade apenaspublicouinternaldeclarada. nota final - Os membros da interface declararam
publicimplicitamente acessibilidade. - Os membros da enumeração declararam
publicimplicitamente a acessibilidade. Nenhum modificador de acesso é permitido em declarações de membro de enumeração.
7.4.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
Tfor pública, o domínio de acessibilidade deTserá o texto do programa ePqualquer programa que faça referência aP. - Se a acessibilidade declarada de
Tfor interna, o domínio de acessibilidade deTserá o texto do programa deP.
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
Mforpublic, o domínio de acessibilidade deMserá o domínio de acessibilidade deT. - Se a acessibilidade declarada de
Mfor , sejaprotected internala união do texto do programa deDe do texto do programa de qualquer tipo derivado de , que é declarado foraPdeTP. O domínio de acessibilidade deMé a interseção do domínio de acessibilidade deTcomD. - Se a acessibilidade declarada de
Mfor , sejaprivate protecteda interseção do texto do programa deDe do texto do programa dePe qualquer tipo derivado deTT. O domínio de acessibilidade deMé a interseção do domínio de acessibilidade deTcomD. - Se a acessibilidade declarada de
Mfor , sejaprotecteda união do texto do programa deDe do texto do programa de qualquer tipo derivado deTT. O domínio de acessibilidade deMé a interseção do domínio de acessibilidade deTcomD. - Se a acessibilidade declarada de
Mforinternal, o domínio de acessibilidade deMserá a interseção do domínio de acessibilidade deTcom o texto de programa deP. - Se a acessibilidade declarada de
Mforprivate, o domínio de acessibilidade deMserá o texto de programa deT.
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
Mfor 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
Mforpublic, o acesso é permitido.- Caso contrário, se
Mforprotected internal, o acesso será permitido se ocorrer dentro do programa no qualMé declarado ou se ocorrer dentro de uma classe derivada da classe na qualMé declarado e ocorre por meio do tipo de classe derivada (§7.4.4).- Caso contrário, se
Mforprotected, o acesso será permitido se ocorrer dentro da classe na qualMé declarado ou se ocorrer dentro de uma classe derivada da classe na qualMé declarado e ocorre por meio do tipo de classe derivada (§7.4.4).- Caso contrário, se
Mforinternal, o acesso será permitido se ocorrer dentro do programa no qualMé declarado.- Caso contrário, se
Mforprivate, o acesso será permitido se ocorrer dentro do tipo no qualMé 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
AeA.Xé ilimitado.- O domínio de acessibilidade de
A.Y,B, ,B.XB.Y,B.C,B.C.X, eB.C.Yé o texto do programa que o contém.- O domínio de acessibilidade de
A.Zé o texto do programa deA.- O domínio de acessibilidade de
B.ZeB.Dé o texto do programa deB, incluindo o texto do programa deB.CeB.D.- O domínio de acessibilidade de
B.C.Zé o texto do programa deB.C.- O domínio de acessibilidade de
B.D.XeB.D.Yé o texto do programa deB, incluindo o texto do programa deB.CeB.D.- O domínio de acessibilidade de
B.D.Zé o texto do programa deB.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 osXmembros tenham acessibilidade pública declarada, todosA.Xtêm domínios de acessibilidade restritos por um tipo de contenção.exemplo de fim
Conforme descrito em §7.3, 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 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
Bclasse herda o membroxprivado daAclasse. Como o membro é privado, ele só pode ser acessado dentro do class_body doA. Assim, o acesso ab.xé bem-sucedido noA.Fmétodo, mas falha noB.Fmétodo.exemplo de fim
7.4.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. Membros da interface de instância definidos com protected ou private protected acesso não podem ser acessados de um class ou struct que implementa essa interface; eles só podem ser acessados de interfaces derivadas. No entanto, class e struct os tipos podem implementar protected membros de instância declarados em uma interface que implementam.
Seja B uma classe base que declara um membro Mde instância protegida e seja D uma classe derivada de B. No class_body (ou record_class_body) de D, o acesso M pode usar um dos seguintes formulários:
- Um type_name ou primary_expression não qualificado
- Um .
- 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
Ade , é possível acessarxatravés de instâncias de ambosAeB, uma vez que em ambos os casos o acesso ocorre através de uma instância deAou uma classe derivada deA. No entanto, dentroBde , não é possível acessarxatravés de uma instância deA, uma vez queAnão deriva deB.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
xsã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.4.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
protectedmemberC<int>.xinDé válida mesmo que a classeDderive deC<string>. nota final
7.4.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
Bclasse resulta em um erro de tempo de compilação porqueAnão é pelo menos tão acessível quantoB.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
Hmétodo INBresulta em um erro de tempo de compilação porque o tipoAde retorno não é pelo menos tão acessível quanto o método.exemplo de fim
7.5 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
paramsdescopedparâmetro outhis, nem se os 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â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
paramsmodificador 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 ou o
scopedmodificador, nem inclui oparamsmodificador que pode ser especificado para o parâmetro mais à direita, ou oscopedmodificador, nem se os parâmetros são necessá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 ou o
scopedmodificador. - 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 nem é o
scopedmodificador. - 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 parameter_mode_modifiersejam consideradas parte de uma assinatura, os membros declarados em um único tipo não podem diferir na assinatura apenas por esses modificadores. 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 outras finalidades de correspondência de assinatura (por exemplo, ocultar ou substituir), parameter_mode_modifiersão consideradas parte da assinatura e não correspondem entre si.
Observação: essa restrição é permitir que os programas C# sejam facilmente convertidos para serem executados em uma plataforma que não forneça uma maneira de definir métodos que diferem apenas em seus parameter_mode_modifier. 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(ref readonly int x); // F(ref int) error 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 parameter_mode_modifier(§15.6.2) faz parte de uma assinatura. Assim,
F(int),F(in int), ,F(out int)eF(ref int)F(ref readonly int)são todas assinaturas exclusivas. No entanto,F(in int)eF(ref readonly int)F(out int)F(ref int)não pode ser declarado dentro da mesma interface porque suas assinaturas diferem apenas por suas parameter_mode_modifier. Além disso, observe que o tipo de retorno e oparamsmodificador 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 doparamsmodificador. Dessa forma, as declarações dos métodosF(int)eF(params string[])identificadas acima resultam em um erro em tempo de compilação. exemplo de fim
7.6 Escopos
7.6.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 por §7.2 que, em 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 delimitado.) O nome do escopo externo é então dito estar oculto na região do texto do programa coberto pelo escopo interno, e o acesso ao nome externo só é possível qualificando o nome.
Observação: talvez não seja possível acessar o nome externo oculto do escopo interno porque não há como qualificá-lo, como com parâmetros de tipo em declarações de tipo aninhadas. nota final
- O escopo de um membro de namespace declarado por um namespace_member_declaration (§14.7) sem namespace_declaration de entressos é todo o texto do programa.
- O escopo de um membro de namespace declarado por um compilation_unit_body cujo nome totalmente qualificado é
N, é cada compilation_unit_body cujo nome totalmente qualificado éNou começa comN, seguido por um período. - 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 éNou começa comN, seguido por um ponto. Por §14,3, os file_scoped_namespace_declarationsão tratados como namespace_declarationequivalentes para essa regra. - O escopo de um nome definido por um extern_alias_directive (§14.4) estende-se pelos global_using_directives, using_directive, global_attributes e compilation_unit_body de seus compilation_unit ou namespace_body imediatamente contidos. 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 afeta apenas a compilation_unit, namespace_body e file_scoped_namespace_declaration em que ela ocorre.
- O escopo de um nome definido ou importado por um global_using_directive se estende pela global_attributes e compilation_unit_body de todos os compilation_unitdo programa.
- O escopo de um nome definido ou importado por um using_directive (§14.6) se estende pelos global_attributes, statement_liste namespace_member_declarationdo 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, type_parameter_constraints_clausee class_body (ou record_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_clausee struct_body (ou record_struct_body) desse struct_declaration.
- O escopo de um parâmetro de tipo declarado por um type_parameter_list em um interface_declaration (§19.2) é o interface_base, type_parameter_constraints_clausee interface_body desse interface_declaration.
- O escopo de um parâmetro de tipo declarado por um type_parameter_list em um delegate_declaration (§21.2) é o return_type, parameter_list e type_parameter_constraints_clausedessa 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 e
nameofexpressões em um atributo no method_declaration ou em seus parâmetros. - O escopo de um membro declarado por um class_member_declaration (§15.3.1) é o class_body (ou record_class_body) em que a declaração ocorre. Além disso, o escopo de um membro de classe se estende ao class_body (ou record_class_body) dessas classes derivadas incluídas no domínio de acessibilidade (§7.4.3) do membro.
- O escopo de um membro declarado por um struct_member_declaration (§16.3) é o struct_body (ou record_struct_body) em que a declaração ocorre.
- O escopo de um membro declarado por um enum_member_declaration (§20.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 e
nameofexpressões em um atributo na declaração do método ou em seus parâmetros. - 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.
- Com exceção dos parâmetros de descarte (§12.22.2), o escopo de um parâmetro declarado em um lambda_expression (§12.22) é o lambda_expression_body desse lambda_expression e
nameofexpressões em um atributo na função anônima ou em seus parâmetros. - Com exceção dos parâmetros de descarte (§12.22.2), o escopo de um parâmetro declarado em um anonymous_method_expression (§12.22) é o bloco desse anonymous_method_expression e
nameofexpressões em um atributo na função anônima ou em seus parâmetros. - 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
switchinstrução (§13.8.3) é o switch_block. - O escopo de uma variável local declarada em uma for_initializer de uma instrução (
for) é a for_initializer, a for_condition, a for_iterator e a embedded_statement da instrução.for - 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
Fse referir aiantes 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 } }
FNo método acima, a primeira atribuição aiespecificamente 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.GNo método, o uso dejno inicializador para a declaração dejé válido porque o uso não precede o declarador.HNo 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ávelAlocal e em um contexto de tipo para se referir à classeA.nota final
Conforme descrito em §7.1.3, os tokens de origem de nível superior são colocados pelo método de ponto de entrada gerado.
Para fins de avaliação de nome simples, depois que o namespace global é atingido, primeiro, é feita uma tentativa de avaliar o nome dentro do método de ponto de entrada gerado e somente se essa tentativa falhar é a avaliação dentro da declaração de namespace global executada.
Isso pode levar ao sombreamento de nomes de namespaces e tipos declarados dentro do namespace global, bem como à sombreamento de nomes importados.
Se a avaliação de nome simples ocorrer fora das instruções de nível superior e a avaliação produzir uma variável ou função local de nível superior, um erro de tempo de compilação resultará.
7.6.2 Ocultação de nome
7.6.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.6.2.2 Ocultando-se 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
Fmétodo, a variávelide instância é ocultada pela variávelilocal , mas dentro doGmétodo,iainda se refere à variável de instância. Dentro da funçãoM1local, ocultafloat io exterior imediatoi. O parâmetroilambda oculta ofloat iinterior 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 declaradoFinInnerporque todas as ocorrências externas deFestão ocultas pela declaração interna. Pelo mesmo motivo, a chamadaF("Hello")resulta em um erro de tempo de compilação.exemplo de fim
7.6.2.3 Ocultando-se por meio da 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, struct ou interface oculta todos os membros da classe base com o mesmo nome.
- Um método introduzido em uma classe, struct ou interface 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.5).
- Um indexador introduzido em uma classe, struct ou interface oculta todos os indexadores de tipo base com a mesma assinatura (§7,5) .
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
FINDerivedfaz 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 deBaseumFmé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
newmodificador indica que oFinDerivedé "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
Fin oculta oDerivedque foi herdado deF, mas como o novoBaseinFtem acesso privado, seu escopo não se estende aDerivedMoreDerived. Assim, a chamadaF()éMoreDerived.Gválida e invocaráBase.F.exemplo de fim
7.7 Namespace e nomes de tipo
7.7.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? ('.' identifier type_argument_list?)*
| qualified_alias_member ('.' identifier type_argument_list?)*
;
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.
Um namespace_or_type_name refere-se a um tipo ou namespace. A resolução para um namespace ou tipo específico envolve duas etapas com base na divisão da gramática em uma parte principal, sendo um dos fragmentos gramaticais:
identifier type_argument_list?qualified_alias_member
e uma parte final, sendo o fragmento gramatical:
('.' identifier type_argument_list?)*
Primeiro, a parte principal é resolvida para determinar R₀, o namespace ou tipo inicial.
Se a parte principal do namespace_or_type_name for um qualified_alias_member, será R₀ o namespace ou o tipo identificado resolvendo-o conforme descrito em §14.9.1.
Caso contrário, a parte principal, sendo o identificador de fragmento gramatical type_argument_list?, terá uma das formas:
II<A₁, ..., Aₓ>
onde:
-
Ié um único identificador; e -
<A₁, ..., Aₓ>é um type_argument_list, quando nenhuma type_argument_list é especificada, considerexser zero.
R₀ é determinado da seguinte maneira:
- Se
xfor zero e 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 o nomeI, entãoR₀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, para cada tipo
Tde 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, struct ou declaração de interface (se houver):- Se
xfor zero e a declaração deTincluir um parâmetro de tipo com o nomeI, entãoR₀refere-se a esse parâmetro de tipo. - Caso contrário, se o namespace_or_type_name aparecer dentro do corpo da declaração de tipo, e
Tou qualquer um de seus tipos base contiver um tipo acessível aninhado com parâmetros de nomeIe de tipox, entãoR₀refere-se 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. Será um erro do compilador se nenhum tipo for mais derivado do que todos os outros.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
xfor zero eIfor o nome de um namespace emN, então:- Se o local em que o namespace_or_type_name ocorre estiver entre uma declaração
Nde namespace e a declaração de namespace contiver um extern_alias_directive ou using_alias_directive que associe o nomeIa um namespace ou tipo, ou qualquer declaração de namespace paraNo programa contém um global_using_alias_directive que associa o nomeIa um namespace ou tipo, em seguida, o namespace_or_type_name é ambíguo e ocorre um erro de tempo de compilação. - Caso contrário,
R₀refere-se ao namespace nomeadoIemN.
- Se o local em que o namespace_or_type_name ocorre estiver entre uma declaração
- Caso contrário, se
Ncontiver um tipo acessível com parâmetros nameIextype, então:- Se
xfor zero e o local em que o namespace_or_type_name ocorrer estiver entre uma declaraçãoNde namespace e a declaração de namespace contiver um extern_alias_directive ou using_alias_directive que associe o nomeIa um namespace ou tipo, ou qualquer declaração de namespace paraNo programa contém um global_using_alias_directive que associa o nomeIa um namespace ou tipo, em seguida, o namespace_or_type_name é ambíguo e ocorre um erro de tempo de compilação. - Caso contrário,
R₀refere-se ao tipo construído com os argumentos de tipo fornecidos.
- Se
- Caso contrário, se o local onde ocorre a namespace_or_type_name estiver entre uma declaração de namespace para
N:- Se
xfor zero e a declaração de namespace contiver um extern_alias_directive ou using_alias_directive que associe o nomeIa um namespace ou tipo importado, ou qualquer declaração de namespace paraNo programa contém um global_using_alias_directive que associa o nomeIa um namespace ou tipo importado e, em seguidaR₀, refere-se a esse namespace ou tipo. - Caso contrário, se os namespaces importados pelos using_namespace_directiveda declaração de namespace e os namespaces e declarações de tipo importados pelos global_using_namespace_directivee global_using_static_directivede qualquer declaração de namespace para
No programa contiverem exatamente um tipo com parâmetros de nomeIextipo, consulteR₀esse tipo construído com os argumentos de tipo fornecidos. - Caso contrário, se os namespaces importados pelos using_namespace_directiveda declaração de namespace e os namespaces e declarações de tipo importados pelos global_using_namespace_directivee global_using_static_directivede qualquer declaração de namespace para
No programa contiverem mais de um tipo com parâmetros de nomeIextipo, o namespace_or_type_name será ambíguo e ocorrerá um erro de tempo de compilação.
- Se
- Se
- Caso contrário, o namespace_or_type_name será indefinido e ocorrerá um erro em tempo de compilação.
- Se
Se R₀ tiver sido resolvida com êxito, a parte à direita do namespace_or_type_name será resolvida. O fragmento final de gramática consiste em k ≥ 0 repetições, com cada repetição aperfeiçoando ainda mais a resolução do namespace ou tipo referenciado.
Se k é zero, ou seja, não há nenhuma parte à direita, então o namespace_or_type_name é resolvido para R₀.
Caso contrário, cada repetição terá uma das formas:
.I.I<A₁, ..., Aₓ>
onde I, A e x são definidos como acima.
Para cada repetição n, onde 1 ≤ n ≤ k, sua resolução, Rₙ, que envolve Rₚ, onde p = n - 1, a resolução da repetição anterior, é determinada da seguinte maneira:
- Se
xfor igual a zero eRₚse referir a um namespace eRₚcontiver um namespace aninhado com nomeI, entãoRₙrefere-se a esse namespace aninhado. - Caso contrário, se
Rₚfizer referência a um namespace eRₚcontiver um tipo acessível com o nomeIe os parâmetros do tipox, entãoRₙrefere-se a esse tipo construído com os argumentos de tipo fornecidos. - Caso contrário, se
Rₚfizer referência a uma classe (possivelmente construída), struct ou tipo de interface eRₚou qualquer um de seus tipos base conter um tipo acessível aninhado com parâmetros de nomeIextipo, consulteRₙ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. Será um erro do compilador se nenhum tipo for mais derivado do que todos os outros.Observação: se o significado de
T.I, para algum tipoT, estiver sendo determinado como parte da resolução da especificação de classe base deT, então a classe base direta deTé considerada comoobject(§15.2.4.2). nota final - Caso contrário, o namespace_or_type_name será inválido e ocorrerá um erro de tempo de compilação.
A resolução do namespace_or_type_name é a resolução da repetição final, Rₖ.
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
Tem um namespace_or_type_name da formaT.I, ou - O namespace_or_type_name está
Tem um typeof_expression (§12.8.18) do formuláriotypeof(T)
Exemplo:
interface A { class NestedClass { public static void M() {} } } interface B { class NestedClass { public static void M() {} } } interface C : A, B { public void Test() { NestedClass.M(); } // ambiguity between // A.NestedClass and B.NestedClass }No exemplo acima, a chamada para entrar
NestedClass.M()é ambíguaC.Test()entreB.NestedClass.M()eA.NestedClass.M()porque nenhum deles é mais derivado do que o outro. Uma referência explícita a ouA.NestedClass.M()B.NestedClass.M()é necessária para resolver a ambiguidade.exemplo de fim
7.7.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.18) para parâmetros de tipo K.
7.7.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
Nfor um membro do namespace global, seu nome totalmente qualificado seráN. - Caso contrário, seu nome totalmente qualificado será
S.N, ondeSé o nome totalmente qualificado do namespace ou declaração de tipo na qualNé 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.8 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:
- Quando o objeto é criado, a memória é alocada para ele, o construtor é executado e o objeto é considerado ativo.
- 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
- 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.
- 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
- 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 executados ou não como parte da terminação do aplicativo é definido pela implementação (§7.1).
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
Ae uma instância de classeB. Esses objetos se tornam elegíveis para coleta de lixo quando a variávelbrecebe o valornull, pois após esse tempo é impossível para qualquer código escrito pelo usuário acessá-los. A saída pode serFinalize instance of A Finalize instance of Bou
Finalize instance of B Finalize instance of Aporque 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
Aantes do finalizador deB, a saída desse programa poderá ser:Finalize instance of A Finalize instance of B A.F RefA is not nullObserve que, embora a instância de
Anão estivesse em uso eAo finalizador do tenha sido executado, ainda é possível que os métodos deA(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 doBfinalizador do fez com que uma instância queAnão estava em uso anteriormente se tornasse acessível a partir da referênciaTest.RefAao vivo . Após a chamada paraWaitForPendingFinalizers, a instância deBé elegível para coleta, mas a instância deAnão é, devido à referênciaTest.RefA.exemplo de fim
7.9 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.
ECMA C# draft specification