7 Conceptos básicos
7.1 Inicio de la aplicación
Un programa se puede compilar como una biblioteca de clases que se va a usar como parte de otras aplicaciones o como una aplicación que se puede iniciar directamente. El mecanismo para determinar este modo de compilación es definido por la implementación y es externo a esta especificación.
Un programa compilado como una aplicación contendrá al menos un método que cumpla los requisitos siguientes:
- Tendrá el nombre
Main
. - Será
static
. - No será genérico.
- Se declarará en un tipo no genérico. Si el tipo que declara el método es un tipo anidado, ninguno de sus tipos envolventes puede ser genérico.
- Puede tener el
async
modificador siempre que el tipo de valor devuelto del método seaSystem.Threading.Tasks.Task
oSystem.Threading.Tasks.Task<int>
. - El tipo de valor devuelto será
void
,int
,System.Threading.Tasks.Task
oSystem.Threading.Tasks.Task<int>
. - No será un método parcial (§15.6.9) sin una implementación.
- La lista de parámetros debe estar vacía o tener un único parámetro de valor de tipo
string[]
.
Nota: Los métodos con el
async
modificador deben tener exactamente uno de los dos tipos devueltos especificados anteriormente para calificar como punto de entrada. Unasync void
método o unasync
método que devuelve un tipo await diferente, comoValueTask
oValueTask<int>
no se califica como punto de entrada. nota final
Si se declara más de un método calificado como punto de entrada dentro de un programa, se puede usar un mecanismo externo para especificar qué método se considera el punto de entrada real de la aplicación. Si se encuentra un método calificado que tiene un tipo de valor devuelto de int
ovoid
, cualquier método calificado que tenga un tipo de valor devuelto de o System.Threading.Tasks.Task
no se considere un método de punto de System.Threading.Tasks.Task<int>
entrada. Se trata de un error en tiempo de compilación para que un programa se compile como una aplicación sin exactamente un punto de entrada. Un programa compilado como una biblioteca de clases puede contener métodos que se calificarían como puntos de entrada de aplicación, pero la biblioteca resultante no tiene ningún punto de entrada.
Normalmente, los modificadores de acceso (§15.3.2) declarados (§7.5.2) de un método están determinados por los modificadores de acceso (§15.3.6) especificados en su declaración y, de forma similar, la accesibilidad declarada de un tipo viene determinada por los modificadores de acceso especificados en su declaración. Para que se pueda llamar a un método determinado de un tipo determinado, se podrá acceder al tipo y al miembro. Sin embargo, el punto de entrada de la aplicación es un caso especial. En concreto, el entorno de ejecución puede acceder al punto de entrada de la aplicación independientemente de su accesibilidad declarada e independientemente de la accesibilidad declarada de sus declaraciones de tipo envolventes.
Cuando el método de punto de entrada tiene un tipo de valor devuelto de System.Threading.Tasks.Task
o System.Threading.Tasks.Task<int>
, un compilador sintetizará un método de punto de entrada sincrónico que llame al método Main
correspondiente. El método sintetizado tiene parámetros y tipos devueltos basados en el Main
método :
- La lista de parámetros del método sintetizado es la misma que la lista de parámetros del
Main
método. - Si el tipo de valor devuelto del
Main
método esSystem.Threading.Tasks.Task
, el tipo de valor devuelto del método sintetizado es .void
- Si el tipo de valor devuelto del
Main
método esSystem.Threading.Tasks.Task<int>
, el tipo de valor devuelto del método sintetizado es .int
La ejecución del método sintetizado continúa de la siguiente manera:
- El método sintetizado llama al
Main
método , pasando sustring[]
valor de parámetro como argumento si elMain
método tiene este parámetro. - Si el
Main
método produce una excepción, el método sintetizado propaga la excepción. - De lo contrario, el punto de entrada sintetizado espera a que se complete la tarea devuelta, llamando a
GetAwaiter().GetResult()
en la tarea mediante el método de instancia sin parámetros o el método de extensión descrito por §C.3. Si se produce un error en la tarea,GetResult()
iniciará una excepción y esta excepción se propagará mediante el método sintetizado. - Para un método con un
Main
tipo de valor devuelto deSystem.Threading.Tasks.Task<int>
, si la tarea se completa correctamente, elint
valor devuelto porGetResult()
se devuelve desde el método sintetizado.
El punto de entrada efectivo de una aplicación es el punto de entrada declarado dentro del programa o el método sintetizado si se requiere uno como se ha descrito anteriormente. El tipo de valor devuelto del punto de entrada efectivo es, por lo tanto, siempre void
o int
.
Cuando se ejecuta una aplicación, se crea un nuevo dominio de aplicación. Pueden existir varias instancias diferentes de una aplicación en la misma máquina al mismo tiempo y cada una tiene su propio dominio de aplicación. Un dominio de aplicación permite el aislamiento de la aplicación actuando como contenedor para el estado de la aplicación. Un dominio de aplicación actúa como contenedor y límite para los tipos definidos en la aplicación y las bibliotecas de clases que usa. Los tipos cargados en un dominio de aplicación son distintos de los mismos tipos cargados en otro dominio de aplicación y las instancias de objetos no se comparten directamente entre dominios de aplicación. Por ejemplo, cada dominio de aplicación tiene su propia copia de variables estáticas para estos tipos y un constructor estático para un tipo se ejecuta como máximo una vez por dominio de aplicación. Las implementaciones son gratuitas para proporcionar mecanismos o directivas definidos por la implementación para la creación y destrucción de dominios de aplicación.
El inicio de la aplicación se produce cuando el entorno de ejecución llama al punto de entrada efectivo de la aplicación. Si el punto de entrada efectivo declara un parámetro, durante el inicio de la aplicación, la implementación garantizará que el valor inicial de ese parámetro sea una referencia no nula a una matriz de cadenas. Esta matriz constará de referencias no nulas a cadenas, denominadas parámetros de aplicación, que son determinados valores definidos por la implementación por el entorno host antes del inicio de la aplicación. La intención es proporcionar a la información de la aplicación determinada antes del inicio de la aplicación desde otro lugar del entorno hospedado.
Nota: En los sistemas que admiten una línea de comandos, los parámetros de aplicación corresponden a lo que se conoce generalmente como argumentos de línea de comandos. nota final
Si el tipo de valor devuelto del punto de entrada efectivo es int
, el valor devuelto de la invocación del método por el entorno de ejecución se usa en la finalización de la aplicación (§7.2).
Aparte de las situaciones enumeradas anteriormente, los métodos de punto de entrada se comportan como los que no son puntos de entrada en todos los aspectos. En concreto, si se invoca el punto de entrada en cualquier otro momento durante la vigencia de la aplicación, como por invocación de método normal, no hay ningún control especial del método: si hay un parámetro, puede tener un valor inicial de null
o unnull
valor que no hace referencia a una matriz que contiene referencias nulas. Del mismo modo, el valor devuelto del punto de entrada no tiene ninguna importancia especial que en la invocación del entorno de ejecución.
7.2 Terminación de la aplicación
La terminación de la aplicación devuelve el control al entorno de ejecución.
Si el tipo de valor devuelto del método de punto de entrada efectivo de la aplicación es y la ejecución se int
completa sin provocar una excepción, el valor del int
devuelto actúa como código de estado de finalización de la aplicación. El propósito de este código es permitir la comunicación de éxito o error en el entorno de ejecución. Si el tipo de valor devuelto del método de punto de entrada efectivo es y la ejecución se void
completa sin provocar una excepción, el código de estado de terminación es 0
.
Si el método de punto de entrada efectivo finaliza debido a una excepción (§21.4), el código de salida se define en la implementación. Además, la implementación puede proporcionar API alternativas para especificar el código de salida.
Si los finalizadores (§15.13) se ejecutan como parte de la terminación de la aplicación está definido por la implementación.
Nota: La implementación de .NET Framework realiza todos los esfuerzos razonables para llamar a finalizadores (§15.13) para todos sus objetos que aún no se han recopilado como elementos no utilizados, a menos que se haya suprimido dicha limpieza (por una llamada al método
GC.SuppressFinalize
de biblioteca , por ejemplo). nota final
7.3 Declaraciones
Las declaraciones de un programa de C# definen los elementos constituyentes del programa. Los programas de C# se organizan mediante espacios de nombres. Se presentan mediante declaraciones de espacio de nombres (§14), que pueden contener declaraciones de tipo y declaraciones de espacio de nombres anidadas. Las declaraciones de tipo (§14.7) se usan para definir clases (§15), estructuras (§16), interfaces (§18), enumeraciones (§19) y delegados (§20). Los tipos de miembros permitidos en una declaración de tipo dependen de la forma de la declaración de tipo. Por ejemplo, Las declaraciones de clase pueden contener declaraciones para constantes (§15.4), campos (§15.5), métodos (§15.6), propiedades (§15.7), eventos (§15.8), indexadores (§15.9) ), operadores (§15.10), constructores de instancia (§15.11), constructores estáticos (§15.12), finalizadores (§15.13) y tipos anidados (§15.3.9).
Una declaración define un nombre en el espacio de declaración al que pertenece la declaración. Se trata de un error en tiempo de compilación para tener dos o más declaraciones que introducen miembros con el mismo nombre en un espacio de declaración, excepto en los casos siguientes:
- Se permiten dos o más declaraciones de espacio de nombres con el mismo nombre en el mismo espacio de declaración. Estas declaraciones de espacio de nombres se agregan para formar un único espacio de nombres lógico y compartir un único espacio de declaración.
- Las declaraciones en programas independientes, pero en el mismo espacio de declaración de espacio de nombres pueden compartir el mismo nombre.
Nota: Sin embargo, estas declaraciones podrían introducir ambigüedades si se incluyen en la misma aplicación. nota final
- Se permiten dos o más métodos con el mismo nombre, pero se permiten firmas distintas en el mismo espacio de declaración (§7.6).
- Se permiten dos o más declaraciones de tipo con el mismo nombre, pero se permiten números distintos de parámetros de tipo en el mismo espacio de declaración (§7.8.2).
- Dos o más declaraciones de tipo con el modificador parcial en el mismo espacio de declaración pueden compartir el mismo nombre, el mismo número de parámetros de tipo y la misma clasificación (clase, estructura o interfaz). En este caso, las declaraciones de tipo contribuyen a un único tipo y se agregan a sí mismos para formar un espacio de declaración único (§15.2.7).
- Una declaración de espacio de nombres y una declaración de tipo en el mismo espacio de declaración pueden compartir el mismo nombre siempre que la declaración de tipo tenga al menos un parámetro de tipo (§7.8.2).
Hay varios tipos diferentes de espacios de declaración, como se describe en lo siguiente.
- Dentro de todas las unidades de compilación de un programa, namespace_member_declarations sin incluir namespace_declaration son miembros de un único espacio de declaración combinado denominado espacio de declaración global.
- Dentro de todas las unidades de compilación de un programa, namespace_member_declarations dentro de namespace_declarations que tienen el mismo nombre de espacio de nombres completo son miembros de un único espacio de declaración combinado.
- Cada compilation_unit y namespace_body tiene un espacio de declaración de alias. Cada extern_alias_directive y using_alias_directive del compilation_unit o namespace_body contribuye a un miembro al espacio de declaración de alias (§14.5.2).
- Cada declaración de interfaz, estructura o clase no parcial crea un nuevo espacio de declaración. Cada declaración de clase, estructura o interfaz parcial contribuye a un espacio de declaración compartido por todas las partes coincidentes del mismo programa (§16.2.4). Los nombres se introducen en este espacio de declaración a través de class_member_declarations, struct_member_declarations, interface_member_declarations o type_parameters. Excepto las declaraciones de constructor de instancia sobrecargada y las declaraciones de constructor estático, una clase o estructura no puede contener una declaración de miembro con el mismo nombre que la clase o estructura. Una clase, estructura o interfaz permite la declaración de métodos y indexadores sobrecargados. Además, una clase o estructura permite la declaración de constructores y operadores de instancia sobrecargados. Por ejemplo, una clase, estructura o interfaz puede contener varias declaraciones de método con el mismo nombre, siempre que estas declaraciones de método sean diferentes en su firma (§7.6). Tenga en cuenta que las clases base no contribuyen al espacio de declaración de una clase y las interfaces base no contribuyen al espacio de declaración de una interfaz. Por lo tanto, se permite que una clase o interfaz derivada declare un miembro con el mismo nombre que un miembro heredado. Se dice que este miembro oculta el miembro heredado.
- Cada declaración de delegado crea un nuevo espacio de declaración. Los nombres se introducen en este espacio de declaración a través de parámetros (fixed_parameters y parameter_arrays) y type_parameters.
- Cada declaración de enumeración crea un nuevo espacio de declaración. Los nombres se introducen en este espacio de declaración a través de enum_member_declarations.
- Cada declaración de método, declaración de propiedad, declaración de descriptor de acceso de propiedad, declaración de indexador, declaración de descriptor de acceso del indexador, declaración de operador, declaración de constructor de instancia, función anónima y función local crea un nuevo espacio de declaración denominado espacio de declaración de variable local. Los nombres se introducen en este espacio de declaración a través de parámetros (fixed_parameters y parameter_arrays) y type_parameters. El descriptor de acceso set para una propiedad o un indexador introduce el nombre
value
como parámetro. El cuerpo del miembro de función, función anónima o función local, si existe, se considera anidado dentro del espacio de declaración de variable local. Cuando un espacio de declaración de variable local y un espacio de declaración de variable local anidado contienen elementos con el mismo nombre, dentro del ámbito del nombre local anidado, el nombre local externo se oculta (§7.7.1) por el nombre local anidado. - Pueden producirse espacios de declaración de variables locales adicionales en declaraciones de miembro, funciones anónimas y funciones locales. Los nombres se introducen en estos espacios de declaración a través de s, declaration_expressions, declaration_statements y exception_specifiers. Los espacios de declaración de variables locales se pueden anidar, pero es un error para un espacio de declaración de variable local y un espacio de declaración de variable local anidado para contener elementos con el mismo nombre. Por lo tanto, dentro de un espacio de declaración anidado no es posible declarar una variable local, función local o constante con el mismo nombre que un parámetro, parámetro de tipo, variable local, función local o constante en un espacio de declaración envolvente. Es posible que dos espacios de declaración contengan elementos con el mismo nombre siempre y cuando ninguno de los espacios de declaración contenga el otro. Las construcciones siguientes crean espacios de declaración local:
- Cada variable_initializer en una declaración de campo y propiedad introduce su propio espacio de declaración de variable local, que no está anidado dentro de ningún otro espacio de declaración de variable local.
- El cuerpo de un miembro de función, una función anónima o una función local, si existe, crea un espacio de declaración de variable local que se considera anidado dentro del espacio de declaración de variables locales de la función.
- Cada constructor_initializer crea un espacio de declaración de variable local anidado dentro de la declaración del constructor de instancia. El espacio de declaración de variable local para el cuerpo del constructor se anida a su vez dentro de este espacio de declaración de variable local.
- Cada bloque, switch_block, specific_catch_clause, iteration_statement y using_statement crea un espacio de declaración de variable local anidado.
- Cada embedded_statement que no forma parte directamente de un statement_list crea un espacio de declaración de variable local anidada.
- Cada switch_section crea un espacio de declaración de variable local anidada. Sin embargo, las variables declaradas directamente dentro del statement_list de la switch_section (pero no dentro de un espacio de declaración de variables locales anidadas dentro de la statement_list) se agregan directamente al espacio de declaración de variables locales del switch_block envolvente, en lugar del del switch_section.
- La traducción sintáctica de una query_expression (§12.20.3) puede introducir una o varias expresiones lambda. Como funciones anónimas, cada una de estas crea un espacio de declaración de variable local como se ha descrito anteriormente.
- Cada bloque o switch_block crea un espacio de declaración independiente para las etiquetas. Los nombres se introducen en este espacio de declaración a través de labeled_statements y se hace referencia a los nombres a través de goto_statements. El espacio de declaración de etiqueta de un bloque incluye los bloques anidados. Por lo tanto, dentro de un bloque anidado no es posible declarar una etiqueta con el mismo nombre que una etiqueta en un bloque envolvente.
Nota: El hecho de que las variables declaradas directamente dentro de un switch_section se agregan al espacio de declaración de variable local de la switch_block en lugar del switch_section pueden dar lugar a código sorprendente. En el ejemplo siguiente, la variable
y
local está en el ámbito dentro de la sección switch para el caso predeterminado, a pesar de la declaración que aparece en la sección switch para el caso 0. La variablez
local no está en el ámbito dentro de la sección switch del caso predeterminado, ya que se introduce en el espacio de declaración de variable local para la sección switch en la que se produce la declaración.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
El orden textual en el que se declaran los nombres no suele ser significativo. En concreto, el orden textual no es significativo para la declaración y el uso de espacios de nombres, constantes, métodos, propiedades, eventos, indexadores, operadores, constructores de instancias, finalizadores, constructores estáticos y tipos. El orden de declaración es significativo de las siguientes maneras:
- El orden de declaración para las declaraciones de campo determina el orden en el que se ejecutan sus inicializadores (si los hay) (§15.5.6.2, §15.5.6.3).
- Las variables locales se definirán antes de que se usen (§7.7).
- El orden de declaración de las declaraciones de miembro de enumeración (§19.4) es significativo cuando se omiten los valores de constant_expression .
Ejemplo: el espacio de declaración de un espacio de nombres es "abierto" y dos declaraciones de espacio de nombres con el mismo nombre completo contribuyen al mismo espacio de declaración. Por ejemplo
namespace Megacorp.Data { class Customer { ... } } namespace Megacorp.Data { class Order { ... } }
Las dos declaraciones de espacio de nombres anteriores contribuyen al mismo espacio de declaración, en este caso declarando dos clases con los nombres
Megacorp.Data.Customer
completos yMegacorp.Data.Order
. Dado que las dos declaraciones contribuyen al mismo espacio de declaración, habría provocado un error en tiempo de compilación si cada una contenía una declaración de una clase con el mismo nombre.ejemplo final
Nota: Como se especificó anteriormente, el espacio de declaración de un bloque incluye los bloques anidados. Por lo tanto, en el ejemplo siguiente, los
F
métodos yG
producen un error en tiempo de compilación porque el nombrei
se declara en el bloque externo y no se puede volver a declarar en el bloque interno. Sin embargo, losH
métodos yI
son válidos, ya que los dosi
se declaran en bloques no anidados independientes.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 Miembros
7.4.1 General
Los espacios de nombres y los tipos tienen miembros.
Nota: Los miembros de una entidad están disponibles con carácter general mediante el uso de un nombre completo que comienza con una referencia a la entidad, seguido de un token "
.
", seguido del nombre del miembro. nota final
Los miembros de un tipo se declaran en la declaración de tipo o se heredan de la clase base del tipo. Cuando un tipo hereda de una clase base, todos los miembros de la clase base, excepto los constructores de instancia, los finalizadores y los constructores estáticos se convierten en miembros del tipo derivado. La accesibilidad declarada de un miembro de clase base no controla si el miembro se hereda, la herencia se extiende a cualquier miembro que no sea un constructor de instancia, constructor estático o finalizador.
Nota: Sin embargo, es posible que un miembro heredado no sea accesible en un tipo derivado, por ejemplo debido a su accesibilidad declarada (§7.5.2). nota final
7.4.2 Miembros del espacio de nombres
Los espacios de nombres y los tipos que no tienen espacio de nombres envolvente son miembros del espacio de nombres global. Esto corresponde directamente a los nombres declarados en el espacio de declaración global.
Los espacios de nombres y los tipos declarados en un espacio de nombres son miembros de ese espacio de nombres. Esto corresponde directamente a los nombres declarados en el espacio de declaración del espacio de nombres.
Los espacios de nombres no tienen restricciones de acceso. No es posible declarar espacios de nombres privados, protegidos o internos, y los nombres de los espacios de nombres siempre son accesibles públicamente.
7.4.3 Miembros de Struct
Los miembros de una estructura son los miembros declarados en la estructura y los miembros heredados de la clase System.ValueType
base directa del struct y la clase object
base indirecta .
Los miembros de un tipo simple corresponden directamente a los miembros del tipo de estructura con alias por el tipo simple (§8.3.5).
7.4.4 Miembros de enumeración
Los miembros de una enumeración son las constantes declaradas en la enumeración y los miembros heredados de la clase System.Enum
base directa de la enumeración y las clases base indirectas System.ValueType
y object
.
7.4.5 Miembros de la clase
Los miembros de una clase son los miembros declarados en la clase y los miembros heredados de la clase base (excepto la clase object
que no tiene ninguna clase base). Los miembros heredados de la clase base incluyen las constantes, campos, métodos, propiedades, eventos, indexadores, operadores y tipos de la clase base, pero no los constructores de instancia, finalizadores y constructores estáticos de la clase base. Los miembros de clase base se heredan sin tener en cuenta su accesibilidad.
Una declaración de clase puede contener declaraciones de constantes, campos, métodos, propiedades, eventos, indexadores, operadores, constructores de instancia, finalizadores, constructores estáticos y tipos.
Los miembros de object
(§8.2.3) y string
(§8.2.5) corresponden directamente a los miembros de los tipos de clase que alias.
7.4.6 Miembros de la interfaz
Los miembros de una interfaz son los miembros declarados en la interfaz y en todas las interfaces base de la interfaz.
Nota: Los miembros de la clase
object
no son, estrictamente hablando, miembros de ninguna interfaz (§18.4). Sin embargo, los miembros de la claseobject
están disponibles a través de la búsqueda de miembros en cualquier tipo de interfaz (§12.5). nota final
7.4.7 Miembros de matriz
Los miembros de una matriz son los miembros heredados de la clase System.Array
.
7.4.8 Miembros delegados
Un delegado hereda miembros de la clase System.Delegate
. Además, contiene un método denominado Invoke
con el mismo tipo de valor devuelto y lista de parámetros especificado en su declaración (§20.2). Una invocación de este método se comportará de forma idéntica a una invocación de delegado (§20.6) en la misma instancia de delegado.
Una implementación puede proporcionar miembros adicionales, ya sea a través de la herencia o directamente en el propio delegado.
7.5 Acceso a miembros
7.5.1 General
Las declaraciones de miembros permiten el control sobre el acceso de los miembros. La accesibilidad de un miembro se establece mediante la accesibilidad declarada (§7.5.2) del miembro combinado con la accesibilidad del tipo contenedor inmediatamente, si existe.
Cuando se permite el acceso a un miembro determinado, se dice que el miembro es accesible. Por el contrario, cuando no se permite el acceso a un miembro determinado, se dice que el miembro es inaccesible. Se permite el acceso a un miembro cuando la ubicación textual en la que tiene lugar el acceso se incluye en el dominio de accesibilidad (§7.5.3) del miembro.
7.5.2 Accesibilidad declarada
La accesibilidad declarada de un miembro puede ser una de las siguientes:
- Público, que se selecciona mediante la inclusión de un
public
modificador en la declaración de miembro. El significado intuitivo depublic
es "acceso no limitado". - Protegido, que se selecciona mediante la inclusión de un
protected
modificador en la declaración de miembro. El significado intuitivo deprotected
es "acceso limitado a la clase o tipos contenedores derivados de la clase contenedora". - Interno, que se selecciona mediante la inclusión de un
internal
modificador en la declaración de miembro. El significado intuitivo deinternal
es "acceso limitado a este ensamblado". - Interno protegido, que se selecciona mediante la inclusión de un
protected
y uninternal
modificador en la declaración de miembro. El significado intuitivo de es "accesible dentro deprotected internal
este ensamblado, así como tipos derivados de la clase contenedora". - Protegido privado, que se selecciona mediante la inclusión de un
private
modificador y y enprotected
la declaración de miembro. El significado intuitivo de es "accesible dentro deprivate protected
este ensamblado por la clase contenedora y los tipos derivados de la clase contenedora". - Privado, que se selecciona mediante la inclusión de un
private
modificador en la declaración de miembro. El significado intuitivo deprivate
es "acceso limitado al tipo contenedor".
En función del contexto en el que tiene lugar una declaración de miembro, solo se permiten determinados tipos de accesibilidad declarados. Además, cuando una declaración de miembro no incluye ningún modificador de acceso, el contexto en el que tiene lugar la declaración determina la accesibilidad declarada predeterminada.
- Los espacios de nombres han
public
declarado implícitamente la accesibilidad. No se permiten modificadores de acceso en declaraciones de espacio de nombres. - Los tipos declarados directamente en unidades de compilación o espacios de nombres (en lugar de dentro de otros tipos) pueden tener
public
ointernal
declarar la accesibilidad y el valor predeterminado parainternal
la accesibilidad declarada. - Los miembros de clase pueden tener cualquiera de los tipos permitidos de accesibilidad declarada y el valor predeterminado para
private
declarar la accesibilidad.Nota: Un tipo declarado como miembro de una clase puede tener cualquiera de los tipos permitidos de accesibilidad declarada, mientras que un tipo declarado como miembro de un espacio de nombres solo
public
puede tener ointernal
declarar accesibilidad. nota final - Los miembros de Struct pueden tener
public
la accesibilidad declarada ,internal
o yprivate
el valor predeterminado paraprivate
declarar la accesibilidad porque las estructuras están selladas implícitamente. Los miembros de estructura introducidos enstruct
(es decir, no heredados por esa estructura) no pueden tenerprotected
accesibilidad declarada ,protected internal
oprivate protected
.Nota: Un tipo declarado como miembro de un struct puede tener
public
accesibilidad declarada ,internal
oprivate
, mientras que un tipo declarado como miembro de un espacio de nombres solopublic
puede tener ointernal
declarar accesibilidad. nota final - Los miembros de interfaz han
public
declarado implícitamente la accesibilidad. No se permite ningún modificador de acceso en declaraciones de miembro de interfaz. - Los miembros de enumeración han
public
declarado implícitamente la accesibilidad. No se permiten modificadores de acceso en declaraciones de miembro de enumeración.
7.5.3 Dominios de accesibilidad
El dominio de accesibilidad de un miembro consta de las secciones (posiblemente desasociadas) del texto del programa en el que se permite el acceso al miembro. Para definir el dominio de accesibilidad de un miembro, se dice que un miembro es de nivel superior si no se declara dentro de un tipo y se dice que un miembro está anidado si se declara dentro de otro tipo. Además, el texto del programa de un programa se define como todo el texto contenido en todas las unidades de compilación del programa, y el texto del programa de un tipo se define como todo el texto contenido en los type_declarations de ese tipo (incluidos, posiblemente, tipos anidados dentro del tipo).
El dominio de accesibilidad de un tipo predefinido (como object
, int
o double
) es ilimitado.
El dominio de accesibilidad de un tipo T
sin enlazar de nivel superior (§8.4.4) que se declara en un programa P
se define de la siguiente manera:
- Si la accesibilidad declarada de
T
es pública, el dominio de accesibilidad de es el texto delT
programa deP
y cualquier programa que haga referencia aP
. - Si la accesibilidad declarada de
T
es interna, el dominio de accesibilidad deT
es el texto del programa deP
.
Nota: A partir de estas definiciones, sigue que el dominio de accesibilidad de un tipo unbound de nivel superior siempre es al menos el texto de programa del programa en el que se declara ese tipo. nota final
El dominio de accesibilidad de un tipo T<A₁, ..., Aₑ>
construido es la intersección del dominio de accesibilidad del tipo T
genérico no enlazado y los dominios de accesibilidad de los argumentos A₁, ..., Aₑ
de tipo .
El dominio de accesibilidad de un miembro M
anidado declarado en un tipo T
dentro de un programa P
se define de la siguiente manera (teniendo en cuenta que es posible que M
sea un tipo):
- Si la accesibilidad declarada de
M
espublic
, el dominio de accesibilidad deM
es el dominio de accesibilidad deT
. - Si la accesibilidad declarada de
M
es , vamosprotected internal
a ser la unión del texto del programa deD
y el texto del programa de cualquier tipo derivado deP
, que se declara fueraT
P
de . El dominio de accesibilidad deM
es la intersección del dominio de accesibilidad deT
conD
. - Si la accesibilidad declarada de
M
es , vamosprivate protected
a ser la intersección del texto del programa deD
y el texto del programa deP
y cualquier tipo derivado deT
T
. El dominio de accesibilidad deM
es la intersección del dominio de accesibilidad deT
conD
. - Si la accesibilidad declarada de
M
es , vamosprotected
a ser la unión del texto del programa deD
y el texto del programa de cualquier tipo derivado deT
T
. El dominio de accesibilidad deM
es la intersección del dominio de accesibilidad deT
conD
. - Si la accesibilidad declarada de
M
esinternal
, el dominio de accesibilidad deM
es la intersección del dominio de accesibilidad deT
con el texto de programa deP
. - Si la accesibilidad declarada de
M
esprivate
, el dominio de accesibilidad deM
es el texto de programa deT
.
Nota: A partir de estas definiciones, sigue que el dominio de accesibilidad de un miembro anidado siempre es al menos el texto del programa del tipo en el que se declara el miembro. Además, sigue que el dominio de accesibilidad de un miembro nunca es más inclusivo que el dominio de accesibilidad del tipo en el que se declara el miembro. nota final
Nota: En términos intuitivos, cuando se accede a un tipo o miembro
M
, se evalúan los pasos siguientes para asegurarse de que se permite el acceso:
- En primer lugar, si
M
se declara dentro de un tipo (en lugar de una unidad de compilación o un espacio de nombres), se produce un error en tiempo de compilación si ese tipo no es accesible.- A continuación, si
M
espublic
, se permite el acceso.- De lo contrario, si
M
esprotected internal
, se permite el acceso si se produce dentro del programa en el queM
se declara o si se produce dentro de una clase derivada de la clase en la queM
se declara y tiene lugar a través del tipo de clase derivada (§7.5.4).- De lo contrario, si
M
esprotected
, se permite el acceso si se produce dentro de la clase en la queM
se declara o si se produce dentro de una clase derivada de la clase en la queM
se declara y tiene lugar a través del tipo de clase derivada (§7.5.4).- De lo contrario, si
M
esinternal
, se permite el acceso si se produce dentro del programa en el queM
se declara.- De lo contrario, si
M
esprivate
, se permite el acceso si se produce dentro del tipo en el queM
se declara.- De lo contrario, el tipo o miembro no es accesible y se produce un error en tiempo de compilación. nota final
Ejemplo: en el código siguiente
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; } }
las clases y los miembros tienen los siguientes dominios de accesibilidad:
- El dominio de accesibilidad de
A
yA.X
es ilimitado.- El dominio de accesibilidad de
A.Y
, ,B
,B.X
B.Y
,B.C
,B.C.X
, yB.C.Y
es el texto del programa del programa contenedor.- El dominio de accesibilidad de
A.Z
es el texto del programa deA
.- El dominio de accesibilidad de y
B.Z
es el texto delB.D
programa deB
, incluido el texto del programa deB.C
yB.D
.- El dominio de accesibilidad de
B.C.Z
es el texto del programa deB.C
.- El dominio de accesibilidad de y
B.D.X
es el texto delB.D.Y
programa deB
, incluido el texto del programa deB.C
yB.D
.- El dominio de accesibilidad de
B.D.Z
es el texto del programa deB.D
. Como se muestra en el ejemplo, el dominio de accesibilidad de un miembro nunca es mayor que el de un tipo contenedor. Por ejemplo, aunque todos losX
miembros tengan accesibilidad declarada pública, todos los dominios deA.X
accesibilidad que están restringidos por un tipo contenedor.ejemplo final
Como se describe en §7.4, todos los miembros de una clase base, excepto los constructores de instancia, los finalizadores y los constructores estáticos, se heredan por tipos derivados. Esto incluye incluso miembros privados de una clase base. Sin embargo, el dominio de accesibilidad de un miembro privado incluye solo el texto del programa del tipo en el que se declara el miembro.
Ejemplo: en el código siguiente
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 } }
la
B
clase hereda el miembrox
privado de laA
clase . Dado que el miembro es privado, solo es accesible dentro del class_body deA
. Por lo tanto, el acceso ab.x
se realiza correctamente en elA.F
método , pero se produce un error en elB.F
método .ejemplo final
7.5.4 Acceso protegido
Cuando se accede a un protected
miembro de instancia o private protected
fuera del texto del programa de la clase en la que se declara y cuando se accede a un protected internal
miembro de instancia fuera del texto del programa en el que se declara, el acceso tendrá lugar dentro de una declaración de clase que deriva de la clase en la que se declara. Además, se requiere el acceso a través de una instancia de ese tipo de clase derivada o de un tipo de clase construido a partir de él. Esta restricción impide que una clase derivada acceda a miembros protegidos de otras clases derivadas, incluso cuando los miembros se heredan de la misma clase base.
Let B
be a base class that declare a protected instance member M
y let be D
a class that derives from B
. Dentro de la class_body de D
, el acceso a M
puede adoptar una de las formas siguientes:
- Un type_name o primary_expression sin calificar
M
- Un
D
. - Un primary_expression del formulario
base.M
. - Primary_expression
]
Además de estas formas de acceso, una clase derivada puede acceder a un constructor de instancia protegida de una clase base en un constructor_initializer (§15.11.2).
Ejemplo: en el código siguiente
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
de , es posible tener accesox
a través de instancias de yA
B
, ya que, en cualquier caso, el acceso tiene lugar a través de una instancia deA
o una clase derivada deA
. Sin embargo, dentroB
de , no es posible accederx
a través de una instancia deA
, ya queA
no deriva deB
.ejemplo final
Ejemplo:
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"; } }
Aquí se permiten las tres asignaciones a
x
porque todas tienen lugar a través de instancias de tipos de clase construidos a partir del tipo genérico.ejemplo final
Nota: El dominio de accesibilidad (§7.5.3) de un miembro protegido declarado en una clase genérica incluye el texto del programa de todas las declaraciones de clase derivadas de cualquier tipo construido a partir de esa clase genérica. En el ejemplo:
class C<T> { protected static T x; } class D : C<string> { static void Main() { C<int>.x = 5; } }
la referencia al
protected
miembroC<int>.x
de esD
válida aunque la claseD
derive deC<string>
. nota final
7.5.5 Restricciones de accesibilidad
Varias construcciones en el lenguaje C# requieren que un tipo sea al menos tan accesible como miembro u otro tipo. Se dice que un tipo T
es al menos tan accesible como miembro o tipo M
si el dominio de accesibilidad de T
es un superconjunto del dominio de accesibilidad de M
. En otras palabras, T
es al menos tan accesible como M
si T
fuera accesible en todos los contextos en los que M
es accesible.
Existen las siguientes restricciones de accesibilidad:
- La clase base directa de un tipo de clase debe ser al menos tan accesible como el propio tipo de clase.
- Las interfaces base explícitas de un tipo de interfaz deben ser al menos tan accesibles como el propio tipo de interfaz.
- El tipo de valor devuelto y los tipos de parámetro de un tipo delegado deben ser al menos tan accesibles como el propio tipo delegado.
- El tipo de una constante debe ser al menos tan accesible como la propia constante.
- El tipo de un campo debe ser al menos tan accesible como el propio campo.
- El tipo de valor devuelto y los tipos de parámetro de un método deben ser al menos tan accesibles como el propio método.
- El tipo de una propiedad debe ser al menos tan accesible como la propia propiedad.
- El tipo de evento debe ser al menos tan accesible como el propio evento.
- El tipo y los tipos de parámetro de un indexador deben ser al menos tan accesibles como el propio indexador.
- El tipo de valor devuelto y los tipos de parámetro de un operador deben ser al menos tan accesibles como el propio operador.
- Los tipos de parámetro de un constructor de instancia deben ser al menos tan accesibles como el propio constructor de instancia.
- Una restricción de tipo de clase o interfaz en un parámetro de tipo debe ser al menos tan accesible como el miembro que declara la restricción.
Ejemplo: en el código siguiente
class A {...} public class B: A {...}
la
B
clase da como resultado un error en tiempo de compilación porqueA
no es al menos tan accesible comoB
.ejemplo final
Ejemplo: Del mismo modo, en el código siguiente
class A {...} public class B { A F() {...} internal A G() {...} public A H() {...} }
el
H
método daB
como resultado un error en tiempo de compilación porque el tipoA
de valor devuelto no es al menos tan accesible como el método .ejemplo final
7.6 Firmas y sobrecarga
Los métodos, constructores de instancias, indizadores y operadores se caracterizan por sus firmas:
- La firma de un método consta del nombre del método, el número de parámetros de tipo y el modo de paso de parámetros y tipo de cada uno de sus parámetros, considerado en el orden de izquierda a derecha. Para estos fines, cualquier parámetro de tipo del método que se produce en el tipo de un parámetro se identifica no por su nombre, sino por su posición ordinal en la lista de parámetros de tipo del método. La firma de un método específicamente no incluye el tipo de valor devuelto, los nombres de parámetro, los nombres de parámetro de tipo, las restricciones de parámetro de tipo, los
params
modificadores de parámetro othis
, ni si los parámetros son obligatorios o opcionales. - La firma de un constructor de instancia consta del tipo y el modo de paso de parámetros de cada uno de sus parámetros, considerado en el orden de izquierda a derecha. La firma de un constructor de instancia específicamente no incluye el
params
modificador que se puede especificar para el parámetro más adecuado, ni si los parámetros son obligatorios o opcionales. - La firma de un indexador consta del tipo de cada uno de sus parámetros, considerado en el orden de izquierda a derecha. La firma de un indexador no incluye específicamente el tipo de elemento, ni incluye el
params
modificador que se puede especificar para el parámetro más adecuado, ni si los parámetros son obligatorios o opcionales. - La firma de un operador consta del nombre del operador y del tipo de cada uno de sus parámetros, considerado en el orden de izquierda a derecha. La firma de un operador específicamente no incluye el tipo de resultado.
- La firma de un operador de conversión consta del tipo de origen y del tipo de destino. La clasificación implícita o explícita de un operador de conversión no forma parte de la firma.
- Dos firmas del mismo tipo de miembro (método, constructor de instancia, indizador o operador) se consideran las mismas firmas si tienen el mismo nombre, número de parámetros de tipo, número de parámetros y modos de paso de parámetros, y existe una conversión de identidad entre los tipos de sus parámetros correspondientes (§10.2.2).
Las firmas son el mecanismo de habilitación para sobrecargar miembros en clases, estructuras e interfaces:
- La sobrecarga de métodos permite que una clase, estructura o interfaz declare varios métodos con el mismo nombre, siempre que sus firmas sean únicas dentro de esa clase, estructura o interfaz.
- La sobrecarga de constructores de instancia permite que una clase o estructura declare varios constructores de instancia, siempre que sus firmas sean únicas dentro de esa clase o estructura.
- La sobrecarga de indizadores permite que una clase, estructura o interfaz declare varios indexadores, siempre que sus firmas sean únicas dentro de esa clase, estructura o interfaz.
- La sobrecarga de operadores permite que una clase o estructura declare varios operadores con el mismo nombre, siempre que sus firmas sean únicas dentro de esa clase o estructura.
Aunque in
los modificadores de parámetro , out
y ref
se consideran parte de una firma, los miembros declarados en un solo tipo no pueden diferir en la firma únicamente por in
, out
y ref
. Se produce un error en tiempo de compilación si dos miembros se declaran en el mismo tipo con firmas que serían iguales si todos los parámetros de ambos métodos con out
o in
modificadores se cambiaran a ref
modificadores. Para otros fines de coincidencia de firmas (por ejemplo, ocultar o invalidar), in
, out
y ref
se consideran parte de la firma y no coinciden entre sí.
Nota: Esta restricción es permitir que los programas de C# se traduzcan fácilmente para ejecutarse en Common Language Infrastructure (CLI), que no proporciona una manera de definir métodos que difieren únicamente en
in
,out
yref
. nota final
Los tipos object
y dynamic
no se distinguen al comparar firmas. Por lo tanto, los miembros declarados en un solo tipo cuyas firmas solo difieren reemplazando object
por dynamic
no se permiten.
Ejemplo: en el ejemplo siguiente se muestra un conjunto de declaraciones de método sobrecargadas junto con sus firmas.
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 }
Tenga en cuenta que los
in
modificadores de parámetro ,out
yref
(§15.6.2) forman parte de una firma. Por lo tanto,F(int)
,F(in int)
,F(out int)
yF(ref int)
son todas las firmas únicas. Sin embargo,F(in int)
,F(out int)
yF(ref int)
no se pueden declarar dentro de la misma interfaz porque sus firmas difieren únicamente porin
,out
yref
. Además, tenga en cuenta que el tipo de valor devuelto y elparams
modificador no forman parte de una firma, por lo que no es posible sobrecargar únicamente en función del tipo de valor devuelto o en la inclusión o exclusión delparams
modificador. Por lo tanto, las declaraciones de los métodosF(int)
eF(params string[])
identificados anteriormente generan un error en tiempo de compilación. ejemplo final
7.7 Ámbitos
7.7.1 General
El ámbito de un nombre es la región del texto del programa dentro del cual es posible hacer referencia a la entidad declarada por el nombre sin cualificación del nombre. Los ámbitos se pueden anidar y un ámbito interno puede volver a declarar el significado de un nombre desde un ámbito externo. (Sin embargo, esto no quita la restricción impuesta por §7.3 que dentro de un bloque anidado no es posible declarar una variable local o una constante local con el mismo nombre que una variable local o una constante local en un bloque envolvente). A continuación, se dice que el nombre del ámbito externo está oculto en la región del texto del programa cubierto por el ámbito interno y el acceso al nombre externo solo es posible si califica el nombre.
El ámbito de un miembro de espacio de nombres declarado por un namespace_member_declaration (§14.6) sin incluir namespace_declaration es todo el texto del programa.
El ámbito de un miembro de espacio de nombres declarado por un namespace_member_declaration dentro de un namespace_declaration cuyo nombre completo es
N
, es el namespace_body de cada namespace_declaration cuyo nombre completo esN
o comienza porN
, seguido de un punto.El ámbito de un nombre definido por un extern_alias_directive (§14.4) se extiende a través de los using_directives, global_attributes y namespace_member_declarations de su compilation_unit o namespace_body. Un extern_alias_directive no contribuye a ningún nuevo miembro al espacio de declaración subyacente. En otras palabras, un extern_alias_directive no es transitivo, sino que afecta solo al compilation_unit o namespace_body en el que se produce.
El ámbito de un nombre definido o importado por un using_directive (§14.5) se extiende a través del global_attributes y namespace_member_declarationde la compilation_unit o namespace_body en el que se produce el using_directive. Un using_directive puede hacer que cero o más nombres de espacio de nombres o tipos estén disponibles dentro de un compilation_unit o namespace_body determinado, pero no contribuye a ningún nuevo miembro al espacio de declaración subyacente. En otras palabras, un using_directive no es transitivo, sino que solo afecta a la compilation_unit o namespace_body en la que se produce.
El ámbito de un parámetro de tipo declarado por un type_parameter_list en un class_declaration (§15.2) es el class_base, type_parameter_constraints_clauses y class_body de ese class_declaration.
Nota: A diferencia de los miembros de una clase, este ámbito no se extiende a las clases derivadas. nota final
El ámbito de un parámetro de tipo declarado por un type_parameter_list en un struct_declaration (§16.2) es el struct_interfaces, type_parameter_constraints_clauses y struct_body de esa struct_declaration.
El ámbito de un parámetro de tipo declarado por un type_parameter_list en un interface_declaration (§18.2) es el interface_base, type_parameter_constraints_clauses y interface_body de esa interface_declaration.
El ámbito de un parámetro de tipo declarado por un type_parameter_list en un delegate_declaration (§20.2) es el return_type, parameter_list y type_parameter_constraints_clausede ese delegate_declaration.
El ámbito de un parámetro de tipo declarado por un type_parameter_list en un method_declaration (§15.6.1) es el method_declaration.
El ámbito de un miembro declarado por un class_member_declaration (§15.3.1) es el class_body en el que se produce la declaración. Además, el ámbito de un miembro de clase se extiende a la class_body de esas clases derivadas que se incluyen en el dominio de accesibilidad (§7.5.3) del miembro.
El ámbito de un miembro declarado por un struct_member_declaration (§16.3) es el struct_body en el que se produce la declaración.
El ámbito de un miembro declarado por un enum_member_declaration (§19.4) es el enum_body en el que se produce la declaración.
El ámbito de un parámetro declarado en un method_declaration (§15.6) es el method_body o ref_method_body de esa method_declaration.
El ámbito de un parámetro declarado en un indexer_declaration (§15.9) es el indexer_body de ese indexer_declaration.
El ámbito de un parámetro declarado en un operator_declaration (§15.10) es el operator_body de ese operator_declaration.
El ámbito de un parámetro declarado en un constructor_declaration (§15.11) es el constructor_initializer y el bloque de esa constructor_declaration.
El ámbito de un parámetro declarado en un lambda_expression (§12.19) es el lambda_expression_body de ese lambda_expression.
El ámbito de un parámetro declarado en un anonymous_method_expression (§12.19) es el bloque de ese anonymous_method_expression.
El ámbito de una etiqueta declarada en un labeled_statement (§13.5) es el bloque en el que se produce la declaración.
El ámbito de una variable local declarada en un local_variable_declaration (§13.6.2) es el bloque en el que se produce la declaración.
El ámbito de una variable local declarada en un switch_block de una instrucción (
switch
) es el switch_block.El ámbito de una variable local declarada en un for_initializer de una
for
instrucción (§13.9.4) es el for_initializer, for_condition, for_iterator y embedded_statement de lafor
instrucción.El ámbito de una constante local declarada en un local_constant_declaration (§13.6.3) es el bloque en el que se produce la declaración. Se trata de un error en tiempo de compilación para hacer referencia a una constante local en una posición textual que precede a su constant_declarator.
El ámbito de una variable declarada como parte de un foreach_statement, using_statement, lock_statement o query_expression viene determinado por la expansión de la construcción especificada.
Dentro del ámbito de un espacio de nombres, una clase, una estructura o un miembro de enumeración, es posible hacer referencia al miembro en una posición textual que precede a la declaración del miembro.
Ejemplo:
class A { void F() { i = 1; } int i = 0; }
Aquí, es válido para
F
hacer referencia ai
antes de que se declare.ejemplo final
Dentro del ámbito de una variable local, es un error en tiempo de compilación para hacer referencia a la variable local en una posición textual que precede a su declarador.
Ejemplo:
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 } }
En el
F
método anterior, la primera asignación ai
específicamente no hace referencia al campo declarado en el ámbito externo. En su lugar, hace referencia a la variable local y da como resultado un error en tiempo de compilación porque precede textualmente a la declaración de la variable. En elG
método , el uso dej
en el inicializador para la declaración dej
es válido porque el uso no precede al declarador. En elH
método , un declarador posterior hace referencia correctamente a una variable local declarada en un declarador anterior dentro de la misma local_variable_declaration.ejemplo final
Nota: Las reglas de ámbito de las variables locales y las constantes locales están diseñadas para garantizar que el significado de un nombre usado en un contexto de expresión siempre sea el mismo dentro de un bloque. Si el ámbito de una variable local se extendiera solo desde su declaración hasta el final del bloque, en el ejemplo anterior, la primera asignación se asignaría a la variable de instancia y la segunda asignación se asignaría a la variable local, lo que posiblemente daría lugar a errores en tiempo de compilación si las instrucciones del bloque se reorganizaban más adelante).
El significado de un nombre dentro de un bloque puede diferir en función del contexto en el que se usa el nombre. En el ejemplo
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" } }
el nombre
A
se usa en un contexto de expresión para hacer referencia a la variableA
local y en un contexto de tipo para hacer referencia a la claseA
.nota final
7.7.2 Ocultación de nombres
7.7.2.1 General
El ámbito de una entidad normalmente abarca más texto del programa que el espacio de declaración de la entidad. En concreto, el ámbito de una entidad puede incluir declaraciones que introducen nuevos espacios de declaración que contienen entidades con el mismo nombre. Estas declaraciones hacen que la entidad original se oculte. Por el contrario, se dice que una entidad es visible cuando no está oculta.
La ocultación de nombres se produce cuando los ámbitos se superponen a través del anidamiento y cuando los ámbitos se superponen a través de la herencia. Las características de los dos tipos de ocultación se describen en las subclases siguientes.
7.7.2.2 Ocultar a través del anidamiento
El ocultamiento de nombres a través del anidamiento puede producirse como resultado de anidar espacios de nombres o tipos dentro de espacios de nombres, como resultado de anidar tipos dentro de clases o estructuras, como resultado de una función local o lambda, y como resultado de las declaraciones de parámetros, variables locales y constantes locales.
Ejemplo: en el código siguiente
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 del
F
método , la variablei
de instancia está oculta por la variablei
local , pero dentro delG
método ,i
todavía hace referencia a la variable de instancia. Dentro de la funciónM1
local,float i
oculta el elemento externo inmediatoi
. El parámetroi
lambda oculta elfloat i
elemento dentro del cuerpo lambda.ejemplo final
Cuando un nombre en un ámbito interno oculta un nombre en un ámbito externo, oculta todas las apariciones sobrecargadas de ese nombre.
Ejemplo: en el código siguiente
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 } } }
La llamada
F(1)
invoca alF
declarado enInner
porque todas las apariciones externas deF
están ocultas por la declaración interna. Por el mismo motivo, la llamadaF("Hello")
produce un error en tiempo de compilación.ejemplo final
7.7.2.3 Ocultar a través de la herencia
La ocultación de nombres a través de la herencia se produce cuando las clases o estructuras vuelven a declarar nombres heredados de las clases base. Este tipo de ocultación de nombres toma una de las siguientes formas:
- Una constante, campo, propiedad, evento o tipo introducido en una clase o estructura oculta todos los miembros de clase base con el mismo nombre.
- Un método introducido en una clase o estructura oculta todos los miembros de clase base que no son métodos con el mismo nombre y todos los métodos de clase base con la misma firma (§7.6).
- Un indexador introducido en una clase o estructura oculta todos los indexadores de clase base con la misma firma (§7.6).
Las reglas que rigen las declaraciones de operador (§15.10) hacen imposible que una clase derivada declare un operador con la misma firma que un operador en una clase base. Por lo tanto, los operadores nunca se ocultan entre sí.
Al contrario de ocultar un nombre de un ámbito externo, ocultar un nombre visible de un ámbito heredado hace que se notifique una advertencia.
Ejemplo: en el código siguiente
class Base { public void F() {} } class Derived : Base { public void F() {} // Warning, hiding an inherited name }
la declaración de
F
enDerived
hace que se notifique una advertencia. Ocultar un nombre heredado no es específicamente un error, ya que esto impediría la evolución independiente de las clases base. Por ejemplo, la situación anterior podría haber llegado porque una versión posterior deBase
introdujo unF
método que no estaba presente en una versión anterior de la clase .ejemplo final
La advertencia causada por ocultar un nombre heredado se puede eliminar mediante el uso del new
modificador :
Ejemplo:
class Base { public void F() {} } class Derived : Base { public new void F() {} }
El
new
modificador indica que elF
elemento deDerived
es "nuevo" y que realmente está pensado para ocultar el miembro heredado.ejemplo final
Una declaración de un nuevo miembro oculta un miembro heredado solo dentro del ámbito del nuevo miembro.
Ejemplo:
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 } }
En el ejemplo anterior, la declaración de
F
en oculta elDerived
que se heredó deF
, pero dado que el nuevoBase
enF
tiene acceso privado, su ámbito no se extiende aDerived
MoreDerived
. Por lo tanto, la llamadaF()
enMoreDerived.G
es válida e invocaráBase.F
.ejemplo final
7.8 Nombres de espacio de nombres y tipos
7.8.1 General
Varios contextos de un programa de C# requieren que se especifique un namespace_name o un type_name .
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
;
Un namespace_name es un namespace_or_type_name que hace referencia a un espacio de nombres.
La siguiente resolución, como se describe a continuación, el namespace_or_type_name de un namespace_name hará referencia a un espacio de nombres o, de lo contrario, se producirá un error en tiempo de compilación. Ningún argumento de tipo (§8.4.2) puede estar presente en un namespace_name (solo los tipos pueden tener argumentos de tipo).
Un type_name es un namespace_or_type_name que hace referencia a un tipo. La siguiente resolución, tal como se describe a continuación, el namespace_or_type_name de un type_name hará referencia a un tipo o, de lo contrario, se producirá un error en tiempo de compilación.
Si el namespace_or_type_name es un qualified_alias_member su significado es como se describe en §14.8.1. De lo contrario, un namespace_or_type_name tiene una de cuatro formas:
I
I<A₁, ..., Aₓ>
N.I
N.I<A₁, ..., Aₓ>
donde I
es un identificador único, N
es un namespace_or_type_name y <A₁, ..., Aₓ>
es un type_argument_list opcional. Cuando no se especifica ningún type_argument_list , considere la posibilidad x
de ser cero.
El significado de un namespace_or_type_name se determina de la siguiente manera:
- Si el namespace_or_type_name es un qualified_alias_member, el significado se especifica en §14.8.1.
- De lo contrario, si el namespace_or_type_name es del formato
I
o del formularioI<A₁, ..., Aₓ>
:- Si
x
es cero y el namespace_or_type_name aparece dentro de una declaración de método genérico (§15.6), pero fuera de los atributos de su encabezado de método, y si esa declaración incluye un parámetro de tipo (§15.2.3) con el nombreI
, el namespace_or_type_name hace referencia a ese parámetro de tipo. - De lo contrario, si el namespace_or_type_name aparece dentro de una declaración de tipo, para cada tipo
T
de instancia (§15.3.2), empezando por el tipo de instancia de esa declaración de tipo y continuando con el tipo de instancia de cada clase o declaración de estructura envolvente (si existe):- Si
x
es cero y la declaración de incluye un parámetro deT
tipo con el nombreI
, el namespace_or_type_name hace referencia a ese parámetro de tipo. - De lo contrario, si el namespace_or_type_name aparece dentro del cuerpo de la declaración de tipo y
T
cualquiera de sus tipos base contienen un tipo accesible anidado que tiene parámetros de nombreI
yx
tipo, entonces el namespace_or_type_name hace referencia a ese tipo construido con los argumentos de tipo especificados. Si hay más de un tipo de este tipo, se selecciona el tipo declarado en el tipo más derivado.
Nota: Los miembros que no son de tipo (constantes, campos, métodos, propiedades, indexadores, operadores, constructores de instancia, finalizadores y constructores estáticos) y miembros de tipo con un número diferente de parámetros de tipo se omiten al determinar el significado de la namespace_or_type_name. nota final
- Si
- De lo contrario, para cada espacio de nombres
N
, empezando por el espacio de nombres en el que se produce el namespace_or_type_name , continuando con cada espacio de nombres envolvente (si existe) y finalizando con el espacio de nombres global, se evalúan los pasos siguientes hasta que se encuentra una entidad:- Si
x
es cero yI
es el nombre de un espacio de nombres enN
, entonces:- Si la ubicación en la que se produce el namespace_or_type_name se incluye en una declaración de espacio de nombres para y la declaración de espacio de nombres contiene una
N
o using_alias_directive que asocia el nombre a un espacio de nombres o tipo, elI
es ambiguo y se produce un error en tiempo de compilación. - De lo contrario, el namespace_or_type_name hace referencia al espacio de nombres denominado
I
enN
.
- Si la ubicación en la que se produce el namespace_or_type_name se incluye en una declaración de espacio de nombres para y la declaración de espacio de nombres contiene una
- De lo contrario, si
N
contiene un tipo accesible que tiene parámetros de nombreI
yx
tipo, a continuación:- Si
x
es cero y la ubicación en la que se produce el namespace_or_type_name se incluye entre una declaración de espacio de nombres paraN
y la declaración de espacio de nombres contiene una extern_alias_directive o using_alias_directive que asocia el nombreI
a un espacio de nombres o tipo, el namespace_or_type_name es ambiguo y se produce un error en tiempo de compilación. - De lo contrario, el namespace_or_type_name hace referencia al tipo construido con los argumentos de tipo especificados.
- Si
- De lo contrario, si la ubicación en la que se produce el namespace_or_type_name se incluye en una declaración de espacio de nombres para
N
:- Si
x
es cero y la declaración de espacio de nombres contiene un extern_alias_directive o using_alias_directive que asocia el nombreI
a un espacio de nombres o tipo importados, el namespace_or_type_name hace referencia a ese espacio de nombres o tipo. - De lo contrario, si los espacios de nombres importados por los using_namespace_directives de la declaración de espacio de nombres contienen exactamente un tipo que tiene parámetros de nombre
I
yx
tipo, el namespace_or_type_name hace referencia a ese tipo construido con los argumentos de tipo especificados. - De lo contrario, si los espacios de nombres importados por los using_namespace_directives de la declaración de espacio de nombres contienen más de un tipo que tiene parámetros de nombre
I
yx
tipo, el namespace_or_type_name es ambiguo y se produce un error.
- Si
- Si
- De lo contrario, el namespace_or_type_name no está definido y se produce un error en tiempo de compilación.
- Si
- De lo contrario, el namespace_or_type_name es del formato
N.I
o del formularioN.I<A₁, ..., Aₓ>
.N
se resuelve primero como un namespace_or_type_name. Si la resolución de no se realiza correctamente, se produce un error en tiempo deN
compilación. De lo contrario,N.I
oN.I<A₁, ..., Aₓ>
se resuelve de la siguiente manera:- Si
x
es cero yN
hace referencia a un espacio de nombres yN
contiene un espacio de nombres anidado con el nombreI
, el namespace_or_type_name hace referencia a ese espacio de nombres anidado. - De lo contrario, si
N
hace referencia a un espacio de nombres yN
contiene un tipo accesible que tiene parámetros de nombreI
yx
tipo, el namespace_or_type_name hace referencia a ese tipo construido con los argumentos de tipo especificados. - De lo contrario, si
N
hace referencia a un tipo de clase o estructura (posiblemente construido) yN
o a cualquiera de sus clases base contienen un tipo accesible anidado que tiene parámetros de nombreI
yx
tipo, el namespace_or_type_name hace referencia a ese tipo construido con los argumentos de tipo especificados. Si hay más de un tipo de este tipo, se selecciona el tipo declarado en el tipo más derivado.Nota: Si el significado de se determina como parte de
N.I
la resolución de la especificación de clase base deN
, la clase base directa deN
se consideraobject
(§15.2.4.2). nota final - De lo contrario,
N.I
es un namespace_or_type_name no válido y se produce un error en tiempo de compilación.
- Si
Solo se permite que un namespace_or_type_name haga referencia a una clase estática (§15.2.2.4) si
- El namespace_or_type_name es en
T
un namespace_or_type_name del formatoT.I
, o - El namespace_or_type_name es el
T
en un typeof_expression (§12.8.18) de la formatypeof(T)
7.8.2 Nombres sin calificar
Cada declaración de espacio de nombres y declaración de tipo tiene un nombre no completo determinado de la siguiente manera:
- Para una declaración de espacio de nombres, el nombre no calificado es el qualified_identifier especificado en la declaración.
- Para una declaración de tipo sin type_parameter_list, el nombre no calificado es el identificador especificado en la declaración.
- Para una declaración de tipo con parámetros de tipo K, el nombre no calificado es el identificador especificado en la declaración, seguido del generic_dimension_specifier (§12.8.18) para los parámetros de tipo K.
7.8.3 Nombres completos
Cada declaración de espacio de nombres y tipo tiene un nombre completo, que identifica de forma única el espacio de nombres o la declaración de tipo entre todos los demás del programa. El nombre completo de un espacio de nombres o una declaración de tipo con un nombre N
no completo se determina de la siguiente manera:
- Si
N
es miembro del espacio de nombres global, su nombre completo esN
. - De lo contrario, su nombre completo es
S.N
, dondeS
es el nombre completo del espacio de nombres o la declaración de tipo en el queN
se declara.
En otras palabras, el nombre completo de N
es la ruta jerárquica completa de identificadores y generic_dimension_specifiers que conducen a N
, empezando por el espacio de nombres global. Dado que todos los miembros de un espacio de nombres o tipo tendrán un nombre único, sigue que el nombre completo de un espacio de nombres o declaración de tipo siempre es único. Se trata de un error en tiempo de compilación para que el mismo nombre completo haga referencia a dos entidades distintas. En concreto:
- Se trata de un error para que una declaración de espacio de nombres y una declaración de tipo tengan el mismo nombre completo.
- Se trata de un error para que dos tipos diferentes de declaraciones de tipo tengan el mismo nombre completo (por ejemplo, si una declaración de estructura y clase tienen el mismo nombre completo).
- Es un error para una declaración de tipo sin el modificador parcial tener el mismo nombre completo que otra declaración de tipo (§15.2.7).
Ejemplo: En el ejemplo siguiente se muestran varias declaraciones de espacio de nombres y tipos junto con sus nombres completos asociados.
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<> } }
ejemplo final
7.9 Administración automática de memoria
C# emplea la administración automática de memoria, que libera a los desarrolladores de asignar y liberar manualmente la memoria ocupada por objetos. Un recolector de elementos no utilizados implementa directivas de administración automática de memoria. El ciclo de vida de administración de memoria de un objeto es el siguiente:
- Cuando se crea el objeto, se le asigna memoria, se ejecuta el constructor y el objeto se considera activo.
- Si no se puede tener acceso al objeto ni a ninguno de sus campos de instancia mediante cualquier posible continuación de la ejecución, aparte de la ejecución de finalizadores, el objeto ya no se considera en uso y es apto para la finalización.
Nota: El compilador de C# y el recolector de elementos no utilizados pueden optar por analizar código para determinar qué referencias a un objeto se pueden usar en el futuro. Por ejemplo, si una variable local que está en el ámbito es la única referencia existente a un objeto, pero esa variable local nunca se hace referencia a en cualquier posible continuación de la ejecución desde el punto de ejecución actual en el procedimiento, el recolector de elementos no utilizados podría tratar (pero no es necesario) tratar el objeto como ya no está en uso. nota final
- Una vez que el objeto es apto para la finalización, en algún momento no especificado más adelante el finalizador (§15.13) (si existe) para el objeto se ejecuta. En circunstancias normales, el finalizador del objeto se ejecuta una sola vez, aunque las API definidas por la implementación pueden permitir que este comportamiento se invalide.
- Una vez ejecutado el finalizador de un objeto, si no se puede tener acceso al objeto ni a ninguno de sus campos de instancia cualquier posible continuación de la ejecución, incluida la ejecución de finalizadores, el objeto se considera inaccesible y el objeto es apto para la colección.
Nota: Un objeto al que no se pudo acceder anteriormente puede volver a ser accesible debido a su finalizador. A continuación se proporciona un ejemplo de esto. nota final
- Por último, en algún momento después de que el objeto sea apto para la recolección, el recolector de elementos no utilizados libera la memoria asociada a ese objeto.
El recolector de elementos no utilizados mantiene información sobre el uso de objetos y usa esta información para tomar decisiones de administración de memoria, como dónde se encuentra en memoria para localizar un objeto recién creado, cuándo reubicar un objeto y cuando un objeto ya no está en uso o inaccesible.
Al igual que otros lenguajes que asumen la existencia de un recolector de elementos no utilizados, C# está diseñado para que el recolector de elementos no utilizados pueda implementar una amplia gama de directivas de administración de memoria. C# no especifica ninguna restricción de tiempo dentro de ese intervalo ni un orden en el que se ejecutan los finalizadores. Si los finalizadores se ejecutan como parte de la terminación de la aplicación está definido por la implementación (§7.2).
El comportamiento del recolector de elementos no utilizados se puede controlar, en cierto grado, a través de métodos estáticos en la clase System.GC
. Esta clase se puede usar para solicitar que se produzca una colección, los finalizadores que se ejecutarán (o no se ejecuten), etc.
Ejemplo: Dado que el recolector de elementos no utilizados tiene permiso para decidir cuándo recopilar objetos y ejecutar finalizadores, una implementación conforme podría generar resultados que difieren de los mostrados por el código siguiente. El 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(); } }
crea una instancia de clase
A
y una instancia de la claseB
. Estos objetos se convierten en aptos para la recolección de elementos no utilizados cuando se asigna el valorb
a la variablenull
, ya que después de este tiempo es imposible que cualquier código escrito por el usuario tenga acceso a ellos. La salida podría ser cualquiera de las dosFinalize instance of A Finalize instance of B
o
Finalize instance of B Finalize instance of A
dado que el lenguaje no impone restricciones en el orden en que se recopilan elementos no utilizados.
En casos sutiles, la distinción entre "apto para la finalización" y "apto para la recopilación" puede ser importante. Por ejemplo,
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"); } } }
En el programa anterior, si el recolector de elementos no utilizados decide ejecutar el finalizador de antes del
A
finalizador deB
, la salida de este programa podría ser:Finalize instance of A Finalize instance of B A.F RefA is not null
Tenga en cuenta que, aunque la instancia de
A
no estaba en uso yA
se ejecutó el finalizador, todavía es posible llamar a métodos deA
(en este caso,F
) desde otro finalizador. Además, tenga en cuenta que la ejecución de un finalizador puede hacer que un objeto se pueda volver a usar desde el programa principal. En este caso, la ejecución delB
finalizador de ha provocado que se pueda acceder a una instancia deA
que anteriormente no estaba en uso desde la referenciaTest.RefA
dinámica . Después de la llamada aWaitForPendingFinalizers
, la instancia deB
es apta para la recopilación, pero la instancia deA
no es debido a la referenciaTest.RefA
.ejemplo final
7.10 Orden de ejecución
La ejecución de un programa de C# continúa de modo que los efectos secundarios de cada subproceso en ejecución se conservan en puntos de ejecución críticos. Un efecto secundario se define como lectura o escritura de un campo volátil, una escritura en una variable no volátil, una escritura en un recurso externo y el inicio de una excepción. Los puntos de ejecución críticos en los que se conservará el orden de estos efectos secundarios son referencias a campos volátiles (§15.5.4), lock
instrucciones (§13.13) y creación y finalización del subproceso. El entorno de ejecución es libre para cambiar el orden de ejecución de un programa de C#, sujeto a las restricciones siguientes:
- La dependencia de los datos se conserva dentro de un subproceso de ejecución. Es decir, el valor de cada variable se calcula como si todas las instrucciones del subproceso se ejecutaran en orden de programa original.
- Las reglas de ordenación de inicialización se conservan (§15.5.5, §15.5.6).
- El orden de los efectos secundarios se conserva con respecto a las lecturas y escrituras volátiles (§15.5.4). Además, el entorno de ejecución no necesita evaluar parte de una expresión si puede deducir que no se usa el valor de esa expresión y que no se producen efectos secundarios necesarios (incluido cualquier causa al llamar a un método o acceder a un campo volátil). Cuando un evento asincrónico interrumpe la ejecución del programa (por ejemplo, una excepción producida por otro subproceso), no se garantiza que los efectos secundarios observables sean visibles en el orden del programa original.
ECMA C# draft specification