Compartir vía


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 sea System.Threading.Tasks.Task o System.Threading.Tasks.Task<int>.
  • El tipo de valor devuelto será void, int, System.Threading.Tasks.Tasko System.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. Un async void método o un async método que devuelve un tipo await diferente, como ValueTask o ValueTask<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 es System.Threading.Tasks.Task, el tipo de valor devuelto del método sintetizado es . void
  • Si el tipo de valor devuelto del Main método es System.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 su string[] valor de parámetro como argumento si el Main 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 de System.Threading.Tasks.Task<int>, si la tarea se completa correctamente, el int valor devuelto por GetResult() 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 nullo 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.SuppressFinalizede 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 variable z 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 y Megacorp.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 y G producen un error en tiempo de compilación porque el nombre i se declara en el bloque externo y no se puede volver a declarar en el bloque interno. Sin embargo, los H métodos y I son válidos, ya que los dos ise 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 objectbase 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 clase object 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 de public 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 de protected 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 de internal es "acceso limitado a este ensamblado".
  • Interno protegido, que se selecciona mediante la inclusión de un protected y un internal modificador en la declaración de miembro. El significado intuitivo de es "accesible dentro de protected 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 en protected la declaración de miembro. El significado intuitivo de es "accesible dentro de private 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 de private 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 o internal declarar la accesibilidad y el valor predeterminado para internal 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 o internal declarar accesibilidad. nota final

  • Los miembros de Struct pueden tener publicla accesibilidad declarada , internalo y private el valor predeterminado para private declarar la accesibilidad porque las estructuras están selladas implícitamente. Los miembros de estructura introducidos en struct (es decir, no heredados por esa estructura) no pueden tener protectedaccesibilidad declarada , protected internalo private protected .

    Nota: Un tipo declarado como miembro de un struct puede tener publicaccesibilidad declarada , internalo private , mientras que un tipo declarado como miembro de un espacio de nombres solo public puede tener o internal 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, into 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 del T programa de P y cualquier programa que haga referencia a P.
  • Si la accesibilidad declarada de T es interna, el dominio de accesibilidad de T es el texto del programa de P.

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 Pse define de la siguiente manera (teniendo en cuenta que es posible que M sea un tipo):

  • Si la accesibilidad declarada de M es public, el dominio de accesibilidad de M es el dominio de accesibilidad de T.
  • Si la accesibilidad declarada de M es , vamos protected internal a ser la unión del texto del programa de D y el texto del programa de cualquier tipo derivado de P, que se declara fuera TPde . El dominio de accesibilidad de M es la intersección del dominio de accesibilidad de T con D.
  • Si la accesibilidad declarada de M es , vamos private protected a ser la intersección del texto del programa de D y el texto del programa de P y cualquier tipo derivado de TT. El dominio de accesibilidad de M es la intersección del dominio de accesibilidad de T con D.
  • Si la accesibilidad declarada de M es , vamos protected a ser la unión del texto del programa de Dy el texto del programa de cualquier tipo derivado de TT. El dominio de accesibilidad de M es la intersección del dominio de accesibilidad de T con D.
  • Si la accesibilidad declarada de M es internal, el dominio de accesibilidad de M es la intersección del dominio de accesibilidad de T con el texto de programa de P.
  • Si la accesibilidad declarada de M es private, el dominio de accesibilidad de M es el texto de programa de T.

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 es public, se permite el acceso.
  • De lo contrario, si M es protected internal, se permite el acceso si se produce dentro del programa en el que M se declara o si se produce dentro de una clase derivada de la clase en la que M se declara y tiene lugar a través del tipo de clase derivada (§7.5.4).
  • De lo contrario, si M es protected, se permite el acceso si se produce dentro de la clase en la que M se declara o si se produce dentro de una clase derivada de la clase en la que M se declara y tiene lugar a través del tipo de clase derivada (§7.5.4).
  • De lo contrario, si M es internal, se permite el acceso si se produce dentro del programa en el que M se declara.
  • De lo contrario, si M es private, se permite el acceso si se produce dentro del tipo en el que M 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 y A.X es ilimitado.
  • El dominio de accesibilidad de A.Y, , B, B.XB.Y, B.C, B.C.X, y B.C.Y es el texto del programa del programa contenedor.
  • El dominio de accesibilidad de A.Z es el texto del programa de A.
  • El dominio de accesibilidad de y B.Z es el texto del B.D programa de B, incluido el texto del programa de B.C y B.D.
  • El dominio de accesibilidad de B.C.Z es el texto del programa de B.C.
  • El dominio de accesibilidad de y B.D.X es el texto del B.D.Y programa de B, incluido el texto del programa de B.C y B.D.
  • El dominio de accesibilidad de B.D.Z es el texto del programa de B.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 los X miembros tengan accesibilidad declarada pública, todos los dominios de A.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 miembro x privado de la A clase . Dado que el miembro es privado, solo es accesible dentro del class_body de A. Por lo tanto, el acceso a b.x se realiza correctamente en el A.F método , pero se produce un error en el B.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 My 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 Ade , es posible tener acceso x a través de instancias de y AB , ya que, en cualquier caso, el acceso tiene lugar a través de una instancia de A o una clase derivada de A. Sin embargo, dentro Bde , no es posible acceder x a través de una instancia de A, ya que A no deriva de B.

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 miembro C<int>.x de es D válida aunque la clase D derive de C<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 porque A no es al menos tan accesible como B.

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 da B como resultado un error en tiempo de compilación porque el tipo A 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 o this , 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 inlos modificadores de parámetro , outy ref se consideran parte de una firma, los miembros declarados en un solo tipo no pueden diferir en la firma únicamente por in, outy 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, outy 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, outy ref. 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 inmodificadores de parámetro , outy ref (§15.6.2) forman parte de una firma. Por lo tanto, F(int), F(in int), F(out int) y F(ref int) son todas las firmas únicas. Sin embargo, F(in int), F(out int) y F(ref int) no se pueden declarar dentro de la misma interfaz porque sus firmas difieren únicamente por in, outy ref. Además, tenga en cuenta que el tipo de valor devuelto y el params 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 del params modificador. Por lo tanto, las declaraciones de los métodos F(int) e F(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 es N o comienza por N, 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 la for 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 a i 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 a i 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 el G método , el uso de j en el inicializador para la declaración de j es válido porque el uso no precede al declarador. En el H 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 variable A local y en un contexto de tipo para hacer referencia a la clase A.

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 variable i de instancia está oculta por la variable ilocal , pero dentro del G método , i todavía hace referencia a la variable de instancia. Dentro de la función M1 local, float i oculta el elemento externo inmediato i. El parámetro i lambda oculta el float 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 al F declarado en Inner porque todas las apariciones externas de F están ocultas por la declaración interna. Por el mismo motivo, la llamada F("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 en Derived 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 de Base introdujo un F 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 el F elemento de Derived 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 el Derived que se heredó de F, pero dado que el nuevo Base en F tiene acceso privado, su ámbito no se extiende a DerivedMoreDerived . Por lo tanto, la llamada F() en MoreDerived.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 formulario I<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 nombre I, 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 de T tipo con el nombre I, 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 nombre I y x 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

    • 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 y I es el nombre de un espacio de nombres en N, 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, el I 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 en N.
      • De lo contrario, si N contiene un tipo accesible que tiene parámetros de nombre I y x 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 para N y la declaración de espacio de nombres contiene una extern_alias_directive o using_alias_directive que asocia el nombre I 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.
      • 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 nombre I 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 y x 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 y x tipo, el namespace_or_type_name es ambiguo y se produce un error.
    • De lo contrario, el namespace_or_type_name no está definido y se produce un error en tiempo de compilación.
  • De lo contrario, el namespace_or_type_name es del formato N.I o del formulario N.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 de N compilación. De lo contrario, N.I o N.I<A₁, ..., Aₓ> se resuelve de la siguiente manera:
    • Si x es cero y N hace referencia a un espacio de nombres y N contiene un espacio de nombres anidado con el nombre I, 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 y N contiene un tipo accesible que tiene parámetros de nombre I y x 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) y N o a cualquiera de sus clases base contienen un tipo accesible anidado que tiene parámetros de nombre I y x 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 de N , la clase base directa de N se considera object (§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.

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 formato T.I, o
  • El namespace_or_type_name es el T en un typeof_expression (§12.8.18) de la forma typeof(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 es N.
  • De lo contrario, su nombre completo es S.N, donde S es el nombre completo del espacio de nombres o la declaración de tipo en el que N 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:

  1. Cuando se crea el objeto, se le asigna memoria, se ejecuta el constructor y el objeto se considera activo.
  2. 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

  3. 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.
  4. 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

  5. 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 clase B. Estos objetos se convierten en aptos para la recolección de elementos no utilizados cuando se asigna el valor ba la variable null , 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 dos

Finalize 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 de B, 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 y Ase ejecutó el finalizador, todavía es posible llamar a métodos de A (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 del Bfinalizador de ha provocado que se pueda acceder a una instancia de A que anteriormente no estaba en uso desde la referencia Test.RefAdinámica . Después de la llamada a WaitForPendingFinalizers, la instancia de B es apta para la recopilación, pero la instancia de A no es debido a la referencia Test.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.