7 Conceitos básicos
7.1 Inicialização do aplicativo
Um programa pode ser compilado como uma biblioteca de classes para ser usado como parte de outros aplicativos ou como um aplicativo que pode ser iniciado diretamente. O mecanismo para determinar este modo de compilação é definido pela implementação e externo a esta especificação.
Um programa compilado como uma aplicação deve conter pelo menos um método que possa ser qualificado como ponto de entrada, satisfazendo os seguintes requisitos:
- Deve ter o nome
Main
. - Deve ser
static
. - Não deve ser genérico.
- Deve ser declarada num tipo não genérico. Se o tipo que declara o método for um tipo aninhado, nenhum dos tipos que o encerram pode ser genérico.
- Ele pode ter o
async
modificador desde que o tipo de retorno do método sejaSystem.Threading.Tasks.Task
ouSystem.Threading.Tasks.Task<int>
. - O tipo de retorno deve ser
void
,int
,System.Threading.Tasks.Task
, ouSystem.Threading.Tasks.Task<int>
. - Não deve ser um método parcial (§15.6.9) sem uma implementação.
- A lista de parâmetros deve estar vazia ou ter um único parâmetro de valor do tipo
string[]
.
Nota: Os métodos com o
async
modificador devem ter exatamente um dos dois tipos de retorno especificados acima para se qualificar como um ponto de entrada. Umasync void
método, ou umasync
método que retorna um tipo aguardado diferente, comoValueTask
ouValueTask<int>
não se qualifica como um ponto de entrada. Nota final
Se mais de um método qualificado como ponto de entrada for declarado dentro de um programa, um mecanismo externo pode ser usado para especificar qual método é considerado o ponto de entrada real para o aplicativo. Se for encontrado um método de qualificação com um tipo de retorno de ou int
qualquer método de qualificação com um tipo de retorno de ou void
não é considerado um método de ponto de System.Threading.Tasks.Task
System.Threading.Tasks.Task<int>
entrada. É um erro em tempo de compilação para um programa ser compilado como um aplicativo sem exatamente um ponto de entrada. Um programa compilado como uma biblioteca de classes pode conter métodos que se qualificariam como pontos de entrada do aplicativo, mas a biblioteca resultante não tem nenhum ponto de entrada.
Normalmente, a acessibilidade declarada (§7.5.2) de um método é determinada pelos modificadores de acesso (§15.3.6) especificados na sua declaração e, da mesma forma, a acessibilidade declarada de um tipo é determinada pelos modificadores de acesso especificados na sua declaração. Para que um determinado método de um determinado tipo seja exigível, tanto o tipo como o membro devem ser acessíveis. No entanto, o ponto de entrada do aplicativo é um caso especial. Especificamente, o ambiente de execução pode acessar o ponto de entrada do aplicativo independentemente de sua acessibilidade declarada e independentemente da acessibilidade declarada de suas declarações de tipo anexadas.
Quando o método de ponto de entrada tem um tipo de retorno de System.Threading.Tasks.Task
ou System.Threading.Tasks.Task<int>
, o compilador deverá sintetizar um método de ponto de entrada síncrono que chama o método Main
correspondente. O método sintetizado tem parâmetros e tipos de retorno com base no Main
método:
- A lista de parâmetros do método sintetizado é a mesma que a lista de parâmetros do
Main
método - Se o tipo de retorno do
Main
método forSystem.Threading.Tasks.Task
, o tipo de retorno do método sintetizado forvoid
- Se o tipo de retorno do
Main
método forSystem.Threading.Tasks.Task<int>
, o tipo de retorno do método sintetizado forint
A execução do método sintetizado procede da seguinte forma:
- O método sintetizado chama o
Main
método, passando seustring[]
valor de parâmetro como um argumento se oMain
método tiver tal parâmetro. - Se o
Main
método lança uma exceção, a exceção é propagada pelo método sintetizado. - Caso contrário, o ponto de entrada sintetizado aguarda a conclusão da tarefa retornada, chamando
GetAwaiter().GetResult()
a tarefa, usando o método de instância sem parâmetros ou o método de extensão descrito por §C.3. Se a tarefa falhar,GetResult()
lançará uma exceção, e essa exceção será propagada pelo método sintetizado. - Para um
Main
método com um tipo de retorno deSystem.Threading.Tasks.Task<int>
, se a tarefa for concluída com êxito, oint
valor retornado porGetResult()
será retornado do método sintetizado.
O ponto de entrada efetivo de um aplicativo é o ponto de entrada declarado dentro do programa, ou o método sintetizado, se necessário, conforme descrito acima. O tipo de retorno do ponto de entrada efetivo é, portanto, sempre void
ou int
.
Quando um aplicativo é executado, um novo domínio de aplicativo é criado. Várias instanciações diferentes de um aplicativo podem existir na mesma máquina ao mesmo tempo, e cada uma tem seu próprio domínio de aplicativo. Um domínio de aplicativo permite o isolamento do aplicativo agindo como um contêiner para o estado do aplicativo. Um domínio de aplicativo atua como um contêiner e limite para os tipos definidos no aplicativo e as bibliotecas de classes que ele usa. Os tipos carregados em um domínio de aplicativo são distintos dos mesmos tipos carregados em outro domínio de aplicativo, e as instâncias de objetos não são compartilhadas diretamente entre domínios de aplicativo. Por exemplo, cada domínio de aplicativo tem sua própria cópia de variáveis estáticas para esses tipos, e um construtor estático para um tipo é executado no máximo uma vez por domínio de aplicativo. As implementações são livres para fornecer políticas ou mecanismos definidos pela implementação para a criação e destruição de domínios de aplicativos.
A inicialização do aplicativo ocorre quando o ambiente de execução chama o ponto de entrada efetivo do aplicativo. Se o ponto de entrada efetivo declarar um parâmetro, durante a inicialização do aplicativo, a implementação deve garantir que o valor inicial desse parâmetro seja uma referência não nula a uma matriz de cadeia de caracteres. Essa matriz deve consistir em referências não nulas a cadeias de caracteres, chamadas parâmetros de aplicativo, que recebem valores definidos pela implementação pelo ambiente host antes da inicialização do aplicativo. A intenção é fornecer ao aplicativo informações determinadas antes da inicialização do aplicativo de outro lugar no ambiente hospedado.
Nota: Em sistemas que suportam uma linha de comando, os parâmetros do aplicativo correspondem ao que geralmente são conhecidos como argumentos de linha de comando. Nota final
Se o tipo de retorno do ponto de entrada efetivo for int
, o valor de retorno da invocação do método pelo ambiente de execução será usado no encerramento do aplicativo (§7.2).
Além das situações listadas acima, os métodos de ponto de entrada se comportam como aqueles que não são pontos de entrada em todos os aspetos. Em particular, se o ponto de entrada for invocado em qualquer outro ponto durante o tempo de vida do aplicativo, como por invocação de método regular, não haverá manipulação especial do método: se houver um parâmetro, ele pode ter um valor inicial de null
, ou um não-valornull
referente a uma matriz que contém referências nulas. Da mesma forma, o valor de retorno do ponto de entrada não tem significado especial além da invocação do ambiente de execução.
7.2 Cessação da candidatura
A terminação do aplicativo retorna o controle para o ambiente de execução.
Se o tipo de retorno do método de ponto de entrada efetivo do aplicativo for int
e a execução for concluída sem resultar em uma exceção, o valor do retornado servirá como o código de status de término do int
aplicativo. O objetivo deste código é permitir a comunicação de sucesso ou falha para o ambiente de execução. Se o tipo de retorno do método de ponto de entrada efetivo for void
e a execução for concluída sem resultar em uma exceção, o código de status de término será 0
.
Se o método de ponto de entrada efetivo terminar devido a uma exceção (§21.4), o código de saída será definido pela implementação. Além disso, a implementação pode fornecer APIs alternativas para especificar o código de saída.
Se os finalizadores (§15.13) são ou não executados como parte do encerramento do aplicativo é definido pela implementação.
Nota: A implementação do .NET Framework faz todos os esforços razoáveis para chamar finalizadores (§15.13) para todos os seus objetos que ainda não foram coletados lixo, a menos que essa limpeza tenha sido suprimida (por uma chamada para o método
GC.SuppressFinalize
de biblioteca, por exemplo). Nota final
7.3 Declarações
As declarações em um programa C# definem os elementos constituintes do programa. Os programas C# são organizados usando namespaces. Eles são introduzidos usando declarações de namespace (§14), que podem conter declarações de tipo e declarações de namespace aninhadas. As declarações de tipo (§14.7) são usadas para definir classes (§15), structs (§16), interfaces (§18), enums (§19) e delegados (§20). Os tipos de membros permitidos numa declaração de tipo dependem da forma da declaração de tipo. Por exemplo, as declarações de classe podem conter declarações para constantes (§15.4), campos (§15.5), métodos (§15.6), propriedades (§15.7), eventos (§15.8), indexadores (§15.9), operadores (§15.10), construtores de instância (§15.11), construtores estáticos (§15.12), finalizadores (§15.13) e tipos aninhados (§15.3.9).
Uma declaração define um nome no espaço de declaração ao qual a declaração pertence. É um erro em tempo de compilação ter duas ou mais declarações que introduzem membros com o mesmo nome em um espaço de declaração, exceto nos seguintes casos:
- Duas ou mais declarações de namespace com o mesmo nome são permitidas no mesmo espaço de declaração. Essas declarações de namespace são agregadas para formar um único namespace lógico e compartilhar um único espaço de declaração.
- Declarações em programas separados, mas no mesmo espaço de declaração de namespace, podem compartilhar o mesmo nome.
Nota: No entanto, estas declarações podem introduzir ambiguidades se incluídas no mesmo pedido. Nota final
- Dois ou mais métodos com o mesmo nome, mas assinaturas distintas, são permitidos no mesmo espaço de declaração (§7.6).
- São permitidas duas ou mais declarações de tipo com o mesmo nome, mas números distintos de parâmetros de tipo no mesmo espaço de declaração (§7.8.2).
- Duas ou mais declarações de tipo com o modificador parcial no mesmo espaço de declaração podem compartilhar o mesmo nome, o mesmo número de parâmetros de tipo e a mesma classificação (classe, struct ou interface). Neste caso, as declarações de tipo contribuem para um único tipo e são elas próprias agregadas para formar um único espaço de declaração (§15.2.7).
- Uma declaração de namespace e uma declaração de tipo no mesmo espaço de declaração podem compartilhar o mesmo nome, desde que a declaração de tipo tenha pelo menos um parâmetro de tipo (§7.8.2).
Existem vários tipos diferentes de espaços de declaração, conforme descrito a seguir.
- Dentro de todas as unidades de compilação de um programa, namespace_member_declarations sem namespace_declaration de inclusão são membros de um único espaço de declaração combinado chamado espaço de declaração global.
- Dentro de todas as unidades de compilação de um programa, namespace_member_declarations dentro de namespace_declarations que têm o mesmo nome de namespace totalmente qualificado são membros de um único espaço de declaração combinado.
- Cada compilation_unit e namespace_body tem um espaço de declaração de alias. Cada extern_alias_directive e using_alias_directive do compilation_unit ou namespace_body contribui com um membro para o espaço de declaração de alias (§14.5.2).
- Cada declaração de classe, struct ou interface não parcial cria um novo espaço de declaração. Cada declaração parcial de classe, struct ou interface contribui para um espaço de declaração compartilhado por todas as partes correspondentes no mesmo programa (§16.2.4). Os nomes são introduzidos neste espaço de declaração através de class_member_declarations, struct_member_declarations, interface_member_declarations ou type_parameters. Exceto para declarações de construtor de instância sobrecarregadas e declarações de construtor estático, uma classe ou struct não pode conter uma declaração de membro com o mesmo nome que a classe ou struct. Uma classe, struct ou interface permite a declaração de métodos e indexadores sobrecarregados. Além disso, uma classe ou struct permite a declaração de construtores e operadores de instância sobrecarregados. Por exemplo, uma classe, struct ou interface pode conter várias declarações de método com o mesmo nome, desde que essas declarações de método sejam diferentes em sua assinatura (§7.6). Observe que as classes base não contribuem para o espaço de declaração de uma classe e as interfaces base não contribuem para o espaço de declaração de uma interface. Assim, uma classe ou interface derivada tem permissão para declarar um membro com o mesmo nome de um membro herdado. Diz-se que tal membro esconde o membro herdado.
- Cada declaração de delegado cria um novo espaço de declaração. Os nomes são introduzidos neste espaço de declaração através dos parâmetros (fixed_parameters e parameter_arrays) e type_parameters.
- Cada declaração de enumeração cria um novo espaço de declaração. Os nomes são introduzidos neste espaço de declaração através de enum_member_declarations.
- Cada declaração de método, declaração de propriedade, declaração de acessador de propriedade, declaração de indexador, declaração de acessador de indexador, declaração de operador, declaração de construtor de instância, função anônima e função local cria um novo espaço de declaração chamado espaço de declaração de variável local. Os nomes são introduzidos neste espaço de declaração através dos parâmetros (fixed_parameters e parameter_arrays) e type_parameters. O acessador definido para uma propriedade ou um indexador introduz o nome
value
como um parâmetro. O corpo do membro da função, função anônima ou função local, se houver, é considerado aninhado dentro do espaço de declaração da variável local. Quando um espaço de declaração de variável local e um espaço de declaração de variável local aninhado contêm elementos com o mesmo nome, dentro do escopo do nome local aninhado, o nome local externo é oculto (§7.7.1) pelo nome local aninhado. - Espaços adicionais de declaração de variável local podem ocorrer dentro de declarações de membro, funções anônimas e funções locais. Os nomes são introduzidos nesses espaços de declaração através de padrõess, declaration_expressions, declaration_statements e exception_specifiers. Os espaços de declaração de variável local podem estar aninhados, mas é um erro para um espaço de declaração de variável local e um espaço de declaração de variável local aninhado para conter elementos com o mesmo nome. Assim, dentro de um espaço de declaração aninhado, não é possível declarar uma variável local, função local ou constante com o mesmo nome que um parâmetro, parâmetro de tipo, variável local, função local ou constante em um espaço de declaração anexo. É possível que dois espaços de declaração contenham elementos com o mesmo nome, desde que nenhum espaço de declaração contenha o outro. Os espaços de declaração local são criados pelas seguintes construções:
- Cada variable_initializer em uma declaração de campo e propriedade introduz seu próprio espaço de declaração de variável local, que não é aninhado em nenhum outro espaço de declaração de variável local.
- O corpo de um membro da função, função anônima ou função local, se houver, cria um espaço de declaração de variável local que é considerado aninhado dentro do espaço de declaração de variável local da função.
- Cada constructor_initializer cria um espaço de declaração de variável local aninhado na declaração do construtor da instância. O espaço de declaração de variável local para o corpo do construtor é, por sua vez, aninhado dentro desse espaço de declaração de variável local.
- Cada bloco, switch_block, specific_catch_clause, iteration_statement e using_statement cria um espaço de declaração de variável local aninhado.
- Cada embedded_statement que não faz parte diretamente de um statement_list cria um espaço de declaração de variável local aninhado.
- Cada switch_section cria um espaço de declaração de variável local aninhado. No entanto, as variáveis declaradas diretamente dentro do statement_list do switch_section (mas não dentro de um espaço de declaração de variável local aninhado dentro do statement_list) são adicionadas diretamente ao espaço de declaração de variável local do switch_block que o inclui, em vez do espaço do switch_section.
- A tradução sintática de um query_expression (§12.20.3) pode introduzir uma ou mais expressões lambda. Como funções anônimas, cada uma delas cria um espaço de declaração de variável local, conforme descrito acima.
- Cada bloco ou switch_block cria um espaço de declaração separado para rótulos. Os nomes são introduzidos neste espaço de declaração através de labeled_statements, e os nomes são referenciados através de goto_statements. O espaço de declaração de rótulo de um bloco inclui todos os blocos aninhados. Assim, dentro de um bloco aninhado não é possível declarar um rótulo com o mesmo nome de um rótulo em um bloco anexo.
Nota: O fato de variáveis declaradas diretamente dentro de um switch_section serem adicionadas ao espaço de declaração de variável local do switch_block em vez do switch_section pode levar a um código surpreendente. No exemplo abaixo, a variável
y
local está no escopo dentro da seção switch para o caso padrão, apesar da declaração aparecer na seção switch para o caso 0. A variávelz
local não está no escopo dentro da seção switch para o caso padrão, pois é introduzida no espaço de declaração da variável local para a seção switch na qual a declaração ocorre.int x = 1; switch (x) { case 0: int y; break; case var z when z < 10: break; default: y = 10; // Valid: y is in scope Console.WriteLine(x + y); // Invalid: z is not scope Console.WriteLine(x + z); break; }
Nota final
A ordem textual em que os nomes são declarados geralmente não tem importância. Em particular, a ordem textual não é significativa para a declaração e o uso de namespaces, constantes, métodos, propriedades, eventos, indexadores, operadores, construtores de instância, finalizadores, construtores estáticos e tipos. A ordem de declaração é significativa das seguintes maneiras:
- A ordem de declaração para declarações de campo determina a ordem em que seus inicializadores (se houver) são executados (§15.5.6.2, §15.5.6.3).
- As variáveis locais devem ser definidas antes de serem utilizadas (§7.7).
- A ordem de declaração para declarações de membro enum (§19.4) é significativa quando constant_expression valores são omitidos.
Exemplo: O espaço de declaração de um namespace é "open ended" e duas declarações de namespace com o mesmo nome totalmente qualificado contribuem para o mesmo espaço de declaração. Por exemplo
namespace Megacorp.Data { class Customer { ... } } namespace Megacorp.Data { class Order { ... } }
As duas declarações de namespace acima contribuem para o mesmo espaço de declaração, neste caso declarando duas classes com os nomes
Megacorp.Data.Customer
totalmente qualificados 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 final
Nota: Conforme especificado acima, o espaço de declaração de um bloco inclui quaisquer blocos aninhados. Assim, no exemplo a seguir, os
F
métodos eG
resultam em um erro em tempo de compilação porque o nomei
é declarado no bloco externo e não pode ser redeclarado no bloco interno. No entanto, osH
métodos eI
são válidos, uma vez que os doisi
's são declarados em blocos separados não aninhados.class A { void F() { int i = 0; if (true) { int i = 1; } } void G() { if (true) { int i = 0; } int i = 1; } void H() { if (true) { int i = 0; } if (true) { int i = 1; } } void I() { for (int i = 0; i < 10; i++) { H(); } for (int i = 0; i < 10; i++) { H(); } } }
Nota final
7.4 Membros
7.4.1 Generalidades
Namespaces e tipos têm membros.
Nota: Os membros de uma entidade estão geralmente disponíveis através do uso de um nome qualificado que começa com uma referência à entidade, seguido por um token "
.
", seguido pelo nome do membro. Nota final
Os membros de um tipo são declarados na declaração de tipo ou herdados da classe base do tipo. Quando um tipo herda de uma classe base, todos os membros da classe base, exceto construtores de instância, finalizadores e construtores estáticos tornam-se membros do tipo derivado. A acessibilidade declarada de um membro de classe base não controla se o membro é herdado — a herança se estende a qualquer membro que não seja um construtor de instância, construtor estático ou finalizador.
Nota: No entanto, um membro herdado pode não estar acessível num tipo derivado, por exemplo, devido à sua acessibilidade declarada (§7.5.2). Nota final
7.4.2 Membros do namespace
Namespaces e tipos que não têm namespace de inclusão são membros do namespace global. Isso corresponde diretamente aos nomes declarados no espaço de declaração global.
Namespaces e tipos declarados dentro de um namespace são membros desse namespace. Isso corresponde diretamente aos nomes declarados no espaço de declaração do namespace.
Os namespaces não têm restrições de acesso. Não é possível declarar namespaces privados, protegidos ou internos, e os nomes de namespace são sempre acessíveis publicamente.
7.4.3 Membros da estrutura
Os membros de um struct são os membros declarados no struct e os membros herdados da classe System.ValueType
base direta do struct e da classe object
base indireta.
Os membros de um tipo simples correspondem diretamente aos membros do tipo struct aliased pelo tipo simples (§8.3.5).
7.4.4 Membros da enumeração
Os membros de uma enumeração são as constantes declaradas na enumeração e os membros herdados da classe System.Enum
base direta da enumeração e as classes System.ValueType
base indiretas e object
.
7.4.5 Membros da turma
Os membros de uma classe são os membros declarados na classe e os membros herdados da classe base (exceto para a classe object
que não tem classe base). Os membros herdados da classe base incluem as constantes, campos, métodos, propriedades, eventos, indexadores, operadores e tipos da classe base, mas não os construtores de instância, finalizadores e construtores estáticos da classe base. Os membros da classe base são herdados independentemente da sua acessibilidade.
Uma declaração de classe pode conter declarações de constantes, campos, métodos, propriedades, eventos, indexadores, operadores, construtores de instância, finalizadores, construtores estáticos e tipos.
Os membros de object
(§8.2.3) e string
(§8.2.5) correspondem diretamente aos membros dos tipos de classe que alias.
7.4.6 Membros da interface
Os membros de uma interface são os membros declarados na interface e em todas as interfaces base da interface.
Nota: Os membros em classe
object
não são, a rigor, membros de qualquer interface (§18.4). No entanto, os membros em classeobject
estão disponíveis através da pesquisa de membros em qualquer tipo de interface (§12.5). Nota final
7.4.7 Membros da matriz
Os membros de uma matriz são os membros herdados da classe System.Array
.
7.4.8 Membros delegados
Um delegado herda membros da classe System.Delegate
. Além disso, contém um método nomeado Invoke
com o mesmo tipo de retorno e lista de parâmetros especificados na sua declaração (§20.2). Uma invocação deste método deve comportar-se de forma idêntica a uma invocação delegada (§20.6) na mesma instância delegada.
Uma implementação pode fornecer membros adicionais, seja por herança ou diretamente no próprio delegado.
7.5 Acesso dos membros
7.5.1 Generalidades
As declarações dos membros permitem o controlo sobre o acesso dos membros. A acessibilidade de um membro é estabelecida pela acessibilidade declarada (§7.5.2) do membro combinada com a acessibilidade do tipo imediatamente contido, se houver.
Quando o acesso a um determinado membro é permitido, diz-se que o membro é acessível. Por outro lado, quando o acesso a um determinado membro é proibido, o membro é considerado inacessível. O acesso a um membro é permitido quando a localização textual em que o acesso ocorre está incluída no domínio de acessibilidade (§7.5.3) do membro.
7.5.2 Acessibilidade declarada
A acessibilidade declarada de um membro pode ser uma das seguintes:
- Public, que é selecionado pela inclusão de um
public
modificador na declaração de membro. O significado intuitivo depublic
é "acesso não limitado". - Protegido, que é selecionado pela inclusão de um
protected
modificador na declaração de membro. O significado intuitivo deprotected
é "acesso limitado à classe que contém ou tipos derivados da classe que contém". - Interno, que é selecionado pela inclusão de um
internal
modificador na declaração de membro. O significado intuitivo deinternal
é "acesso limitado a esta assembleia". - Protegido interno, que é selecionado incluindo um
protected
e uminternal
modificador na declaração de membro. O significado intuitivo deprotected internal
é "acessível dentro desta assembléia, bem como tipos derivados da classe que contém". - Proteção privada, que é selecionada incluindo um
private
e umprotected
modificador na declaração de membro. O significado intuitivo deprivate protected
é "acessível dentro desta assembléia pela classe que contém e tipos derivados da classe que contém". - Private, que é selecionado incluindo um
private
modificador na declaração de membro. O significado intuitivo deprivate
é "acesso limitado ao tipo que contém".
Dependendo do contexto em que uma declaração de membro ocorre, apenas certos tipos de acessibilidade declarada são permitidos. Além disso, quando uma declaração de membro não inclui nenhum modificador de acesso, o contexto em que a declaração ocorre determina a acessibilidade declarada padrão.
- Os namespaces implicitamente declararam
public
acessibilidade. Nenhum modificador de acesso é permitido em declarações de namespace. - Os tipos declarados diretamente em unidades de compilação ou namespaces (em oposição a outros tipos) podem ter
public
ouinternal
declarado acessibilidade e padrão parainternal
acessibilidade declarada. - Os membros da classe podem ter qualquer um dos tipos permitidos de acessibilidade declarada e padrão para
private
acessibilidade declarada.Nota: Um tipo declarado como membro de uma classe pode ter qualquer um dos tipos permitidos de acessibilidade declarada, enquanto um tipo declarado como membro de um namespace pode ter apenas
public
ouinternal
declarada acessibilidade. Nota final - Os membros Struct podem ter
public
,internal
ouprivate
acessibilidade declarada e padrão paraprivate
acessibilidade declarada porque structs são implicitamente selados. Os membros Struct introduzidos em umstruct
(ou seja, não herdados por esse struct) não podem terprotected
,protected internal
ouprivate protected
acessibilidade declarada.Nota: Um tipo declarado como membro de uma struct pode ter
public
,internal
ouprivate
acessibilidade declarada, enquanto um tipo declarado como membro de um namespace pode ter apenaspublic
ouinternal
declarada acessibilidade. Nota final - Os membros da interface declararam
public
implicitamente a acessibilidade. Nenhum modificador de acesso é permitido em declarações de membro da interface. - Os membros da enumeração declararam
public
implicitamente a acessibilidade. Nenhum modificador de acesso é permitido em declarações de membro de enumeração.
7.5.3 Domínios de acessibilidade
O domínio de acessibilidade de um membro consiste nas seções (possivelmente disjuntas) do texto do programa em que o acesso ao membro é permitido. Para fins de definição do domínio de acessibilidade de um membro, um membro é dito de nível superior se não for declarado dentro de um tipo, e um membro é dito ser aninhado se for declarado dentro de outro tipo. Além disso, o texto do programa de um programa é definido como todo o texto contido em todas as unidades de compilação do programa, e o texto do programa de um tipo é definido como todo o texto contido nos type_declarations desse tipo (incluindo, possivelmente, os tipos que estão aninhados dentro do tipo).
O domínio de acessibilidade de um tipo predefinido (como object
, int
ou double
) é ilimitado.
O domínio de acessibilidade de um tipo T
não vinculado de nível superior (§8.4.4) que é declarado em um programa P
é definido da seguinte forma:
- Se a acessibilidade declarada de é pública, o domínio de
T
acessibilidade deT
é o texto do programa deP
e qualquer programa que faz referênciaP
. - Se a acessibilidade declarada de é interna, o domínio de
T
acessibilidade de é o texto doT
programa deP
.
Nota: A partir dessas definições, segue-se que o domínio de acessibilidade de um tipo não vinculado de nível superior é sempre pelo menos o texto do programa no qual esse tipo é declarado. Nota final
O domínio de acessibilidade para um tipo T<A₁, ..., Aₑ>
construído é a interseção do domínio de acessibilidade do tipo T
genérico não acoplado e os domínios de acessibilidade dos argumentos A₁, ..., Aₑ
de tipo .
O domínio de acessibilidade de um membro M
aninhado declarado em um tipo T
dentro de um programa P
, é definido da seguinte forma (observando que M
ele mesmo pode ser um tipo):
- Se a acessibilidade declarada de é
M
, o domínio depublic
acessibilidade deM
é o domínio de acessibilidade deT
. - Se a acessibilidade declarada de é , vamos
M
ser a união do texto do programa deprotected internal
e o texto do programa de qualquer tipo derivado deD
, que é declaradoP
foraT
.P
O domínio de acessibilidade deM
é a interseção do domínio de acessibilidade deT
comD
. - Se a acessibilidade declarada de
M
é , vamosprivate protected
ser a interseção do texto do programa deD
e o texto do programa deP
e qualquer tipo derivado deT
T
. O domínio de acessibilidade deM
é a interseção do domínio de acessibilidade deT
comD
. - Se a acessibilidade declarada de
M
é , vamosprotected
ser a união do texto do programa deD
e o texto do programa de qualquer tipo derivado deT
T
. O domínio de acessibilidade deM
é a interseção do domínio de acessibilidade deT
comD
. - Se a acessibilidade declarada de
M
éinternal
, o domínio de acessibilidade deM
é a interseção do domínio de acessibilidade deT
com o texto do programa deP
. - Se a acessibilidade declarada de é
M
, o domínio deprivate
acessibilidade de é o texto doM
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 em que o membro é declarado. Nota final
Nota: Em termos intuitivos, quando um tipo ou membro
M
é acedido, são avaliados os seguintes passos para garantir que o acesso é permitido:
- Primeiro, se
M
for declarado dentro de um tipo (em oposição a uma unidade de compilação ou um namespace), ocorrerá um erro em tempo de compilação se esse tipo não estiver acessível.- Então, se
M
forpublic
, o acesso é permitido.- Caso contrário, se
M
forprotected internal
, o acesso é permitido se ocorrer dentro do programa no qualM
é declarado, ou se ocorrer dentro de uma classe derivada da classe na qualM
é declarada e ocorre através do tipo de classe derivada (§7.5.4).- Caso contrário, se
M
forprotected
, o acesso é permitido se ocorrer dentro da classe em queM
é declarado, ou se ocorrer dentro de uma classe derivada da classe em queM
é declarada e ocorrer através do tipo de classe derivado (§7.5.4).- Caso contrário, se
M
forinternal
, o acesso é permitido se ocorrer dentro do programa em queM
é declarado.- Caso contrário, se
M
forprivate
, o acesso é permitido se ocorrer dentro do tipo em queM
é declarado.- Caso contrário, o tipo ou membro fica inacessível e ocorre um erro em tempo de compilação. Nota final
Exemplo: No código a seguir
public class A { public static int X; internal static int Y; private static int Z; } internal class B { public static int X; internal static int Y; private static int Z; public class C { public static int X; internal static int Y; private static int Z; } private class D { public static int X; internal static int Y; private static int Z; } }
As classes e membros têm os seguintes domínios de acessibilidade:
- O domínio de acessibilidade de
A
eA.X
é ilimitado.- O domínio de acessibilidade de
A.Y
,B
,B.X
, ,B.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.Z
eB.D
é o texto do programa deB
, incluindo o texto do programa deB.C
eB.D
.- O domínio de acessibilidade de
B.C.Z
é o texto do programa deB.C
.- O domínio de acessibilidade de
B.D.X
eB.D.Y
é o texto do programa deB
, incluindo o texto do programa deB.C
eB.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 de contenção. Por exemplo, embora todos osX
membros tenham acessibilidade pública declarada, todos, excetoA.X
têm domínios de acessibilidade que são limitados por um tipo de contenção.Exemplo final
Conforme descrito no §7.4, todos os membros de uma classe base, exceto construtores, finalizadores e construtores estáticos, são herdados por tipos derivados. Isso inclui até mesmo membros privados de uma classe base. No entanto, o domínio de acessibilidade de um membro privado inclui apenas o texto do programa do tipo em que o membro é declarado.
Exemplo: No código a seguir
class A { int x; static void F(B b) { b.x = 1; // Ok } } class B : A { static void F(B b) { b.x = 1; // Error, x not accessible } }
A
B
classe herda o membrox
privado daA
classe. Como o membro é privado, ele só é acessível dentro do class_body deA
. Assim, o acesso ab.x
é bem-sucedido noA.F
método, mas falha noB.F
método.Exemplo final
7.5.4 Acesso protegido
Quando um protected
membro da instância ou private protected
é acessado fora do texto do programa da classe na qual é declarado, e quando um protected internal
membro da instância é acessado fora do texto do programa no qual é declarado, o acesso deve ocorrer dentro de uma declaração de classe que deriva da classe na qual é declarado. Além disso, o acesso deve ocorrer através de uma instância desse tipo de classe derivado ou de um tipo de classe construído a partir dele. Essa restrição impede que uma classe derivada acesse membros protegidos de outras classes derivadas, mesmo quando os membros são herdados da mesma classe base.
Seja B
uma classe base que declare um membro M
de instância protegido e seja D
uma classe derivada de B
. No class_bodyD
acesso pode M
assumir uma das seguintes formas:
- Uma type_name ou primary_expression não qualificada
M
- Um
D
- Uma primary_expression do formulário
base.M
. - Uma
]
Além dessas formas de acesso, uma classe derivada pode acessar um construtor de instância protegida de uma classe base em um constructor_initializer (§15.11.2).
Exemplo: No código a seguir
public class A { protected int x; static void F(A a, B b) { a.x = 1; // Ok b.x = 1; // Ok } } public class B : A { static void F(A a, B b) { a.x = 1; // Error, must access through instance of B b.x = 1; // Ok } }
Dentro
A
do , é possível acessarx
através de instâncias de ambos eA
B
, uma vez que em ambos os casos o acesso ocorre através de uma instância de ou uma classe derivadaA
deA
. No entanto, dentroB
de , não é possível acessarx
através de uma instância deA
, uma vez queA
não deriva deB
.Exemplo final
Exemplo:
class C<T> { protected T x; } class D<T> : C<T> { static void F() { D<T> dt = new D<T>(); D<int> di = new D<int>(); D<string> ds = new D<string>(); dt.x = default(T); di.x = 123; ds.x = "test"; } }
Aqui, as três atribuições são
x
permitidas porque todas elas ocorrem através de instâncias de tipos de classe construídos a partir do tipo genérico.Exemplo final
Nota: O domínio de acessibilidade (§7.5.3) de um membro protegido declarado em uma classe genérica inclui o texto do programa de todas as declarações de classe derivadas de qualquer tipo construído a partir dessa classe genérica. No exemplo:
class C<T> { protected static T x; } class D : C<string> { static void Main() { C<int>.x = 5; } }
A referência a
protected
MemberC<int>.x
InD
é válida mesmo que a classeD
derive deC<string>
. Nota final
7.5.5 Limitações de acessibilidade
Várias construções na linguagem C# exigem que um tipo seja pelo menos tão acessível quanto um membro ou outro tipo. Diz-se que um tipo T
é pelo menos tão acessível quanto um membro ou tipo M
se o domínio de acessibilidade de T
é um superconjunto do domínio de acessibilidade de M
. Por outras palavras, T
é pelo menos tão acessível como M
se T
fosse acessível em todos os contextos em que M
é acessível.
Existem as seguintes restrições de acessibilidade:
- A classe de base direta de um tipo de classe deve ser pelo menos tão acessível como o próprio tipo de classe.
- As interfaces de base explícitas de um tipo de interface devem ser pelo menos tão acessíveis como o próprio tipo de interface.
- O tipo de retorno e os tipos de parâmetros de um tipo delegado devem ser pelo menos tão acessíveis quanto o próprio tipo de delegado.
- O tipo de constante deve ser pelo menos tão acessível como a própria constante.
- O tipo de campo deve ser pelo menos tão acessível como o próprio campo.
- O tipo de retorno e os tipos de parâmetros de um método devem ser pelo menos tão acessíveis como o próprio método.
- O tipo de propriedade deve ser, pelo menos, tão acessível como o próprio imóvel.
- O tipo de evento deve ser pelo menos tão acessível como o próprio evento.
- O tipo e os tipos de parâmetros de um indexador devem ser pelo menos tão acessíveis como o próprio indexador.
- Os tipos de retorno e os tipos de parâmetros de um operador devem ser pelo menos tão acessíveis como o próprio operador.
- Os tipos de parâmetros de um construtor de instância devem ser pelo menos tão acessíveis quanto o próprio construtor de instância.
- Uma restrição de tipo de interface ou classe relativa a um parâmetro de tipo deve ser pelo menos tão acessível como o membro que declara essa restrição.
Exemplo: No código a seguir
class A {...} public class B: A {...}
A
B
classe resulta em um erro em tempo de compilação porqueA
não é pelo menos tão acessível quantoB
.Exemplo final
Exemplo: Da mesma forma, no código a seguir
class A {...} public class B { A F() {...} internal A G() {...} public A H() {...} }
O
H
método emB
resulta em um erro em tempo de compilação porque o tipoA
de retorno não é pelo menos tão acessível quanto o método.Exemplo final
7.6 Assinaturas e sobrecarga
Métodos, construtores de instância, indexadores e operadores são caracterizados por suas assinaturas:
- A assinatura de um método consiste no nome do método, no número de parâmetros de tipo e no tipo e modo de passagem de parâmetros de cada um de seus parâmetros, considerados na ordem da esquerda para a direita. Para esses fins, qualquer parâmetro de tipo do método que ocorre no tipo de um parâmetro é identificado não por seu nome, mas por sua posição ordinal na lista de parâmetros de tipo do método. A assinatura de um método especificamente não inclui o tipo de retorno, nomes de parâmetros, nomes de parâmetros de tipo, restrições de parâmetros de tipo, modificadores de parâmetros ou
params
nem se osthis
parâmetros são necessários ou opcionais. - A assinatura de um construtor de instância consiste no tipo e no modo de passagem de parâmetros de cada um de seus parâmetros, considerados na ordem da esquerda para a direita. A assinatura de um construtor de instância especificamente não inclui o
params
modificador que pode ser especificado para o parâmetro mais à direita, nem se os parâmetros são necessários ou opcionais. - A assinatura de um indexador consiste no tipo de cada um dos seus parâmetros, considerados na ordem da esquerda para a direita. A assinatura de um indexador especificamente não inclui o tipo de elemento, nem o
params
modificador que pode ser especificado para o parâmetro mais à direita, nem se os parâmetros são obrigatórios ou opcionais. - A assinatura de um operador consiste no nome do operador e no tipo de cada um dos seus parâmetros, considerados na ordem da esquerda para a direita. A assinatura de um operador especificamente não inclui o tipo de resultado.
- A assinatura de um operador de conversão consiste no tipo de origem e no tipo de destino. A classificação implícita ou explícita de um operador de conversão não faz parte da assinatura.
- Duas assinaturas do mesmo tipo de membro (método, construtor de instância, indexador ou operador) são consideradas as mesmas assinaturas se tiverem o mesmo nome, número de parâmetros de tipo, número de parâmetros e modos de passagem de parâmetros, e existir uma conversão de identidade entre os tipos de seus parâmetros correspondentes (§10.2.2).
As assinaturas são o mecanismo habilitador para a sobrecarga de membros em classes, estruturas e interfaces:
- A sobrecarga de métodos permite que uma classe, struct ou interface declare vários métodos com o mesmo nome, desde que suas assinaturas sejam exclusivas dentro dessa classe, struct ou interface.
- A sobrecarga de construtores de instância permite que uma classe ou struct declare vários construtores de instância, desde que suas assinaturas sejam exclusivas dentro dessa classe ou estrutura.
- A sobrecarga de indexadores permite que uma classe, struct ou interface declare vários indexadores, desde que suas assinaturas sejam exclusivas dentro dessa classe, struct ou interface.
- A sobrecarga de operadores permite que uma classe ou struct declare vários operadores com o mesmo nome, desde que suas assinaturas sejam exclusivas dentro dessa classe ou struct.
Embora in
, out
e ref
modificadores de parâmetros sejam considerados parte de uma assinatura, os membros declarados em um único tipo não podem diferir na assinatura apenas por in
, out
e ref
. Um erro em tempo de compilação ocorre se dois membros são declarados no mesmo tipo com assinaturas que seriam as mesmas se todos os parâmetros em ambos os métodos com out
ou in
modificadores fossem alterados para ref
modificadores. Para outros fins de correspondência de assinatura (por exemplo, ocultar ou substituir), in
, out
, e ref
são considerados parte da assinatura e não correspondem entre si.
Nota: Esta restrição é para permitir que programas C# sejam facilmente traduzidos para serem executados na Common Language Infrastructure (CLI), que não fornece uma maneira de definir métodos que diferem apenas em
in
,out
eref
. Nota final
Os tipos object
e dynamic
não são distinguidos quando se comparam assinaturas. Por conseguinte, não são permitidos membros declarados num único tipo cujas assinaturas diferem apenas por substituição object
por dynamic
por.
Exemplo: O exemplo a seguir mostra um conjunto de declarações de método sobrecarregadas junto com suas assinaturas.
interface ITest { void F(); // F() void F(int x); // F(int) void F(ref int x); // F(ref int) void F(out int x); // F(out int) error void F(object o); // F(object) void F(dynamic d); // error. void F(int x, int y); // F(int, int) int F(string s); // F(string) int F(int x); // F(int) error void F(string[] a); // F(string[]) void F(params string[] a); // F(string[]) error void F<S>(S s); // F<0>(0) void F<T>(T t); // F<0>(0) error void F<S,T>(S s); // F<0,1>(0) void F<T,S>(S s); // F<0,1>(1) ok }
Observe que qualquer
in
modificador ,out
eref
parâmetro (§15.6.2) fazem parte de uma assinatura. Assim,F(int)
,F(in int)
,F(out int)
, eF(ref int)
são todas assinaturas únicas. No entanto,F(in int)
,F(out int)
eF(ref int)
não podem ser declarados dentro da mesma interface porque suas assinaturas diferem apenas porin
,out
eref
. Além disso, observe que o tipo de retorno e oparams
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 doparams
modificador. Como tal, as declarações dos métodosF(int)
eF(params string[])
identificadas acima, resultam em um erro em tempo de compilação. Exemplo final
7.7 Âmbitos de aplicação
7.7.1 Generalidades
O escopo de um nome é a região do texto do programa dentro da qual é possível referir-se à entidade declarada pelo nome sem qualificação do nome. Os escopos podem ser aninhados e um escopo interno pode redeclarar o significado de um nome de um escopo externo. (Isso não elimina, no entanto, a restrição imposta pelo §7.3 de que dentro de um bloco aninhado não é possível declarar uma variável local ou constante local com o mesmo nome que uma variável local ou constante local em um bloco fechado.) Diz-se então que o nome do escopo externo está oculto na região do texto do programa coberta pelo escopo interno, e o acesso ao nome externo só é possível qualificando o nome.
O escopo de um membro de namespace declarado por um namespace_member_declaration (§14.6) sem namespace_declaration de inclusão é todo o texto do programa.
O escopo de um membro de namespace declarado por um namespace_member_declaration dentro de um namespace_declaration cujo nome totalmente qualificado é
N
, é o namespace_body de cada namespace_declaration cujo nome totalmente qualificado éN
ou começa comN
, seguido por um ponto.O âmbito de um nome definido por um extern_alias_directive (§14.4) estende-se ao using_directives, global_attributes e namespace_member_declarations dos seus compilation_unit ou namespace_body que contenham imediatamente. Um extern_alias_directive não contribui com novos membros para o espaço de declaração subjacente. Em outras palavras, uma extern_alias_directive não é transitiva, mas, sim, afeta apenas a compilation_unit ou namespace_body em que ocorre.
O âmbito de um nome definido ou importado por um using_directive (§14.5) estende-se ao global_attributes e namespace_member_declarations do compilation_unit ou namespace_body em que o using_directive ocorre. Um using_directive pode disponibilizar zero ou mais nomes de namespace ou tipo em um determinado compilation_unit ou namespace_body, mas não contribui com novos membros para o espaço de declaração subjacente. Em outras palavras, um using_directive não é transitivo, mas afeta apenas o compilation_unit ou namespace_body em que ocorre.
O escopo de um parâmetro de tipo declarado por um type_parameter_list em um class_declaration (§15.2) é o class_base, type_parameter_constraints_clauses e class_body desse class_declaration.
Nota: Ao contrário dos membros de uma classe, este âmbito não se estende às classes derivadas. Nota final
O âmbito de um parâmetro de tipo declarado por um type_parameter_list num struct_declaration (§16.2) é o struct_interfaces, type_parameter_constraints_clauses e struct_body desse struct_declaration.
O âmbito de um parâmetro de tipo declarado por um type_parameter_list num interface_declaration (§18.2) é o interface_base, type_parameter_constraints_clauses e interface_body desse interface_declaration.
O escopo de um parâmetro de tipo declarado por um type_parameter_list em um delegate_declaration (§20.2) é o return_type, parameter_list e type_parameter_constraints_clauses desse delegate_declaration.
O âmbito de um parâmetro de tipo declarado por um type_parameter_list num method_declaration (§15.6.1) é o method_declaration.
O âmbito de um membro declarado por um class_member_declaration (§15.3.1) é o class_body em que a declaração ocorre. Além disso, o âmbito de um membro de classe estende-se à class_body das classes derivadas que estão incluídas no domínio de acessibilidade (§7.5.3) do membro.
O âmbito de um membro declarado por um struct_member_declaration (§16.3) é o struct_body em que a declaração ocorre.
O âmbito de um membro declarado por um enum_member_declaration (§19.4) é o enum_body em que a declaração ocorre.
O âmbito de aplicação de um parâmetro declarado num method_declaration (§15.6) é o method_body ou ref_method_body desse method_declaration.
O âmbito de aplicação de um parâmetro declarado numa indexer_declaration (§15.9) é o indexer_body dessa indexer_declaration.
O âmbito de aplicação de um parâmetro declarado numa operator_declaration (§15.10) é o operator_body dessa operator_declaration.
O âmbito de um parâmetro declarado numa constructor_declaration (§15.11) é o constructor_initializer e o bloco dessa constructor_declaration.
O âmbito de aplicação de um parâmetro declarado numa lambda_expression (§12.19) é o lambda_expression_body dessa lambda_expression.
O âmbito de um parâmetro declarado num anonymous_method_expression (§12.19) é o bloco dessa anonymous_method_expression.
O âmbito de aplicação de um rótulo declarado numa labeled_statement (§13.5) é o bloco em que a declaração ocorre.
O escopo de uma variável local declarada em um local_variable_declaration (§13.6.2) é o bloco no qual a declaração ocorre.
O âmbito de uma variável local declarada numa switch_block de uma declaração (
switch
) é o switch_block.O escopo de uma variável local declarada em uma for_initializer de uma
for
declaração (§13.9.4) é a for_initializer, for_condition, for_iterator e embedded_statement dafor
declaração.O âmbito de uma constante local declarada num local_constant_declaration (§13.6.3) é o bloco em que a declaração ocorre. É um erro em tempo de compilação referir-se a uma constante local em uma posição textual que precede sua constant_declarator.
O escopo de uma variável declarada como parte de uma foreach_statement, using_statement, lock_statement ou query_expression é determinado pela expansão da construção dada.
Dentro do escopo de um namespace, classe, struct ou membro de enumeração, é possível referir-se ao membro em uma posição textual que precede a declaração do membro.
Exemplo:
class A { void F() { i = 1; } int i = 0; }
Aqui, é válido referir-se
F
antesi
de ser declarado.Exemplo final
Dentro do escopo de uma variável local, é um erro em tempo de compilação referir-se à variável local em uma posição textual que precede seu declarador.
Exemplo:
class A { int i = 0; void F() { i = 1; // Error, use precedes declaration int i; i = 2; } void G() { int j = (j = 1); // Valid } void H() { int a = 1, b = ++a; // Valid } }
No método acima, a
F
primeira atribuição ai
especificamente não se refere ao campo declarado no escopo externo. Em vez disso, refere-se à variável local e resulta em um erro em tempo de compilação porque precede textualmente a declaração da variável.G
No método, o uso dej
no inicializador para a declaração dej
é válido porque o uso não precede o declarador.H
No método, um declarador subsequente refere-se corretamente a uma variável local declarada em um declarador anterior dentro do mesmo local_variable_declaration.Exemplo final
Nota: As regras de escopo para variáveis locais e constantes locais são projetadas para garantir que o significado de um nome usado em um contexto de expressão seja sempre o mesmo dentro de um bloco. Se o escopo de uma variável local se estendesse apenas de sua declaração até o final do bloco, então, no exemplo acima, a primeira atribuição seria atribuída à variável de instância e a segunda atribuição atribuiria à variável local, possivelmente levando a erros em tempo de compilação se as instruções do bloco fossem posteriormente reorganizadas.)
O significado de um nome dentro de um bloco pode diferir com base no contexto em que o nome é usado. No exemplo
class A {} class Test { static void Main() { string A = "hello, world"; string s = A; // expression context Type t = typeof(A); // type context Console.WriteLine(s); // writes "hello, world" Console.WriteLine(t); // writes "A" } }
O nome
A
é usado em um contexto de expressão para se referir à variávelA
local e em um contexto de tipo para se referir à classeA
.Nota final
7.7.2 Ocultação de nomes
7.7.2.1 Generalidades
O escopo de uma entidade normalmente engloba mais texto de programa do que o espaço de declaração da entidade. Em particular, o âmbito de uma entidade pode incluir declarações que introduzam novos espaços de declaração contendo entidades com o mesmo nome. Tais declarações fazem com que a entidade original fique oculta. Por outro lado, diz-se que uma entidade é visível quando não está escondida.
A ocultação de nomes ocorre quando os escopos se sobrepõem por meio de aninhamento e quando os escopos se sobrepõem por meio de herança. As características dos dois tipos de ocultação são descritas nas subcláusulas seguintes.
7.7.2.2 Ocultando-se através do nesting
A ocultação de nomes por meio do aninhamento pode ocorrer como resultado do aninhamento de namespaces ou tipos dentro de namespaces, como resultado de tipos de aninhamento dentro de classes ou estruturas, como resultado de uma função local ou lambda e como resultado de parâmetro, variável local e declarações constantes locais.
Exemplo: No código a seguir
class A { int i = 0; void F() { int i = 1; void M1() { float i = 1.0f; Func<double, double> doubler = (double i) => i * 2.0; } } void G() { i = 1; } }
Dentro do
F
método, a variáveli
de instância é ocultada pela variáveli
local , mas dentro doG
método,i
ainda se refere à variável de instância. Dentro da funçãoM1
local ofloat i
esconde o imediato-exteriori
. O parâmetroi
lambda oculta ofloat i
interior do corpo lambda.Exemplo final
Quando um nome em um escopo interno oculta um nome em um escopo externo, ele oculta todas as ocorrências sobrecarregadas desse nome.
Exemplo: No código a seguir
class Outer { static void F(int i) {} static void F(string s) {} class Inner { static void F(long l) {} void G() { F(1); // Invokes Outer.Inner.F F("Hello"); // Error } } }
A chamada
F(1)
invoca oF
declarado emInner
porque todas as ocorrências externas deF
são ocultadas pela declaração interna. Pelo mesmo motivo, a chamadaF("Hello")
resulta em um erro em tempo de compilação.Exemplo final
7.7.2.3 Ocultação por herança
A ocultação de nomes por herança ocorre quando classes ou estruturas redeclaram nomes que foram herdados de classes base. Este tipo de ocultação de nomes assume uma das seguintes formas:
- Uma constante, campo, propriedade, evento ou tipo introduzido em uma classe ou struct oculta todos os membros da classe base com o mesmo nome.
- Um método introduzido em uma classe ou struct oculta todos os membros da classe base não-método com o mesmo nome e todos os métodos de classe base com a mesma assinatura (§7.6).
- Um indexador introduzido numa classe ou struct oculta todos os indexadores de classe base com a mesma assinatura (§7.6).
As regras que regem as declarações de operador (ponto 15.10) tornam impossível a uma classe derivada declarar um operador com a mesma assinatura que um operador de uma classe de base. Assim, os operadores nunca se escondem uns aos outros.
Ao contrário de ocultar um nome de um escopo externo, ocultar um nome visível de um escopo herdado faz com que um aviso seja relatado.
Exemplo: No código a seguir
class Base { public void F() {} } class Derived : Base { public void F() {} // Warning, hiding an inherited name }
a declaração de
F
inDerived
faz com que uma advertência seja comunicada. Ocultar um nome herdado não é especificamente um erro, uma vez que isso impediria a evolução separada das classes base. Por exemplo, a situação acima pode ter surgido porque uma versão posterior doBase
introduziu umF
método que não estava presente em uma versão anterior da classe.Exemplo final
O aviso causado pela ocultação de um nome herdado pode ser eliminado através do uso do new
modificador:
Exemplo:
class Base { public void F() {} } class Derived : Base { public new void F() {} }
O
new
modificador indica que oF
inDerived
é "novo", e que ele é realmente destinado a esconder o membro herdado.Exemplo final
Uma declaração de um novo membro oculta um membro herdado apenas no âmbito do novo membro.
Exemplo:
class Base { public static void F() {} } class Derived : Base { private new static void F() {} // Hides Base.F in Derived only } class MoreDerived : Derived { static void G() { F(); // Invokes Base.F } }
No exemplo acima, a declaração de
F
in esconde oDerived
que foi herdado deF
, mas como o novoBase
inF
tem acesso privado, seu escopo não se estende aDerived
MoreDerived
. Assim, a chamadaF()
MoreDerived.G
é válida e invocaráBase.F
.Exemplo final
7.8 Nomes de namespace e tipo
7.8.1 Generalidades
Vários contextos em um programa C# exigem que um namespace_name ou um type_name sejam especificados.
namespace_name
: namespace_or_type_name
;
type_name
: namespace_or_type_name
;
namespace_or_type_name
: identifier type_argument_list?
| namespace_or_type_name '.' identifier type_argument_list?
| qualified_alias_member
;
Um namespace_name é um namespace_or_type_name que se refere a um namespace.
Após a resolução, conforme descrito abaixo, o namespace_or_type_name de um namespace_name deve referir-se a um namespace ou, caso contrário, ocorrerá um erro em tempo de compilação. Nenhum argumento de tipo (§8.4.2) pode estar presente em um namespace_name (apenas tipos podem ter argumentos de tipo).
Um type_name é um namespace_or_type_name que se refere a um tipo. Após a resolução, conforme descrito abaixo, o namespace_or_type_name de um type_name deve referir-se a um tipo, ou de outra forma ocorre um erro em tempo de compilação.
Se o namespace_or_type_name for um qualified_alias_member o seu significado é o descrito no §14.8.1. Caso contrário, um namespace_or_type_name tem uma de quatro formas:
I
I<A₁, ..., Aₓ>
N.I
N.I<A₁, ..., Aₓ>
onde I
é um identificador único, N
é um namespace_or_type_name e <A₁, ..., Aₓ>
é um type_argument_list opcional. Quando nenhum type_argument_list for especificado, considere x
zero.
O significado de um namespace_or_type_name é determinado da seguinte forma:
- Se o namespace_or_type_name for um qualified_alias_member, o significado é o especificado no §14.8.1.
- Caso contrário, se o namespace_or_type_name for da forma
I
ou da formaI<A₁, ..., Aₓ>
:- Se
x
for zero e o namespace_or_type_name aparecer dentro de uma declaração de método genérico (§15.6), mas fora dos atributos de seu cabeçalho de método, e se essa declaração incluir um parâmetro de tipo (§15.2.3) com nomeI
, então o namespace_or_type_name refere-se a esse parâmetro de tipo. - Caso contrário, se o namespace_or_type_name aparecer dentro de uma declaração de tipo, então para cada tipo
T
de instância (§15.3.2), começando com o tipo de instância dessa declaração de tipo e continuando com o tipo de instância de cada classe anexa ou declaração struct (se houver):- Se
x
for zero e a declaração de incluir um parâmetro type com nameT
, então oI
refere-se a esse parâmetro type. - Caso contrário, se o namespace_or_type_name aparecer dentro do corpo da declaração de tipo, e
T
ou qualquer um de seus tipos base contiver um tipo acessível aninhado com parâmetros de nomeI
ex
tipo, o namespace_or_type_name se refere a esse tipo construído com os argumentos de tipo fornecidos. Se houver mais de um desses tipos, o tipo declarado dentro do tipo mais derivado será selecionado.
Nota: Membros não-tipo (constantes, campos, métodos, propriedades, indexadores, operadores, construtores de instância, finalizadores e construtores estáticos) e membros de tipo com um número diferente de parâmetros de tipo são ignorados ao determinar o significado do namespace_or_type_name. Nota final
- Se
- Caso contrário, para cada namespace
N
, começando com o namespace no qual o namespace_or_type_name ocorre, continuando com cada namespace de inclusão (se houver) e terminando com o namespace global, as seguintes etapas são avaliadas até que uma entidade seja localizada:- Se
x
for zero eI
for o nome de um namespace noN
, então:- Se o local onde o namespace_or_type_name ocorre estiver incluído por uma declaração de namespace para
N
e a declaração de namespace contiver um extern_alias_directive ou using_alias_directive que associe o nomeI
a um namespace ou tipo, o namespace_or_type_name será ambíguo e ocorrerá um erro em tempo de compilação. - Caso contrário, o namespace_or_type_name refere-se ao namespace nomeado
I
emN
.
- Se o local onde o namespace_or_type_name ocorre estiver incluído por uma declaração de namespace para
- Caso contrário, se
N
contiver um tipo acessível com parâmetros nameI
ex
type, então:- Se
x
for zero e o local onde o namespace_or_type_name ocorre estiver incluído por uma declaração de namespace paraN
e a declaração de namespace contiver um extern_alias_directive ou using_alias_directive que associe o nomeI
a um namespace ou tipo, o namespace_or_type_name será ambíguo e ocorrerá um erro em tempo de compilação. - Caso contrário, o namespace_or_type_name refere-se ao tipo construído com os argumentos de tipo fornecidos.
- Se
- Caso contrário, se o local onde o namespace_or_type_name ocorre estiver incluído por uma declaração de namespace para
N
:- Se
x
for zero e a declaração de namespace contiver um extern_alias_directive ou using_alias_directive que associe o nomeI
a um namespace ou tipo importado, o namespace_or_type_name se refere a esse namespace ou tipo. - Caso contrário, se os namespaces importados pelos using_namespace_directives da declaração de namespace contiverem exatamente um tipo com parâmetros name
I
ex
type, o namespace_or_type_name se refere a esse tipo construído com os argumentos de tipo fornecidos. - Caso contrário, se os namespaces importados pelos using_namespace_directives da declaração de namespace contiverem mais de um tipo com parâmetros name
I
ex
type, o namespace_or_type_name será ambíguo e ocorrerá um erro.
- Se
- Se
- Caso contrário, o namespace_or_type_name é indefinido e ocorre um erro em tempo de compilação.
- Se
- Caso contrário, a namespace_or_type_name é da forma
N.I
ou da formaN.I<A₁, ..., Aₓ>
.N
é primeiramente resolvido como um namespace_or_type_name. Se a resolução do não for bem-sucedida, ocorrerá um erro em tempo deN
compilação. Caso contrário,N.I
ouN.I<A₁, ..., Aₓ>
é resolvido da seguinte forma:- Se
x
for zero eN
se referir a um namespace eN
contiver um namespace aninhado com nomeI
, o namespace_or_type_name se refere a esse namespace aninhado. - Caso contrário, se
N
se refere a um namespace eN
contém um tipo acessível com parâmetros nameI
ex
type, então o namespace_or_type_name refere-se a esse tipo construído com os argumentos de tipo fornecidos. - Caso contrário, se
N
se refere a uma classe (possivelmente construída) ou tipo struct eN
ou qualquer uma de suas classes base contém um tipo acessível aninhado com parâmetros de nomeI
ex
tipo, então o namespace_or_type_name refere-se a esse tipo construído com os argumentos de tipo fornecidos. Se houver mais de um desses tipos, o tipo declarado dentro do tipo mais derivado será selecionado.Nota: Se o significado de
N.I
está a ser determinado como parte da resolução da especificação da classe deN
base, então considera-se que a classe de base direta deN
é (object
§15.2.4.2). Nota final - Caso contrário,
N.I
é um namespace_or_type_name inválido e ocorre um erro em tempo de compilação.
- Se
Um namespace_or_type_name só pode fazer referência a uma classe estática (§15.2.2.4) se:
- A namespace_or_type_name é a
T
namespace_or_type_name da formaT.I
, ou - O namespace_or_type_name é o
T
numa typeof_expression (§12.8.18) da formatypeof(T)
7.8.2 Nomes não qualificados
Cada declaração de namespace e declaração de tipo tem um nome não qualificado determinado da seguinte maneira:
- Para uma declaração de namespace, o nome não qualificado é o qualified_identifier especificado na declaração.
- Para uma declaração de tipo sem type_parameter_list, o nome não qualificado é o identificador especificado na declaração.
- Para uma declaração de tipo com parâmetros de tipo K, o nome não qualificado é o identificador de especificado na declaração, seguido pelo generic_dimension_specifier (§12.8.18) para parâmetros de tipo K.
7.8.3 Nomes totalmente qualificados
Cada namespace e declaração de tipo tem um nome totalmente qualificado, que identifica exclusivamente o namespace ou declaração de tipo entre todos os outros dentro do programa. O nome totalmente qualificado de um namespace ou declaração de tipo com nome N
não qualificado é determinado da seguinte maneira:
- Se
N
for um membro do namespace global, seu nome totalmente qualificado seráN
. - Caso contrário, seu nome totalmente qualificado é
S.N
, 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 referir-se a duas entidades distintas. Em particular:
- É um erro para uma declaração de namespace e uma declaração de tipo ter o mesmo nome totalmente qualificado.
- É um erro que dois tipos diferentes de declarações de tipo tenham o mesmo nome totalmente qualificado (por exemplo, se uma declaração struct e uma declaração de classe tiverem o mesmo nome totalmente qualificado).
- É um erro que uma declaração de tipo sem o modificador parcial tenha o mesmo nome totalmente qualificado que outra declaração de tipo (§15.2.7).
Exemplo: O exemplo abaixo mostra várias declarações de namespace e tipo, juntamente com seus nomes totalmente qualificados associados.
class A {} // A namespace X // X { class B // X.B { class C {} // X.B.C } namespace Y // X.Y { class D {} // X.Y.D } } namespace X.Y // X.Y { class E {} // X.Y.E class G<T> // X.Y.G<> { class H {} // X.Y.G<>.H } class G<S,T> // X.Y.G<,> { class H<U> {} // X.Y.G<,>.H<> } }
Exemplo final
7.9 Gestão automática de memória
O C# emprega o gerenciamento automático de memória, que libera os desenvolvedores de alocar e liberar manualmente a memória ocupada por objetos. As políticas de gerenciamento automático de memória são implementadas por um coletor de lixo. O ciclo de vida de gerenciamento de memória de um objeto é o seguinte:
- Quando o objeto é criado, a memória é alocada para ele, o construtor é executado e o objeto é considerado dinâmico.
- Se nem o objeto nem qualquer um de seus campos de instância podem ser acessados por qualquer possível continuação da execução, além da execução de finalizadores, o objeto é considerado não mais em uso e torna-se elegível para finalização.
Nota: O compilador C# e o coletor de lixo podem optar por analisar o código para determinar quais referências a um objeto podem ser usadas no futuro. Por exemplo, se uma variável local que está no escopo é a única referência existente a um objeto, mas essa variável local nunca é referida em qualquer possível continuação da execução a partir do ponto de execução atual no procedimento, o coletor de lixo pode (mas não é obrigado a) tratar o objeto como não mais em uso. Nota final
- 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.
- Uma vez que o finalizador de um objeto é executado, se nem o objeto nem qualquer um de seus campos de instância podem ser acessados por qualquer possível continuação da execução, incluindo a execução de finalizadores, o objeto é considerado inacessível e o objeto se torna elegível para coleção.
Nota: Um objeto que anteriormente não podia ser acessado pode se tornar acessível novamente devido ao seu finalizador. Um exemplo disso é fornecido abaixo. Nota final
- Finalmente, em algum momento depois que o objeto se torna elegível para coleta, o coletor de lixo libera a memória associada a esse objeto.
O coletor de lixo mantém informações sobre o uso do objeto e usa essas informações para tomar decisões de gerenciamento de memória, como onde na memória localizar um objeto recém-criado, quando realocar um objeto e quando um objeto não está mais em uso ou inacessível.
Como outras linguagens que pressupõem a existência de um coletor de lixo, o C# foi projetado para que o coletor de lixo possa implementar uma ampla gama de políticas de gerenciamento de memória. C# não especifica uma restrição de tempo dentro desse período, nem uma ordem na qual os finalizadores são executados. Se os finalizadores são ou não executados como parte do encerramento do aplicativo é definido pela implementação (§7.2).
O comportamento do coletor de lixo pode ser controlado, até certo ponto, através de métodos estáticos na classe System.GC
. Essa classe pode ser usada para solicitar que uma coleção ocorra, finalizadores sejam executados (ou não executados) e assim por diante.
Exemplo: Como o coletor de lixo tem ampla latitude para decidir quando coletar objetos e executar finalizadores, uma implementação em conformidade pode produzir uma saída diferente da mostrada pelo código a seguir. O programa
class A { ~A() { Console.WriteLine("Finalize instance of A"); } } class B { object Ref; public B(object o) { Ref = o; } ~B() { Console.WriteLine("Finalize instance of B"); } } class Test { static void Main() { B b = new B(new A()); b = null; GC.Collect(); GC.WaitForPendingFinalizers(); } }
Cria uma instância de classe
A
e uma instância de classeB
. Estes objetos tornam-se elegíveis para a recolha de lixo quando é atribuído o valorb
à variávelnull
, uma vez que após este tempo é impossível para qualquer código escrito pelo utilizador aceder aos mesmos. A saída pode ser qualquerFinalize instance of A Finalize instance of B
ou
Finalize instance of B Finalize instance of A
porque a linguagem não impõe restrições à ordem em que os objetos são recolhidos.
Em casos sutis, a distinção entre "elegível para finalização" e "elegível para cobrança" pode ser importante. Por exemplo,
class A { ~A() { Console.WriteLine("Finalize instance of A"); } public void F() { Console.WriteLine("A.F"); Test.RefA = this; } } class B { public A Ref; ~B() { Console.WriteLine("Finalize instance of B"); Ref.F(); } } class Test { public static A RefA; public static B RefB; static void Main() { RefB = new B(); RefA = new A(); RefB.Ref = RefA; RefB = null; RefA = null; // A and B now eligible for finalization GC.Collect(); GC.WaitForPendingFinalizers(); // B now eligible for collection, but A is not if (RefA != null) { Console.WriteLine("RefA is not null"); } } }
No programa acima, se o coletor de lixo optar por executar o finalizador de antes do
A
finalizador deB
, então a saída deste programa pode ser:Finalize instance of A Finalize instance of B A.F RefA is not null
Observe que, embora a instância de
A
não estava em uso eA
o finalizador do foi 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. Neste caso, a execução do finalizador doB
fez com que uma instância que anteriormente não estava em uso, se tornasse acessível a partir daA
referênciaTest.RefA
ao vivo. Após a chamada paraWaitForPendingFinalizers
, a instância deB
é elegível para coleta, mas a instância deA
não é, devido à referênciaTest.RefA
.Exemplo final
7.10 Ordem de execução
A execução de um programa em C# prossegue de tal forma que os efeitos colaterais de cada thread de execução são preservados em pontos críticos de execução. Um efeito colateral é definido como uma leitura ou gravação de um campo volátil, uma gravação em uma variável não volátil, uma gravação em um recurso externo e o lançamento de uma exceção. Os pontos críticos de execução em que a ordem desses efeitos colaterais deve ser preservada são referências a campos voláteis (§15.5.4), lock
declarações (§13.13) e criação e terminação de threads. O ambiente de execução é livre para alterar a ordem de execução de um programa C#, sujeito às seguintes restrições:
- A dependência de dados é preservada dentro de um thread de execução. Ou seja, o valor de cada variável é calculado como se todas as instruções no thread fossem executadas na ordem original do programa.
- As regras de ordenação de inicialização são preservadas (§15.5.5, §15.5.6).
- A ordenação dos efeitos secundários é preservada no que diz respeito a leituras e gravações voláteis (§15.5.4). Além disso, o ambiente de execução não precisa avaliar parte de uma expressão se puder deduzir que o valor dessa expressão não é usado e que nenhum efeito colateral necessário é produzido (incluindo qualquer causado pela chamada de um método ou acesso a um campo volátil). Quando a execução do programa é interrompida por um evento assíncrono (como uma exceção lançada por outro thread), não é garantido que os efeitos colaterais observáveis sejam visíveis na ordem original do programa.
ECMA C# draft specification