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 deberá contener al menos un método que califique como un punto de entrada cumpliendo los siguientes requisitos:

  • 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 awaitable diferente, como ValueTask o ValueTask<int>, no 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 o void, cualquier método calificado que tenga un tipo de valor devuelto de System.Threading.Tasks.Task o System.Threading.Tasks.Task<int> no se considera un método de entrada. Es un error de compilación que un programa se compile como una aplicación sin tener exactamente un único 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, la accesibilidad declarada (§7.5.2) de un método está determinada por los modificadores de acceso (§15.3.6) especificados en su declaración y, de forma similar, la accesibilidad declarada de un tipo está 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, tanto el tipo como el miembro deben ser accesibles. 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 tipo de retorno Main, si la tarea se completa correctamente, el valor System.Threading.Tasks.Task<int> devuelto por int 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 libres para proporcionar políticas o mecanismos 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 información a la aplicación determinada antes de que la aplicación se inicie desde otra ubicación en el 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 mediante una 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 un valor no-null que hace referencia a una matriz que contiene referencias nulas. Del mismo modo, el valor devuelto del punto de entrada no tiene ninguna importancia especial aparte de 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 int y la ejecución se completa sin provocar una excepción, el valor del int devuelto actúa como el 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 void y la ejecución se 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.

La implementación define si los finalizadores (§15.13) se ejecutan o no como parte de la terminación de la aplicación.

Nota: La implementación de .NET Framework hace todos los esfuerzos razonables para llamar a finalizadores (§15.13) para todos sus objetos que aún no se han recolectado como elementos no utilizados, a menos que dicha limpieza se haya suprimido (mediante una llamada al método de biblioteca GC.SuppressFinalize, 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. Es un error en tiempo de compilación tener dos o más declaraciones que introducen miembros con el mismo nombre dentro de un espacio de declaración, excepto en los siguientes casos:

  • 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 separados, pero en el mismo espacio de declaración 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, las namespace_member_declarationsdentro de las namespace_declarationsque 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 afirma 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 accesor de propiedad, declaración de indexador, declaración de accesor 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 la función, función anónima o función local, si existe, se considera anidado dentro del espacio de declaración de variables locales. 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 patrones, expresiones_de_declaración, instrucciones_de_declaración y especificadores_de_excepción. Los espacios de declaración de variables locales se pueden anidar, pero es un error que un espacio de declaración de variable local y un espacio anidado tengan 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 la variable local para el cuerpo del constructor está, a su vez, anidado dentro de este mismo espacio de declaración de la variable local.
    • Cada bloque, switch_block, specific_catch_clause, iteration_statement y using_statement crean un espacio anidado para la declaración de variables locales.
    • 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 variables locales anidadas. 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 etiquetas 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 local y está dentro del ámbito en la sección switch para el caso predeterminado, a pesar de que su declaración 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, índices, operadores, constructores de instancia, 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 sin límites; y dos declaraciones 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 definición, en este caso, definiendo dos clases con los nombres completamente calificados Megacorp.Data.Customer 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 métodos H y I son válidos, ya que los dos ise declaran en bloques independientes no anidados.

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 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 pertenecen a ningún 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 estructura

Los miembros de una estructura son los miembros declarados en la estructura y los miembros heredados de la estructura base directa System.ValueType y de la estructura base indirecta object.

Los miembros de un tipo simple corresponden directamente a los miembros del tipo de estructura que el tipo simple tiene como alias (§8.3.5).

7.4.4 Miembros de la 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 a los que tienen como 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 la 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, combinada con la accesibilidad del tipo inmediatamente contenedor, 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 modificador public en la declaración de miembro. El significado intuitivo de public es "acceso no limitado".
  • Protected, que se selecciona mediante la inclusión de un protected en la declaración de miembro. El significado intuitivo de protected es "acceso limitado a la clase que contiene o a los tipos derivados de la clase que contiene".
  • Interno, que se selecciona mediante la inclusión de un internal 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 los modificadores protected y internal en la declaración de miembro. El significado intuitivo de protected internal es "accesible dentro de este ensamblado, así como en tipos derivados de la clase contenedora".
  • Private protected, que se selecciona incluyendo un private y un protected modificador en la declaración del miembro. El significado intuitivo de private protected es "accesible dentro de este ensamblado por la clase contenedora y los tipos derivados de la clase contenedora".
  • Privado, que se selecciona mediante la inclusión de un modificador private 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 tienen la accesibilidad declarada implícitamente public. 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 declarada una accesibilidad de public o de internal, y por defecto tienen accesibilidad declarada de internal.
  • Los miembros de clase pueden tener cualquiera de los tipos permitidos de accesibilidad declarada y se establece por defecto a la accesibilidad declarada de private.

    Nota: Un tipo declarado como miembro de una clase puede tener cualquiera de los tipos de accesibilidad permitidos, mientras que un tipo declarado como miembro de un espacio de nombres solo puede tener public o internal accesibilidad declarada. nota final

  • Los miembros de struct pueden tener public, internal o private accesibilidad declarada y predeterminan a accesibilidad declarada de private porque los structs están sellados de forma implícita. Los miembros de estructura introducidos en struct (es decir, no heredados por esa estructura) no pueden tener protected, protected internal o private protected declarada accesibilidad.

    Nota: Un tipo declarado como miembro de un struct puede tener public, internal o private como accesibilidad declarada, mientras que un tipo declarado como miembro de un espacio de nombres solo puede tener public o internal como accesibilidad declarada. nota final

  • Los miembros de interfaz han public declarado implícitamente la accesibilidad. No se permite ningún modificador de acceso en declaraciones de miembros 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. A efectos de definir el dominio de accesibilidad de un miembro, se dice que un miembro es de nivel superior si no está declarado dentro de un tipo, y se dice que un miembro está anidado si está declarado 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 T es el texto del 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 genérico no enlazado T y los dominios de accesibilidad de los argumentos de tipo A₁, ..., Aₑ.

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 protected internal, que D sea la unión del texto del programa de P y el texto del programa de cualquier tipo que derive de T, declarado fuera de P. 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 private protected, deje que D sea la intersección del texto del programa de P y el texto del programa de T y cualquier tipo derivado de T. 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 protected, que D sea la unión del texto del programa de T y el texto del programa de cualquier tipo derivado de T. 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.X, B.Y, B.C, B.C.X, y B.C.Y es el texto del programa que lo contiene.
  • El dominio de accesibilidad de A.Z es el texto del programa de A.
  • El dominio de accesibilidad de B.Z y B.D es el texto del 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 B.D.X y B.D.Y es el texto del 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, los dominios de accesibilidad de todos excepto A.X 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 que el acceso se realice a través de una instancia de ese tipo de clase derivada o de un tipo de clase construido a partir de ello. 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.

Sea B una clase base que declare un miembro de instancia protegido M, y que D sea una clase que deriva de 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 de la forma M.
  • Una expresión_primaria de la forma E.M, siempre que el tipo de E sea T o una clase derivada de T, donde T es la clase D, o un tipo de clase construido a partir de D.
  • Un primary_expression de la forma base.M.
  • Una primary_expression de la forma base[argument_list].

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 de A, es posible acceder a x a través de instancias de A y B, ya que, en cualquier caso, el acceso se realiza a través de una instancia de A o de una clase derivada de A. Sin embargo, dentro de B, no es posible acceder a 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 en D es válida aunque la clase D provenga 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 método H en B da como resultado un error en tiempo de compilación porque el tipo de retorno A no es tan accesible como el propio 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 no incluye específicamente el tipo de valor devuelto, los nombres de los parámetros, los nombres y las restricciones de los parámetros de tipo, ni los modificadores de parámetro params o this, ni si los parámetros son obligatorios u 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 no incluye específicamente el modificador params que se puede especificar para el parámetro más a la derecha, ni tampoco 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 que permite 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 los modificadores de parámetro in, out y ref se consideran parte de una firma, los miembros declarados en un único 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 modificadores out o in se cambiaran a modificadores ref. 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, no se permiten miembros que se declaran en un solo tipo y cuyas firmas solo difieren al reemplazar object por dynamic.

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 anidarse, y un ámbito interno puede redefinir el significado de un nombre que proviene de 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 declarado en 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 con 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. Una directiva extern_alias_directive no aporta ningún miembro nuevo 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 sobre los global_attributes y las namespace_member_declarations de la compilation_unit o el namespace_body en el que se produce el using_directive. A directiva_de_uso puede poner a disposición cero o más nombres de espacios de nombres o tipos dentro de un determinado unidad_de_compilación o espacio_nombre_cuerpo, pero no aporta ningún miembro nuevo 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 tiene lugar 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 esa 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. Es un error en tiempo de compilación referirse a una constante local en una posición textual que precede a su declarador_de_constantes.

  • 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 que F haga 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 referirse a la variable local en una posición textual que aparece antes de su declaración.

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 al anidar espacios de nombres o tipos dentro de otros espacios de nombres, al anidar tipos dentro de clases o estructuras, debido a una función local o una expresión lambda, y debido a 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 lambda i oculta el float i dentro del cuerpo de la lambda.

ejemplo final

Cuando un nombre en un contexto interno oculta un nombre en un contexto 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í.

A diferencia de ocultar un nombre en un ámbito externo, ocultar un nombre visible en un ámbito heredado provoca 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 modificador new indica que F en Derived es 'nuevo', y que se utiliza 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 Espacio de nombres y nombres de tipo

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? ('.' identifier type_argument_list?)*
    | qualified_alias_member ('.' identifier type_argument_list?)*
    ;

Un namespace_name es un namespace_or_type_name que hace referencia a un espacio de nombres.

Tras la resolución descrita a continuación, el namespace_or_type_name de un namespace_name debe referirse 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.

Tras la resolución según 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.

Un namespace_or_type_name hace referencia a un tipo o a un espacio de nombres. La resolución de un espacio de nombres o tipo particular implica dos pasos que se basan en dividir la gramática en una parte inicial, que es uno de los fragmentos de la gramática.

  • identifier type_argument_list?
  • qualified_alias_member

y una parte final, siendo el fragmento de gramática:

  • ('.' identifier type_argument_list?)*

Primero se resuelve la parte principal para determinar R₀, el espacio de nombres o tipo inicial.

Si la parte inicial de namespace_or_type_name es un qualified_alias_member, entonces R₀ es el espacio de nombres o el tipo que se identifica al resolverlo como se describe en §14.8.1.

De lo contrario, la parte principal, siendo el fragmento de gramática identificador type_argument_list?, tendrá una de las formas:

  • I
  • I<A₁, ..., Aₓ>

donde:

  • I es un identificador único; y
  • <A₁, ..., Aₓ> es un type_argument_list, cuando no se especifica ningún type_argument_list considera x que es cero.

R₀ se determina de la siguiente manera:

  • 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, entonces R₀ 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, entonces R₀ 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 o cualquiera de sus tipos base contienen un tipo accesible anidado que tiene parámetros de nombre I y x tipo, entonces R₀ 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 está incluida en 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 con un espacio de nombres o tipo, entonces el namespace_or_type_name es ambiguo y se produce un error de compilación en tiempo de ejecución.
        • De lo contrario, R₀ hace referencia al espacio de nombres denominado I en N.
      • De lo contrario, si N contiene un tipo accesible que tiene de nombre I y parámetros de tipo x, entonces:
        • 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, R₀ hace referencia al tipo construido con los argumentos de tipo especificados.
      • De lo contrario, si la ubicación donde ocurre el namespace_or_type_name está incluido 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, hace R₀ 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 tipo I y x, entonces R₀ 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 con los parámetros de tipo I y x, entonces 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 no está definido y se produce un error en tiempo de compilación.

Si R₀ se ha resuelto correctamente, se resuelve la parte final del namespace_or_type_name. El fragmento de gramática final consta de k ≥ 0 repeticiones, donde cada repetición resuelve de manera adicional el espacio de nombres o el tipo al que se hace referencia.

Si k es cero, es decir, no hay ninguna parte final, entonces el namespace_or_type_name se resuelve en R₀.

De lo contrario, cada repetición tendrá una de las formas:

  • .I
  • .I<A₁, ..., Aₓ>

donde I, A y x se definen como antes.

Para cada repetición n, donde 1 ≤ n ≤ k, su resolución Rₙ, que implica Rₚ, donde p = n - 1, la resolución de la repetición anterior se determina de la siguiente manera:

  • Si x es cero y Rₚ hace referencia a un espacio de nombres y Rₚ contiene un espacio de nombres anidado con el nombre I, hace Rₙ referencia a ese espacio de nombres anidado.
  • De lo contrario, si Rₚ hace referencia a un espacio de nombres y Rₚ contiene un tipo accesible que tiene parámetros de nombre I y x tipo, hace Rₙ referencia a ese tipo construido con los argumentos de tipo especificados.
  • De lo contrario, si Rₚ hace referencia a un tipo de clase o estructura (posiblemente construido) y Rₚ o a cualquiera de sus clases base contienen un tipo accesible anidado que tiene parámetros de nombre I y x tipo, entonces Rₙ 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 T.I, para algún tipo T, se determina como parte de la resolución de la especificación de clase base de T , la clase base directa de T se considera object (§15.2.4.2). nota final

  • De lo contrario, el namespace_or_type_name no es válido y se produce un error en tiempo de compilación.

La resolución del namespace_or_type_name es la resolución de la repetición final, Rₖ.

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 no calificados

Cada declaración de espacio de nombres y declaración de tipo tiene un nombre no calificado 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 totalmente cualificados

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 completamente calificado de un espacio de nombres o una declaración de tipo con un nombre N sin calificar 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 completamente cualificado de N es la ruta jerárquica completa de identificadores y especificadores de dimensión genérica que conducen a , 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. Es un error de tiempo de compilación que el mismo nombre completamente calificado se refiera a dos entidades distintas. En concreto:

  • Es un error que tanto una declaración de espacio de nombres como una declaración de tipo tengan el mismo nombre completo.
  • Es un error que dos tipos diferentes de declaraciones de tipo tengan el mismo nombre completo (por ejemplo, si una declaración de estructura y una de clase tienen el mismo nombre completo).
  • Es un error que una declaración de tipo sin el modificador parcial tenga 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 basura implementa políticas de gestió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 vuelve a referenciar en ninguna posible continuación de la ejecución desde el punto de ejecución actual en el procedimiento, el recolector de basura podría (pero no está obligado a) 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 desde cualquier posible continuación de la ejecución, incluida la ejecución de finalizadores, se considera que el objeto es inaccesible y se vuelve apto para la recolecció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 la ejecución de los finalizadores forma parte de la terminación de la aplicación, depende de la definición de 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 realice una recolección, ejecutar (o no) los finalizadores, 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 los objetos son recolectados por el recolector de basura.

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 basura elige ejecutar el finalizador de A antes que el 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 finalizador de B ha provocado que se pueda acceder a una instancia de A que anteriormente no estaba en uso desde la referencia dinámica Test.RefA. 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 una lectura o escritura de un campo volátil, una escritura en una variable no volátil, una escritura en un recurso externo y el lanzamiento 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.