Compartir a través de


12 expresiones

12.1 General

Una expresión es una secuencia de operadores y operandos. Esta cláusula define la sintaxis, el orden de evaluación de operandos y operadores, y el significado de las expresiones.

12.2 Clasificaciones de expresiones

12.2.1 General

El resultado de una expresión se clasifica como uno de los siguientes:

  • Valor . Todos los valores tienen un tipo asociado.
  • Una variable . A menos que se especifique lo contrario, se escribe explícitamente una variable y tiene un tipo asociado, es decir, el tipo declarado de la variable. Una variable con tipo implícito no tiene ningún tipo asociado.
  • Literal null. Una expresión con esta clasificación se puede convertir implícitamente en un tipo de referencia o un tipo de valor que acepta valores NULL.
  • Una función anónima. Una expresión con esta clasificación se puede convertir implícitamente en un tipo de delegado compatible o tipo de árbol de expresión.
  • Una tupla. Cada tupla tiene un número fijo de elementos, cada uno con una expresión y un nombre de elemento de tupla opcional.
  • Acceso a propiedades. Cada acceso de propiedad tiene un tipo asociado, es decir, el tipo de la propiedad. Además, un acceso a propiedades puede tener una expresión de instancia asociada. Cuando se invoca un descriptor de acceso de una propiedad de instancia, el resultado de evaluar la expresión de instancia se convierte en la instancia representada por this (§12.8.14).
  • Acceso a un indexador. Cada acceso al indexador tiene un tipo asociado, es decir, el tipo de elemento del indexador. Además, un acceso de indexador tiene una expresión de instancia asociada y una lista de argumentos asociada. Cuando se invoca un descriptor de acceso de indexador, el resultado de evaluar la expresión de instancia se convierte en la instancia representada por this (§12.8.14) y el resultado de evaluar la lista de argumentos se convierte en la lista de parámetros de la invocación.
  • Nada. Esto ocurre cuando la expresión es una invocación de un método con un tipo de valor devuelto de void. Una expresión clasificada como nada solo es válida en el contexto de un statement_expression (§13.7) o como el cuerpo de un lambda_expression (§12.19).

En el caso de las expresiones que se producen como subexpresiones de expresiones más grandes, con las restricciones indicadas, el resultado también se puede clasificar como uno de los siguientes:

  • Espacio de nombres. Una expresión con esta clasificación solo puede aparecer como el lado izquierdo de un member_access (§12.8.7). En cualquier otro contexto, una expresión clasificada como un espacio de nombres produce un error en tiempo de compilación.
  • Tipo . Una expresión con esta clasificación solo puede aparecer como el lado izquierdo de un member_access (§12.8.7). En cualquier otro contexto, una expresión clasificada como un tipo produce un error en tiempo de compilación.
  • Un grupo de métodos, que es un conjunto de métodos sobrecargados resultantes de una búsqueda de miembros (§12.5). Un grupo de métodos puede tener una expresión de instancia asociada y una lista de argumentos de tipo asociado. Cuando se invoca un método de instancia, el resultado de evaluar la expresión de instancia se convierte en la instancia representada por this (§12.8.14). Se permite un grupo de métodos en un invocation_expression (§12.8.10) o en un delegate_creation_expression (§12.8.17.6) y se puede convertir implícitamente en un tipo de delegado compatible (§10.8). En cualquier otro contexto, una expresión clasificada como un grupo de métodos produce un error en tiempo de compilación.
  • Acceso a eventos. Cada acceso a eventos tiene un tipo asociado, es decir, el tipo del evento. Además, un acceso a eventos puede tener una expresión de instancia asociada. Un acceso a eventos puede aparecer como el operando izquierdo de los += operadores y -= (§12.21.5). En cualquier otro contexto, una expresión clasificada como acceso a eventos provoca un error en tiempo de compilación. Cuando se invoca un descriptor de acceso de evento de instancia, el resultado de evaluar la expresión de instancia se convierte en la instancia representada por this (§12.8.14).
  • Una expresión throw, que se puede usar es varios contextos para iniciar una excepción en una expresión. Una expresión throw se puede convertir mediante una conversión implícita a cualquier tipo.

Un acceso a propiedades o acceso al indexador siempre se vuelve a clasificar como un valor mediante la realización de una invocación del descriptor de acceso get o del descriptor de acceso set. El descriptor de acceso determinado viene determinado por el contexto de la propiedad o el acceso al indexador: si el acceso es el destino de una asignación, se invoca el descriptor de acceso set para asignar un nuevo valor (§12.21.2). De lo contrario, se invoca el descriptor de acceso get para obtener el valor actual (§12.2.2).

Un descriptor de acceso de instancia es un acceso de propiedad en una instancia, un acceso a eventos en una instancia o un acceso de indexador.

12.2.2 Valores de expresiones

La mayoría de las construcciones que implican una expresión requieren en última instancia la expresión para indicar un valor. En tales casos, si la expresión real denota un espacio de nombres, un tipo, un grupo de métodos o nada, se produce un error en tiempo de compilación. Sin embargo, si la expresión denota un acceso de propiedad, un acceso de indexador o una variable, el valor de la propiedad, el indexador o la variable se sustituye implícitamente:

  • El valor de una variable es simplemente el valor almacenado actualmente en la ubicación de almacenamiento identificada por la variable. Una variable se considerará asignada definitivamente (§9.4) antes de que se pueda obtener su valor o, de lo contrario, se produzca un error en tiempo de compilación.
  • El valor de una expresión de acceso de propiedad se obtiene invocando el descriptor de acceso get de la propiedad . Si la propiedad no tiene ningún descriptor de acceso get, se produce un error en tiempo de compilación. De lo contrario, se realiza una invocación de miembro de función (§12.6.6) y el resultado de la invocación se convierte en el valor de la expresión de acceso de propiedad.
  • El valor de una expresión de acceso del indexador se obtiene invocando el descriptor de acceso get del indexador. Si el indexador no tiene ningún descriptor de acceso get, se produce un error en tiempo de compilación. De lo contrario, se realiza una invocación de miembro de función (§12.6.6) con la lista de argumentos asociada a la expresión de acceso del indexador y el resultado de la invocación se convierte en el valor de la expresión de acceso del indexador.
  • El valor de una expresión de tupla se obtiene aplicando una conversión de tupla implícita (§10.2.13) al tipo de la expresión de tupla. Se trata de un error para obtener el valor de una expresión de tupla que no tiene un tipo.

12.3 Enlace estático y dinámico

12.3.1 General

El enlace es el proceso de determinar a qué se refiere una operación, en función del tipo o valor de las expresiones (argumentos, operandos, receptores). Por ejemplo, el enlace de una llamada de método se determina en función del tipo del receptor y los argumentos. El enlace de un operador se determina en función del tipo de sus operandos.

En C# el enlace de una operación se determina normalmente en tiempo de compilación, en función del tipo en tiempo de compilación de sus subexpresiones. Del mismo modo, si una expresión contiene un error, el compilador detecta y notifica el error. Este enfoque se conoce como enlace estático.

Sin embargo, si una expresión es una expresión dinámica (es decir, tiene el tipo dynamic), esto indica que cualquier enlace en el que participa debe basarse en su tipo en tiempo de ejecución en lugar del tipo que tiene en tiempo de compilación. Por lo tanto, el enlace de dicha operación se aplaza hasta el momento en que se va a ejecutar la operación durante la ejecución del programa. Esto se conoce como enlace dinámico.

Cuando una operación está enlazada dinámicamente, el compilador realiza poca o ninguna comprobación. En su lugar, si se produce un error en el enlace en tiempo de ejecución, los errores se notifican como excepciones en tiempo de ejecución.

Las siguientes operaciones en C# están sujetas al enlace:

  • Acceso a miembros: e.M
  • Invocación de método: e.M(e₁,...,eᵥ)
  • Invocación de delegado: e(e₁,...,eᵥ)
  • Acceso a elementos: e[e₁,...,eᵥ]
  • Creación de objetos: nuevo C(e₁,...,eᵥ)
  • Operadores unarios sobrecargados: +, , ! -(solo negación lógica), ~, ++, --, , true,false
  • Operadores binarios sobrecargados: , , , %!=&<<<>||??^==>=&&>>/|*-+<=
  • Operadores de asignación: , , , +=-=, %=, ^=<<=&=*=|=/== ref=>>=
  • Conversiones implícitas y explícitas

Cuando no hay expresiones dinámicas implicadas, el valor predeterminado de C# es el enlace estático, lo que significa que los tipos en tiempo de compilación de subexpresiones se usan en el proceso de selección. Sin embargo, cuando una de las subexpresiones de las operaciones enumeradas anteriormente es una expresión dinámica, la operación se enlaza dinámicamente.

Es un error en tiempo de compilación si una invocación de método está enlazada dinámicamente y cualquiera de los parámetros, incluido el receptor, son parámetros de entrada.

12.3.2 Tiempo de enlace

El enlace estático tiene lugar en tiempo de compilación, mientras que el enlace dinámico tiene lugar en tiempo de ejecución. En las subclases siguientes, el término tiempo de enlace hace referencia a tiempo de compilación o en tiempo de ejecución, en función de cuándo se produzca el enlace.

Ejemplo: a continuación se muestran las nociones de enlace estático y dinámico y de tiempo de enlace:

object o = 5;
dynamic d = 5;
Console.WriteLine(5); // static binding to Console.WriteLine(int)
Console.WriteLine(o); // static binding to Console.WriteLine(object)
Console.WriteLine(d); // dynamic binding to Console.WriteLine(int)

Las dos primeras llamadas se enlazan estáticamente: la sobrecarga de Console.WriteLine se selecciona en función del tipo de tiempo de compilación de su argumento. Por lo tanto, el tiempo de enlace es tiempo de compilación.

La tercera llamada se enlaza dinámicamente: la sobrecarga de Console.WriteLine se selecciona en función del tipo en tiempo de ejecución de su argumento. Esto sucede porque el argumento es una expresión dinámica: su tipo en tiempo de compilación es dinámico. Por lo tanto, el tiempo de enlace de la tercera llamada es en tiempo de ejecución.

ejemplo final

12.3.3 Enlace dinámico

Esta subclausa es informativa.

El enlace dinámico permite a los programas de C# interactuar con objetos dinámicos, es decir, objetos que no siguen las reglas normales del sistema de tipos de C#. Los objetos dinámicos pueden ser objetos de otros lenguajes de programación con distintos sistemas de tipos, o pueden ser objetos que se configuran mediante programación para implementar su propia semántica de enlace para diferentes operaciones.

El mecanismo por el que un objeto dinámico implementa su propia semántica es definido por la implementación. Una interfaz determinada , definida de nuevo por la implementación, se implementa mediante objetos dinámicos para indicar al tiempo de ejecución de C# que tienen semántica especial. Por lo tanto, cada vez que las operaciones de un objeto dinámico están enlazadas dinámicamente, su propia semántica de enlace, en lugar de las de C# especificadas en esta especificación, tome el control.

Aunque el propósito del enlace dinámico es permitir la interoperación con objetos dinámicos, C# permite el enlace dinámico en todos los objetos, tanto si son dinámicos como si no. Esto permite una integración más fluida de objetos dinámicos, ya que los resultados de las operaciones en ellos pueden no ser objetos dinámicos, pero siguen siendo de un tipo desconocido para el programador en tiempo de compilación. Además, el enlace dinámico puede ayudar a eliminar el código basado en reflexión propenso a errores incluso cuando no hay objetos implicados en objetos dinámicos.

12.3.4 Tipos de subexpresiones

Cuando una operación está enlazada estáticamente, el tipo de una subexpresión (por ejemplo, un receptor y un argumento, un índice o un operando) siempre se considera el tipo en tiempo de compilación de esa expresión.

Cuando una operación se enlaza dinámicamente, el tipo de una subexpresión se determina de diferentes maneras en función del tipo de tiempo de compilación de la subexpresión:

  • Se considera que una subexpresión del tipo en tiempo de compilación dinámico tiene el tipo del valor real que la expresión se evalúa en tiempo de ejecución.
  • Subexpresión cuyo tipo de tiempo de compilación es un parámetro de tipo se considera que tiene el tipo al que está enlazado el parámetro de tipo en tiempo de ejecución.
  • De lo contrario, se considera que la subexpresión tiene su tipo en tiempo de compilación.

Operadores 12.4

12.4.1 General

Las expresiones se construyen con operandos y operadores. Los operadores de una expresión indican qué operaciones se aplican a los operandos.

Ejemplo: Algunos ejemplos de operadores son +, -, *, /y new. Algunos ejemplos de operandos son literales, campos, variables locales y expresiones. ejemplo final

Hay tres tipos de operadores:

  • Operadores unarios. Los operadores unarios toman un operando y usan notación de prefijo (como –x) o notación postfijo (como x++).
  • Operadores binarios. Los operadores binarios toman dos operandos y todos usan la notación infijo (como x + y).
  • Operador ternario. Solo existe un operador ternario, ?:, y toma tres operandos y usa la notación de infijo (c ? x : y).

El orden de evaluación de los operadores en una expresión viene determinado por la precedencia y la asociatividad de los operadores (§12.4.2).

Los operandos de una expresión se evalúan de izquierda a derecha.

Ejemplo: En F(i) + G(i++) * H(i), se llama al método F mediante el valor anterior de i, se llama al método G con el valor anterior de iy, por último, se llama al método H con el nuevo valor de i. Esto es independiente de y no está relacionado con la precedencia del operador. ejemplo final

Algunos operadores se pueden sobrecargar. La sobrecarga del operador (§12.4.3) permite especificar implementaciones de operador definidas por el usuario para las operaciones en las que uno o ambos operandos son de una clase o un tipo de estructura definidos por el usuario.

12.4.2 Precedencia y asociatividad del operador

Cuando una expresión contiene varios operadores, su precedencia controla el orden en el que se evalúan los operadores individuales.

Nota: Por ejemplo, la expresión x + y * z se evalúa como x + (y * z) porque el * operador tiene mayor prioridad que el operador binario + . nota final

La precedencia de un operador se establece mediante la definición de su producción gramatical asociada.

Nota: Por ejemplo, un additive_expression consta de una secuencia de multiplicative_expressionseparados por + operadores o - , lo que proporciona a los + operadores y - una prioridad inferior a los *operadores , /y % . nota final

Nota: En la tabla siguiente se resumen todos los operadores en orden de prioridad de mayor a menor:

Subclause Categoría Operadores
§12.8 Principal x.y x?.y f(x) a[x] a?[x] x++ x-- x! new typeof default checked unchecked delegate stackalloc
§12.9 Unario + - !x ~ ++x --x (T)x await x
§12.10 Multiplicativo * / %
§12.10 Aditivo + -
§12.11 Shift << >>
§12.12 Comprobación de tipos y relacional < > <= >= is as
§12.12 Igualdad == !=
§12.13 Y lógico &
§12.13 XOR lógico ^
§12.13 O lógico \|
§12.14 AND condicional &&
§12.14 OR condicional \|\|
§12.15 y §12.16 Expresión de fusión y lanzamiento nulas ?? throw x
§12.18 Condicional ?:
§12.21 y §12.19 Asignación y expresión lambda = = ref *= /= %= += -= <<= >>= &= ^= \|= =>

nota final

Cuando un operando se encuentra entre dos operadores con la misma precedencia, la asociatividad de los operadores controla el orden en que se realizan las operaciones:

  • Excepto los operadores de asignación y el operador de fusión null, todos los operadores binarios son asociativos a la izquierda, lo que significa que las operaciones se realizan de izquierda a derecha.

    Ejemplo: x + y + z se evalúa como (x + y) + z. ejemplo final

  • Los operadores de asignación, el operador de fusión null y el operador condicional (?:) son asociativos a la derecha, lo que significa que las operaciones se realizan de derecha a izquierda.

    Ejemplo: x = y = z se evalúa como x = (y = z). ejemplo final

La precedencia y la asociatividad pueden controlarse mediante paréntesis.

Ejemplo: x + y * z primero multiplica y por z y, a continuación, agrega el resultado a x, pero (x + y) * z primero agrega x y y , a continuación, multiplica el resultado por z. ejemplo final

Sobrecarga del operador 12.4.3

Todos los operadores unarios y binarios tienen implementaciones predefinidas. Además, las implementaciones definidas por el usuario se pueden introducir mediante la inclusión de declaraciones de operador (§15.10) en clases y estructuras. Las implementaciones de operador definidas por el usuario siempre tienen prioridad sobre las implementaciones de operador predefinidas: solo cuando no existan implementaciones de operador definidas por el usuario, se considerarán las implementaciones de operador predefinidas, como se describe en §12.4.4 y §12.4.5.

Los operadores unarios sobrecargables son:

+ - ! (solo negación lógica) ~ ++ -- true false

Nota: Aunque true y false no se usan explícitamente en expresiones (y, por tanto, no se incluyen en la tabla de precedencia de §12.4.2), se consideran operadores porque se invocan en varios contextos de expresión: expresiones booleanas (§12.24) y expresiones que implican los operadores lógicos condicionales (§12.18) y condicionales (§12.14). nota final

Nota: El operador null-forgiving (postfix !, §12.8.9) no es un operador sobrecargable. nota final

Los operadores binarios sobrecargables son:

+  -  *  /  %  &  |  ^  <<  >>  ==  !=  >  <  <=  >=

Solo se pueden sobrecargar los operadores enumerados anteriormente. En concreto, no es posible sobrecargar el acceso a miembros, la invocación de método o los =operadores , &&, =>checked??||typeofnew?:unchecked, default, y . asis

Cuando se sobrecarga un operador binario, el operador de asignación compuesto correspondiente, si existe, también se sobrecarga implícitamente.

Ejemplo: Una sobrecarga de operador * también es una sobrecarga del operador *=. Esto se describe más adelante en §12.21. ejemplo final

El propio (=) operador de asignación no se puede sobrecargar. Una asignación siempre realiza un almacén sencillo de un valor en una variable (§12.21.2).

Las operaciones de conversión, como (T)x, se sobrecargan proporcionando conversiones definidas por el usuario (§10.5).

Nota: Las conversiones definidas por el usuario no afectan al comportamiento de los is operadores o as . nota final

El acceso a elementos, como a[x], no se considera un operador sobrecargable. En su lugar, la indexación definida por el usuario se admite mediante indexadores (§15.9).

En expresiones, se hace referencia a los operadores mediante notación de operador y, en declaraciones, se hace referencia a los operadores mediante notación funcional. En la tabla siguiente se muestra la relación entre las notaciones funcionales y de operador para operadores unarios y binarios. En la primera entrada, «op» denota cualquier operador de prefijo unario sobrecargable. En la segunda entrada, «op» denota los operadores y -- postfijo ++ unarios. En la tercera entrada, «op» denota cualquier operador binario sobrecargable.

Nota: Para obtener un ejemplo de sobrecarga de los ++ operadores y -- , consulte §15.10.2. nota final

Notación del operador Notación funcional
«op» x operator «op»(x)
x «op» operator «op»(x)
x «op» y operator «op»(x, y)

Las declaraciones de operador definidas por el usuario siempre requieren que al menos uno de los parámetros sea del tipo de clase o estructura que contiene la declaración del operador.

Nota: Por lo tanto, no es posible que un operador definido por el usuario tenga la misma firma que un operador predefinido. nota final

Las declaraciones de operador definidas por el usuario no pueden modificar la sintaxis, la precedencia ni la asociatividad de un operador.

Ejemplo: el / operador siempre es un operador binario, siempre tiene el nivel de precedencia especificado en §12.4.2 y siempre es asociativo a la izquierda. ejemplo final

Nota: Aunque es posible que un operador definido por el usuario realice cualquier cálculo por favor, las implementaciones que producen resultados distintos de los que se esperan de forma intuitiva se desaconsejan. Por ejemplo, una implementación del operador == debe comparar los dos operandos para obtener igualdad y devolver un resultado adecuado bool . nota final

Las descripciones de operadores individuales de §12.9 a §12.21 especifican las implementaciones predefinidas de los operadores y las reglas adicionales que se aplican a cada operador. Las descripciones hacen uso de los términos resolución de sobrecarga del operador unario, resolución de sobrecarga de operadores binarios, promoción numérica y definiciones de operador elevado de las cuales se encuentran en las subclases siguientes.

12.4.4 Resolución de sobrecarga del operador unario

Una operación del formulario «op» x o x «op», donde «op» es un operador unario sobrecargable, y x es una expresión de tipo X, se procesa de la siguiente manera:

  • El conjunto de operadores candidatos definidos por el usuario proporcionados por X para la operación operator «op»(x) se determina mediante las reglas de §12.4.6.
  • Si el conjunto de operadores candidatos definidos por el usuario no está vacío, se convierte en el conjunto de operadores candidatos para la operación. De lo contrario, las implementaciones binarias operator «op» predefinidas, incluidas sus formas levantadas, se convierten en el conjunto de operadores candidatos para la operación. Las implementaciones predefinidas de un operador determinado se especifican en la descripción del operador. Los operadores predefinidos proporcionados por un tipo de enumeración o delegado solo se incluyen en este conjunto cuando el tipo de tiempo de enlace (o el tipo subyacente si es un tipo que acepta valores NULL) de cualquiera de los operandos es la enumeración o el tipo delegado.
  • Las reglas de resolución de sobrecarga de §12.6.4 se aplican al conjunto de operadores candidatos para seleccionar el mejor operador con respecto a la lista (x)de argumentos y este operador se convierte en el resultado del proceso de resolución de sobrecarga. Si la resolución de sobrecarga no puede seleccionar un único operador mejor, se produce un error en tiempo de enlace.

Resolución de sobrecarga del operador binario 12.4.5

Una operación del formulario x «op» y, donde «op» es un operador binario sobrecargable, x es una expresión de tipo X, y y es una expresión de tipo Y, se procesa de la siguiente manera:

  • Se determina el conjunto de operadores candidatos definidos por el usuario proporcionados por X y Y para la operación operator «op»(x, y) . El conjunto consta de la unión de los operadores candidatos proporcionados por X y los operadores candidatos proporcionados por Y, cada uno determinado mediante las reglas de §12.4.6. Para el conjunto combinado, los candidatos se combinan de la siguiente manera:
    • Si X y Y son convertibles de identidad, o si X y Y se derivan de un tipo base común, los operadores candidatos compartidos solo se producen en el conjunto combinado una vez.
    • Si hay una conversión de identidad entre X y Y, un operador «op»Y proporcionado por Y tiene el mismo tipo «op»X de valor devuelto proporcionado por X y los tipos de operando de tienen una conversión de «op»Y identidad a los tipos de operando correspondientes de «op»X , solo «op»X se produce en el conjunto.
  • Si el conjunto de operadores candidatos definidos por el usuario no está vacío, se convierte en el conjunto de operadores candidatos para la operación. De lo contrario, las implementaciones binarias operator «op» predefinidas, incluidas sus formas levantadas, se convierten en el conjunto de operadores candidatos para la operación. Las implementaciones predefinidas de un operador determinado se especifican en la descripción del operador. Para los operadores de enumeración y delegado predefinidos, los únicos operadores considerados son los proporcionados por un tipo de enumeración o delegado que es el tipo de tiempo de enlace de uno de los operandos.
  • Las reglas de resolución de sobrecarga de §12.6.4 se aplican al conjunto de operadores candidatos para seleccionar el mejor operador con respecto a la lista (x, y)de argumentos y este operador se convierte en el resultado del proceso de resolución de sobrecarga. Si la resolución de sobrecarga no puede seleccionar un único operador mejor, se produce un error en tiempo de enlace.

12.4.6 Operadores definidos por el usuario candidatos

Dado un tipo T y una operación operator «op»(A), donde «op» es un operador sobrecargable y A es una lista de argumentos, el conjunto de operadores candidatos definidos por el usuario proporcionados por T para el operador «op»(A) se determina de la siguiente manera:

  • Determine el tipo T₀. Si T es un tipo de valor que acepta valores NULL, T₀ es su tipo subyacente; de lo contrario, T₀ es igual a T.
  • Para todas las declaraciones de T₀ y todas las operator «op» formas levantadas de estos operadores, si al menos un operador es aplicable (§12.6.4.2) con respecto a la lista Ade argumentos , el conjunto de operadores candidatos consta de todos estos operadores aplicables en T₀.
  • De lo contrario, si T₀ es object, el conjunto de operadores candidatos está vacío.
  • De lo contrario, el conjunto de operadores candidatos proporcionado por T₀ es el conjunto de operadores candidatos proporcionados por la clase base directa de T₀o la clase base efectiva de si T₀ es un parámetro de T₀ tipo.

12.4.7 Promociones numéricas

12.4.7.1 General

Esta subclausa es informativa.

§12.4.7 y sus subclases son un resumen del efecto combinado de:

  • reglas para conversiones numéricas implícitas (§10.2.3);
  • las reglas para mejorar la conversión (§12.6.4.7); y
  • los operadores aritméticos disponibles (§12.10), relacionales (§12.12) y lógicos enteros (§12.13.2).

La promoción numérica consta de realizar automáticamente ciertas conversiones implícitas de los operandos de los operadores unarios predefinidos y numéricos binarios. La promoción numérica no es un mecanismo distinto, sino un efecto de aplicar la resolución de sobrecarga a los operadores predefinidos. La promoción numérica no afecta específicamente a la evaluación de operadores definidos por el usuario, aunque los operadores definidos por el usuario se pueden implementar para mostrar efectos similares.

Como ejemplo de promoción numérica, considere las implementaciones predefinidas del operador binario * :

int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);

Cuando se aplican reglas de resolución de sobrecarga (§12.6.4) a este conjunto de operadores, el efecto es seleccionar el primero de los operadores para los que existen conversiones implícitas de los tipos de operando.

Ejemplo: para la operación b * s, donde b es y byte s es una short, la resolución de sobrecarga selecciona operator *(int, int) como el mejor operador. Por lo tanto, el efecto es que b y se convierten en inty el tipo del resultado es ints . Del mismo modo, para la operación , donde i es y d int es , overload doublela resolución selecciona operator *(double, double) como el mejor i * doperador. ejemplo final

Fin del texto informativo.

12.4.7.2 Promociones numéricas unarias

Esta subclausa es informativa.

La promoción numérica unaria se produce para los operandos de los operadores unarios predefinidos +, -y ~ . La promoción numérica unaria consiste simplemente en convertir operandos de tipo sbyte, , byteshort, ushorto char al tipo int. Además, para el operador unario : la promoción numérica unaria convierte operandos de tipo uint en el tipo long.

Fin del texto informativo.

12.4.7.3 Promociones numéricas binarias

Esta subclausa es informativa.

La promoción numérica binaria se produce para los operandos de los operadores binarios predefinidos +, , -, ==/&|^%*><!=>=y <= binarios. La promoción numérica binaria convierte implícitamente ambos operandos en un tipo común que, en el caso de los operadores no relacionales, también se convierte en el tipo de resultado de la operación. La promoción numérica binaria consta de aplicar las siguientes reglas, en el orden en que aparecen aquí:

  • Si cualquiera de los operandos es de tipo decimal, el otro operando se convierte en el tipo decimalo se produce un error en tiempo de enlace si el otro operando es de tipo float o double.
  • De lo contrario, si cualquiera de los operandos es de tipo double, el otro operando se convierte en el tipo double.
  • De lo contrario, si cualquiera de los operandos es de tipo float, el otro operando se convierte en el tipo float.
  • De lo contrario, si cualquiera de los operandos es de tipo ulong, el otro operando se convierte en el tipo ulongo se produce un error en tiempo de enlace si el otro operando es de type sbyte, short, into long.
  • De lo contrario, si cualquiera de los operandos es de tipo long, el otro operando se convierte en el tipo long.
  • De lo contrario, si cualquiera de los operandos es de tipo uint y el otro operando es de tipo sbyte, shorto int, ambos operandos se convierten al tipo long.
  • De lo contrario, si cualquiera de los operandos es de tipo uint, el otro operando se convierte en el tipo uint.
  • De lo contrario, ambos operandos se convierten en el tipo int.

Nota: La primera regla no permite ninguna operación que combine el decimal tipo con los double tipos y float . La regla sigue del hecho de que no hay conversiones implícitas entre el decimal tipo y los double tipos y float . nota final

Nota: Tenga en cuenta también que no es posible que un operando sea de tipo ulong cuando el otro operando es de un tipo entero firmado. El motivo es que no existe ningún tipo entero que pueda representar el intervalo completo de ulong tipos enteros así como los tipos enteros firmados. nota final

En ambos casos anteriores, se puede usar una expresión de conversión para convertir explícitamente un operando a un tipo compatible con el otro operando.

Ejemplo: en el código siguiente

decimal AddPercent(decimal x, double percent) =>
    x * (1.0 + percent / 100.0);

Se produce un error en tiempo de enlace porque no decimal se puede multiplicar por .double El error se resuelve convirtiendo explícitamente el segundo operando en decimal, como se indica a continuación:

decimal AddPercent(decimal x, double percent) =>
    x * (decimal)(1.0 + percent / 100.0);

ejemplo final

Fin del texto informativo.

12.4.8 Operadores de elevación

Los operadores de elevación permiten que los operadores predefinidos y definidos por el usuario que operan en tipos de valor que no aceptan valores NULL también se usen con formas que aceptan valores NULL de esos tipos. Los operadores de elevación se construyen a partir de operadores predefinidos y definidos por el usuario que cumplen ciertos requisitos, como se describe en lo siguiente:

  • Para los operadores +unarios , ++, -, --( !negación lógica) y ~, existe una forma levantada de un operador si los tipos de operando y resultado son tipos de valor que no aceptan valores NULL. El formulario elevado se construye agregando un único ? modificador al operando y a los tipos de resultado. El operador elevado genera un null valor si el operando es null. De lo contrario, el operador elevado desencapsula el operando, aplica el operador subyacente y ajusta el resultado.
  • Para los operadores +binarios , -, *, %|&^/<<y >>, existe una forma levantada de un operador si los tipos de resultado y operando son todos los tipos de valor que no aceptan valores NULL. El formulario elevado se construye agregando un único ? modificador a cada operando y tipo de resultado. El operador elevado genera un null valor si uno o ambos operandos son null (una excepción que es los & operadores y | del bool? tipo, como se describe en §12.13.5). De lo contrario, el operador elevado desencapsula los operandos, aplica el operador subyacente y ajusta el resultado.
  • Para los operadores == de igualdad y !=, existe una forma levantada de un operador si los tipos de operando son tipos de valor que no aceptan valores NULL y si el tipo de resultado es bool. El formulario elevado se construye agregando un único ? modificador a cada tipo de operando. El operador elevado considera dos null valores iguales y un null valor distinto de cualquier valor que nonull sea. Si ambos operandos no son,null el operador elevado desencapsula los operandos y aplica el operador subyacente para generar el bool resultado.
  • Para los operadores <relacionales , >, <=y >=, existe una forma levantada de un operador si los tipos de operando son tipos de valor que no aceptan valores NULL y si el tipo de resultado es bool. El formulario elevado se construye agregando un único ? modificador a cada tipo de operando. El operador elevado genera el valor false si uno o ambos operandos son null. De lo contrario, el operador elevado desencapsula los operandos y aplica el operador subyacente para generar el bool resultado.

12.5 Búsqueda de miembros

12.5.1 General

Una búsqueda de miembros es el proceso por el que se determina el significado de un nombre en el contexto de un tipo. Una búsqueda de miembros puede producirse como parte de la evaluación de un simple_name (§12.8.4) o un member_access (§12.8.7) en una expresión. Si el simple_name o member_access se produce como el primary_expression de un invocation_expression (§12.8.10.2), se dice que se invoca al miembro.

Si un miembro es un método o evento, o si es una constante, campo o propiedad de un tipo delegado (§20) o el tipo dynamic (§8.2.4), se dice que el miembro es invocable.

La búsqueda de miembros considera no solo el nombre de un miembro, sino también el número de parámetros de tipo que tiene el miembro y si el miembro es accesible. Para la búsqueda de miembros, los métodos genéricos y los tipos genéricos anidados tienen el número de parámetros de tipo indicados en sus declaraciones respectivas y todos los demás miembros tienen cero parámetros de tipo.

Una búsqueda de miembro de un nombre N con K argumentos de tipo en un tipo T se procesa de la siguiente manera:

  • En primer lugar, se determina un conjunto de miembros accesibles denominados N :
    • Si T es un parámetro de tipo, el conjunto es la unión de los conjuntos de miembros accesibles denominados N en cada uno de los tipos especificados como una restricción principal o una restricción secundaria (§15.2.5) para T, junto con el conjunto de miembros accesibles denominados N en object.
    • De lo contrario, el conjunto consta de todos los miembros accesibles (§7.5) denominados N en T, incluidos los miembros heredados y los miembros accesibles denominados N en object. Si T es un tipo construido, el conjunto de miembros se obtiene sustituyendo argumentos de tipo como se describe en §15.3.3. Los miembros que incluyen un override modificador se excluyen del conjunto.
  • A continuación, si K es cero, se quitan todos los tipos anidados cuyas declaraciones incluyen parámetros de tipo. Si K no es cero, se quitan todos los miembros con un número diferente de parámetros de tipo. Cuando K es cero, los métodos que tienen parámetros de tipo no se quitan, ya que el proceso de inferencia de tipos (§12.6.3) podría deducir los argumentos de tipo.
  • A continuación, si se invoca el miembro, todos los miembros no invocables se quitan del conjunto.
  • A continuación, los miembros ocultos por otros miembros se quitan del conjunto. Para cada miembro S.M del conjunto, donde S es el tipo en el que se declara el miembro M , se aplican las reglas siguientes:
    • Si M es un miembro constante, campo, propiedad, evento o enumeración, todos los miembros declarados en un tipo base de S se quitan del conjunto.
    • Si M es una declaración de tipo, todos los tipos no declarados en un tipo base de S se quitan del conjunto y todas las declaraciones de tipo con el mismo número de parámetros M de tipo que se declaran en un tipo base de S se quitan del conjunto.
    • Si M es un método, todos los miembros que no son métodos declarados en un tipo base de S se quitan del conjunto.
  • A continuación, los miembros de interfaz ocultos por los miembros de clase se quitan del conjunto. Este paso solo tiene un efecto si T es un parámetro de tipo y T tiene una clase base efectiva distinta object de y un conjunto de interfaz efectivo no vacío (§15.2.5). Para cada miembro S.M del conjunto, donde S es el tipo en el que se declara el miembro M , se aplican las reglas siguientes si S es una declaración de clase distinta de object:
    • Si M es una constante, campo, propiedad, evento, miembro de enumeración o declaración de tipo, todos los miembros declarados en una declaración de interfaz se quitan del conjunto.
    • Si M es un método, todos los miembros que no son métodos declarados en una declaración de interfaz se quitan del conjunto y todos los métodos con la misma firma M declarada en una declaración de interfaz se quitan del conjunto.
  • Por último, después de quitar miembros ocultos, se determina el resultado de la búsqueda:
    • Si el conjunto consta de un único miembro que no es un método, este miembro es el resultado de la búsqueda.
    • De lo contrario, si el conjunto contiene solo métodos, este grupo de métodos es el resultado de la búsqueda.
    • De lo contrario, la búsqueda es ambigua y se produce un error en tiempo de enlace.

Para las búsquedas de miembros en tipos distintos de los parámetros de tipo e interfaces, y las búsquedas de miembros en interfaces que son estrictamente de herencia única (cada interfaz de la cadena de herencia tiene exactamente cero o una interfaz base directa), el efecto de las reglas de búsqueda es simplemente que los miembros derivados ocultan los miembros base con el mismo nombre o firma. Estas búsquedas de herencia única nunca son ambiguas. Las ambigüedades que pueden surgir de búsquedas de miembros en interfaces de herencia múltiple se describen en §18.4.6.

Nota: Esta fase solo tiene en cuenta un tipo de ambigüedad. Si la búsqueda de miembros da como resultado un grupo de métodos, es posible que se produzca un error en los usos adicionales del grupo de métodos debido a ambigüedad, como se describe en §12.6.4.1 y §12.6.6.2. nota final

12.5.2 Tipos base

Para fines de búsqueda de miembros, se considera que un tipo T tiene los siguientes tipos base:

  • Si T es object o dynamic, T no tiene ningún tipo base.
  • Si T es un enum_type, los tipos base de son los tipos System.Enumde T clase , System.ValueTypey object.
  • Si T es un struct_type, los tipos base de son los tipos System.ValueType de T clase y object.

    Nota: Un nullable_value_type es un struct_type (§8.3.1). nota final

  • Si T es un class_type, los tipos base de son las clases base de T , incluido el tipo objectde Tclase .
  • Si T es un interface_type, los tipos base de T son las interfaces base de T y el tipo objectde clase .
  • Si T es un array_type, los tipos base de son los tipos System.Array de T clase y object.
  • Si T es un delegate_type, los tipos base de son los tipos System.Delegate de T clase y object.

12.6 Miembros de la función

12.6.1 General

Los miembros de función son miembros que contienen instrucciones ejecutables. Los miembros de función son siempre miembros de tipos y no pueden ser miembros de espacios de nombres. C# define las siguientes categorías de miembros de función:

  • Métodos
  • Propiedades
  • Eventos
  • Indizadores
  • Operadores definidos por el usuario
  • Constructores de instancias
  • Constructores estáticos
  • Finalizadores

Excepto los finalizadores y los constructores estáticos (que no se pueden invocar explícitamente), las instrucciones contenidas en los miembros de función se ejecutan a través de invocaciones de miembro de función. La sintaxis real para escribir una invocación de miembro de función depende de la categoría de miembro de función determinada.

La lista de argumentos (§12.6.2) de una invocación de miembro de función proporciona valores reales o referencias de variables para los parámetros del miembro de función.

Las invocaciones de métodos genéricos pueden emplear la inferencia de tipos para determinar el conjunto de argumentos de tipo que se van a pasar al método . Este proceso se describe en §12.6.3.

Las invocaciones de métodos, indizadores, operadores y constructores de instancia emplean resolución de sobrecarga para determinar cuál de un conjunto candidato de miembros de función que se va a invocar. Este proceso se describe en §12.6.4.

Una vez que se ha identificado un miembro de función determinado en tiempo de enlace, posiblemente mediante la resolución de sobrecarga, el proceso en tiempo de ejecución real de invocar al miembro de función se describe en §12.6.6.

Nota: En la tabla siguiente se resume el procesamiento que tiene lugar en construcciones que implican las seis categorías de miembros de función que se pueden invocar explícitamente. En la tabla , e, x, ye value indican expresiones clasificadas como variables o valores, T indica una expresión clasificada como un tipo, F es el nombre simple de un método y P es el nombre simple de una propiedad.

Construcción Ejemplo Descripción
Invocación de método F(x, y) La resolución de sobrecarga se aplica para seleccionar el mejor método F de la clase o estructura contenedora. El método se invoca con la lista (x, y)de argumentos . Si el método no statices , la expresión de instancia es this.
T.F(x, y) La resolución de sobrecarga se aplica para seleccionar el mejor método F de la clase o estructura T. Se produce un error en tiempo de enlace si el método no statices . El método se invoca con la lista (x, y)de argumentos .
e.F(x, y) La resolución de sobrecarga se aplica para seleccionar el mejor método F de la clase, estructura o interfaz dada por el tipo de e. Se produce un error en tiempo de enlace si el método es static. El método se invoca con la expresión e de instancia y la lista (x, y)de argumentos .
Property Access P Se invoca el descriptor de acceso get de la propiedad P en la clase o estructura contenedora. Se produce un error en tiempo de compilación si P es de solo escritura. Si P no statices , la expresión de instancia es this.
P = value El descriptor de acceso set de la propiedad P de la clase o estructura contenedora se invoca con la lista (value)de argumentos . Se produce un error en tiempo de compilación si P es de solo lectura. Si P no statices , la expresión de instancia es this.
T.P Se invoca el descriptor de acceso get de la propiedad P en la clase o estructura T . Se produce un error en tiempo de compilación si P no static es o si P es de solo escritura.
T.P = value El descriptor de acceso set de la propiedad P de la clase o estructura T se invoca con la lista (value)de argumentos . Se produce un error en tiempo de compilación si P no static es o si P es de solo lectura.
e.P El descriptor de acceso get de la propiedad P de la clase, estructura o interfaz dada por el tipo de E se invoca con la expresión ede instancia . Se produce un error en tiempo de enlace si P es static o si P es de solo escritura.
e.P = value El descriptor de acceso set de la propiedad P de la clase, estructura o interfaz dada por el tipo de E se invoca con la expresión e de instancia y la lista (value)de argumentos . Se produce un error en tiempo de enlace si P es static o si P es de solo lectura.
Acceso a eventos E += value Se invoca el descriptor de acceso add del evento E en la clase o estructura contenedora. Si E no statices , la expresión de instancia es this.
E -= value Se invoca el descriptor de acceso remove del evento E en la clase o estructura contenedora. Si E no statices , la expresión de instancia es this.
T.E += value Se invoca el descriptor de acceso add del evento E en la clase o estructura T . Se produce un error en tiempo de enlace si E no statices .
T.E -= value Se invoca el descriptor de acceso remove del evento E en la clase o estructura T . Se produce un error en tiempo de enlace si E no statices .
e.E += value El descriptor de acceso add del evento E en la clase, estructura o interfaz dada por el tipo de E se invoca con la expresión ede instancia . Se produce un error en tiempo de enlace si E es static.
e.E -= value El descriptor de acceso remove del evento E en la clase, estructura o interfaz dada por el tipo de E se invoca con la expresión ede instancia . Se produce un error en tiempo de enlace si E es static.
Acceso a indizador e[x, y] La resolución de sobrecarga se aplica para seleccionar el mejor indexador de la clase, estructura o interfaz dada por el tipo de e. El descriptor de acceso get del indexador se invoca con la expresión e de instancia y la lista (x, y)de argumentos . Se produce un error en tiempo de enlace si el indexador es de solo escritura.
e[x, y] = value La resolución de sobrecarga se aplica para seleccionar el mejor indexador de la clase, estructura o interfaz dada por el tipo de e. El descriptor de acceso set del indexador se invoca con la expresión e de instancia y la lista (x, y, value)de argumentos . Se produce un error en tiempo de enlace si el indexador es de solo lectura.
Invocación de operador -x La resolución de sobrecarga se aplica para seleccionar el mejor operador unario de la clase o estructura dada por el tipo de x. El operador seleccionado se invoca con la lista (x)de argumentos .
x + y La resolución de sobrecarga se aplica para seleccionar el mejor operador binario de las clases o estructuras dadas por los tipos de x y y. El operador seleccionado se invoca con la lista (x, y)de argumentos .
Invocación del constructor de instancia new T(x, y) La resolución de sobrecarga se aplica para seleccionar el mejor constructor de instancia de la clase o estructura T. El constructor de instancia se invoca con la lista (x, y)de argumentos .

nota final

12.6.2 Listas de argumentos

12.6.2.1 General

Cada invocación de miembro de función y delegado incluye una lista de argumentos, que proporciona valores reales o referencias a variables para los parámetros del miembro de función. La sintaxis para especificar la lista de argumentos de una invocación de miembro de función depende de la categoría de miembro de función:

  • Por ejemplo, los constructores, métodos, indizadores y delegados, los argumentos se especifican como un argument_list, como se describe a continuación. Para los indexadores, al invocar el descriptor de acceso set, la lista de argumentos incluye además la expresión especificada como el operando derecho del operador de asignación.

    Nota: Este argumento adicional no se usa para la resolución de sobrecargas, solo durante la invocación del descriptor de acceso set. nota final

  • En el caso de las propiedades, la lista de argumentos está vacía al invocar el descriptor de acceso get y consta de la expresión especificada como el operando derecho del operador de asignación al invocar el descriptor de acceso set.
  • En el caso de los eventos, la lista de argumentos consta de la expresión especificada como el operando derecho del += operador o -= .
  • En el caso de los operadores definidos por el usuario, la lista de argumentos consta del operando único del operador unario o de los dos operandos del operador binario.

Los argumentos de las propiedades (§15.7) y los eventos (§15.8) siempre se pasan como parámetros de valor (§15.6.2.2). Los argumentos de los operadores definidos por el usuario (§15.10) siempre se pasan como parámetros de valor (§15.6.2.2) o parámetros de entrada (§9.2.8). Los argumentos de los indexadores (§15.9) siempre se pasan como parámetros de valor (§15.6.2.2), parámetros de entrada (§9.2.8) o matrices de parámetros (§15.6.2.4). Los parámetros de salida y referencia no se admiten para estas categorías de miembros de función.

Los argumentos de un constructor de instancia, método, indexador o invocación de delegado se especifican como un argument_list:

argument_list
    : argument (',' argument)*
    ;

argument
    : argument_name? argument_value
    ;

argument_name
    : identifier ':'
    ;

argument_value
    : expression
    | 'in' variable_reference
    | 'ref' variable_reference
    | 'out' variable_reference
    ;

Un argument_list consta de uno o varios argumentos, separados por comas. Cada argumento consta de un argument_name opcional seguido de un argument_value. Un argumento con un argument_name se conoce como argumento con nombre, mientras que un argumento sin un argument_name es un argumento posicional.

El argument_value puede adoptar una de las siguientes formas:

  • Expresión que indica que el argumento se pasa como un parámetro de valor o se transforma en un parámetro de entrada y, a continuación, se pasa como eso, según lo determinado por (§12.6.4.2 y descrito en §12.6.2.3.
  • La palabra clave in seguida de un variable_reference (§9.5), que indica que el argumento se pasa como parámetro de entrada (§15.6.2.3.2). Se asignará definitivamente una variable (§9.4) antes de que se pueda pasar como parámetro de entrada.
  • Palabra clave ref seguida de un variable_reference (§9.5), que indica que el argumento se pasa como parámetro de referencia (§15.6.2.3.3). Se asignará definitivamente una variable (§9.4) antes de que se pueda pasar como parámetro de referencia.
  • La palabra clave out seguida de un variable_reference (§9.5), que indica que el argumento se pasa como parámetro de salida (§15.6.2.3.4). Una variable se considera asignada definitivamente (§9.4) después de una invocación de miembro de función en la que la variable se pasa como parámetro de salida.

El formulario determina el modo de paso de parámetros del argumento: valor, entrada, referencia o salida, respectivamente. Sin embargo, como se mencionó anteriormente, un argumento con el modo de paso de valores puede transformarse en uno con el modo de paso de entrada.

Pasar un campo volátil (§15.5.4) como parámetro de entrada, salida o referencia provoca una advertencia, ya que el método invocado no puede tratar el campo como volátil.

12.6.2.2 Parámetros correspondientes

Para cada argumento de una lista de argumentos, debe haber un parámetro correspondiente en el miembro de función o delegado que se invoca.

La lista de parámetros usada en lo siguiente se determina de la siguiente manera:

  • En el caso de los métodos virtuales e indexadores definidos en clases, la lista de parámetros se elige de la primera declaración o invalidación del miembro de función que se encuentra al comenzar con el tipo estático del receptor y buscar en sus clases base.
  • Para los métodos parciales, se usa la lista de parámetros de la declaración de método parcial de definición.
  • Para todos los demás miembros de función y delegados solo hay una lista de parámetros única, que es la que se usa.

La posición de un argumento o parámetro se define como el número de argumentos o parámetros anteriores a él en la lista de argumentos o la lista de parámetros.

Los parámetros correspondientes para los argumentos miembro de función se establecen de la siguiente manera:

  • Argumentos de la argument_list de constructores de instancia, métodos, indexadores y delegados:
    • Argumento posicional donde un parámetro se produce en la misma posición de la lista de parámetros corresponde a ese parámetro, a menos que el parámetro sea una matriz de parámetros y se invoque el miembro de función en su forma expandida.
    • Argumento posicional de un miembro de función con una matriz de parámetros invocada en su forma expandida, que se produce en o después de la posición de la matriz de parámetros en la lista de parámetros, corresponde a un elemento de la matriz de parámetros.
    • Un argumento con nombre corresponde al parámetro del mismo nombre en la lista de parámetros.
    • Para los indexadores, al invocar el descriptor de acceso set, la expresión especificada como operando derecho del operador de asignación corresponde al parámetro implícito value de la declaración del descriptor de acceso set.
  • En el caso de las propiedades, al invocar el descriptor de acceso get no hay argumentos. Al invocar el descriptor de acceso set, la expresión especificada como operando derecho del operador de asignación corresponde al parámetro de valor implícito de la declaración del descriptor de acceso set.
  • Para los operadores unarios definidos por el usuario (incluidas las conversiones), el operando único corresponde al parámetro único de la declaración de operador.
  • Para los operadores binarios definidos por el usuario, el operando izquierdo corresponde al primer parámetro y el operando derecho corresponde al segundo parámetro de la declaración de operador.
  • Un argumento sin nombre corresponde a ningún parámetro cuando es después de un argumento con nombre fuera de posición o un argumento con nombre que corresponde a una matriz de parámetros.

    Nota: Esto impide void M(bool a = true, bool b = true, bool c = true); que M(c: false, valueB);. El primer argumento se usa fuera de posición (el argumento se usa en la primera posición, pero el parámetro denominado c está en tercera posición), por lo que se debe nombrar los argumentos siguientes. En otras palabras, los argumentos con nombre no finales solo se permiten cuando el nombre y la posición dan como resultado encontrar el mismo parámetro correspondiente. nota final

12.6.2.3 Evaluación en tiempo de ejecución de listas de argumentos

Durante el procesamiento en tiempo de ejecución de una invocación de miembro de función (§12.6.6), las expresiones o referencias de variables de una lista de argumentos se evalúan en orden, de izquierda a derecha, como se indica a continuación:

  • Para un argumento value, si el modo de paso del parámetro es value

    • la expresión de argumento se evalúa y se realiza una conversión implícita (§10.2) al tipo de parámetro correspondiente. El valor resultante se convierte en el valor inicial del parámetro value en la invocación del miembro de función.

    • De lo contrario, el modo de paso del parámetro es input. Si el argumento es una referencia de variable y existe una conversión de identidad (§10.2.2) entre el tipo del argumento y el tipo del parámetro, la ubicación de almacenamiento resultante se convierte en la ubicación de almacenamiento representada por el parámetro en la invocación del miembro de función. De lo contrario, se crea una ubicación de almacenamiento con el mismo tipo que el del parámetro correspondiente. La expresión de argumento se evalúa y se realiza una conversión implícita (§10.2) al tipo de parámetro correspondiente. El valor resultante se almacena dentro de esa ubicación de almacenamiento. Esa ubicación de almacenamiento se representa mediante el parámetro de entrada en la invocación del miembro de función.

      Ejemplo: dadas las siguientes declaraciones y llamadas a métodos:

      static void M1(in int p1) { ... }
      int i = 10;
      M1(i);         // i is passed as an input argument
      M1(i + 5);     // transformed to a temporary input argument
      

      En la M1(i) llamada al método, i se pasa como argumento de entrada, porque se clasifica como una variable y tiene el mismo tipo int que el parámetro de entrada. En la M1(i + 5) llamada al método, se crea una variable sin int nombre, se inicializa con el valor del argumento y, a continuación, se pasa como argumento de entrada. Consulte §12.6.4.2 y §12.6.4.4.

      ejemplo final

  • Para un argumento de entrada, salida o referencia, la referencia de variable se evalúa y la ubicación de almacenamiento resultante se convierte en la ubicación de almacenamiento representada por el parámetro en la invocación del miembro de función. Para un argumento de entrada o referencia, la variable se asignará definitivamente en el punto de la llamada al método. Si la referencia de variable se proporciona como argumento de salida o es un elemento de matriz de un reference_type, se realiza una comprobación en tiempo de ejecución para asegurarse de que el tipo de elemento de la matriz es idéntico al tipo del parámetro . Si se produce un error en esta comprobación, se produce una System.ArrayTypeMismatchException excepción .

Nota: Esta comprobación en tiempo de ejecución es necesaria debido a la covarianza de matriz (§17.6). nota final

Ejemplo: en el código siguiente

class Test
{
    static void F(ref object x) {...}

    static void Main()
    {
        object[] a = new object[10];
        object[] b = new string[10];
        F(ref a[0]); // Ok
        F(ref b[1]); // ArrayTypeMismatchException
    }
}

la segunda invocación de F hace que se produzca un System.ArrayTypeMismatchException elemento porque el tipo de elemento real de b es string y no objectes .

ejemplo final

Los métodos, indizadores y constructores de instancia pueden declarar su parámetro más adecuado para que sea una matriz de parámetros (§15.6.2.4). Estos miembros de función se invocan en su forma normal o en su forma expandida según cuál sea aplicable (§12.6.4.2):

  • Cuando se invoca un miembro de función con una matriz de parámetros en su forma normal, el argumento proporcionado para la matriz de parámetros será una expresión única que sea implícitamente convertible (§10.2) al tipo de matriz de parámetros. En este caso, la matriz de parámetros actúa exactamente como un parámetro de valor.
  • Cuando se invoca un miembro de función con una matriz de parámetros en su forma expandida, la invocación especificará cero o más argumentos posicionales para la matriz de parámetros, donde cada argumento es una expresión que es implícitamente convertible (§10.2) al tipo de elemento de la matriz de parámetros. En este caso, la invocación crea una instancia del tipo de matriz de parámetros con una longitud correspondiente al número de argumentos, inicializa los elementos de la instancia de matriz con los valores de argumento especificados y usa la instancia de matriz recién creada como argumento real.

Las expresiones de una lista de argumentos siempre se evalúan en orden textual.

Ejemplo: Por lo tanto, el ejemplo

class Test
{
    static void F(int x, int y = -1, int z = -2) =>
        Console.WriteLine($"x = {x}, y = {y}, z = {z}");

    static void Main()
    {
        int i = 0;
        F(i++, i++, i++);
        F(z: i++, x: i++);
    }
}

genera el resultado

x = 0, y = 1, z = 2
x = 4, y = -1, z = 3

ejemplo final

Cuando se invoca un miembro de función con una matriz de parámetros en su forma expandida con al menos un argumento expandido, la invocación se procesa como si una expresión de creación de matriz con un inicializador de matriz (§12.8.17.5) se insertara alrededor de los argumentos expandidos. Se pasa una matriz vacía cuando no hay argumentos para la matriz de parámetros; no se especifica si la referencia pasada es a una matriz vacía recién asignada o existente.

Ejemplo: Dada la declaración

void F(int x, int y, params object[] args);

las siguientes invocaciones del formato expandido del método

F(10, 20, 30, 40);
F(10, 20, 1, "hello", 3.0);

corresponde exactamente a

F(10, 20, new object[] { 30, 40 });
F(10, 20, new object[] { 1, "hello", 3.0 });

ejemplo final

Cuando se omiten argumentos de un miembro de función con los parámetros opcionales correspondientes, los argumentos predeterminados de la declaración de miembro de función se pasan implícitamente. (Esto puede implicar la creación de una ubicación de almacenamiento, como se ha descrito anteriormente).

Nota: Dado que siempre son constantes, su evaluación no afectará a la evaluación de los argumentos restantes. nota final

12.6.3 Inferencia de tipos

12.6.3.1 General

Cuando se llama a un método genérico sin especificar argumentos de tipo, un proceso de inferencia de tipos intenta inferir argumentos de tipo para la llamada. La presencia de la inferencia de tipos permite usar una sintaxis más conveniente para llamar a un método genérico y permite al programador evitar especificar información de tipo redundante.

Ejemplo:

class Chooser
{
    static Random rand = new Random();

    public static T Choose<T>(T first, T second) =>
        rand.Next(2) == 0 ? first : second;
}

class A
{
    static void M()
    {
        int i = Chooser.Choose(5, 213); // Calls Choose<int>
        string s = Chooser.Choose("apple", "banana"); // Calls Choose<string>
    }
}

A través de la inferencia de tipos, los argumentos int de tipo y string se determinan de los argumentos al método .

ejemplo final

La inferencia de tipos se produce como parte del procesamiento en tiempo de enlace de una invocación de método (§12.8.10.2) y tiene lugar antes del paso de resolución de sobrecarga de la invocación. Cuando se especifica un grupo de métodos determinado en una invocación de método y no se especifica ningún argumento de tipo como parte de la invocación de método, la inferencia de tipos se aplica a cada método genérico del grupo de métodos. Si la inferencia de tipos se realiza correctamente, los argumentos de tipo inferido se usan para determinar los tipos de argumentos para la resolución de sobrecarga posterior. Si la resolución de sobrecarga elige un método genérico como el que se va a invocar, los argumentos de tipo inferido se usan como argumentos de tipo para la invocación. Si se produce un error en la inferencia de tipos para un método determinado, ese método no participa en la resolución de sobrecargas. El error de inferencia de tipo, en y de sí mismo, no provoca un error en tiempo de enlace. Sin embargo, a menudo conduce a un error en tiempo de enlace cuando la resolución de sobrecarga no encuentra ningún método aplicable.

Si cada argumento proporcionado no corresponde exactamente a un parámetro del método (§12.6.2.2), o si hay un parámetro no opcional sin ningún argumento correspondiente, se produce un error inmediatamente en la inferencia. De lo contrario, supongamos que el método genérico tiene la siguiente firma:

Tₑ M<X₁...Xᵥ>(T₁ p₁ ... Tₓ pₓ)

Con una llamada de método del formulario M(E₁ ...Eₓ) , la tarea de inferencia de tipo es buscar argumentos de tipo únicos para cada uno de los parámetros S₁...Sᵥ X₁...Xᵥ de tipo para que la llamada M<S₁...Sᵥ>(E₁...Eₓ) sea válida.

El proceso de inferencia de tipos se describe a continuación como un algoritmo. Un compilador conforme se puede implementar mediante un enfoque alternativo, siempre que alcance el mismo resultado en todos los casos.

Durante el proceso de inferencia, cada parámetro Xᵢ de tipo se fija en un tipo Sᵢ determinado o no se fija con un conjunto asociado de límites. Cada uno de los límites es de algún tipo T. Inicialmente, cada variable Xᵢ de tipo está sin fijar con un conjunto vacío de límites.

La inferencia de tipos tiene lugar en fases. Cada fase intentará deducir argumentos de tipo para más variables de tipo en función de los resultados de la fase anterior. La primera fase realiza algunas inferencias iniciales de límites, mientras que la segunda fase corrige variables de tipo a tipos específicos e infiere más límites. Es posible que la segunda fase tenga que repetirse varias veces.

Nota: La inferencia de tipos también se usa en otros contextos, incluida la conversión de grupos de métodos (§12.6.3.14) y la búsqueda del mejor tipo común de un conjunto de expresiones (§12.6.3.15). nota final

12.6.3.2 La primera fase

Para cada uno de los argumentos Eᵢdel método :

12.6.3.3 La segunda fase

La segunda fase continúa de la siguiente manera:

  • Todas las variables de tipo sin fijar que no dependen de (§12.6.3.6) Xₑ son fijas (§12.6.3.12Xᵢ).
  • Si no existen estas variables de tipo, se fijan todas las variables Xᵢ de tipo sin fijar para las que se mantienen todas las siguientes operaciones:
    • Hay al menos una variable Xₑ de tipo que depende deXᵢ
    • Xᵢ tiene un conjunto no vacío de límites
  • Si no existen estas variables de tipo y todavía hay variables de tipo sin fijar , se produce un error en la inferencia de tipos.
  • De lo contrario, si no existen variables de tipo sin fijar, la inferencia de tipos se realiza correctamente.
  • De lo contrario, para todos los argumentos Eᵢ con el tipo Tᵢ de parámetro correspondiente donde los tipos de salida (§12.6.3.5) contienen variables Xₑ de tipo sin fijar, pero los tipos de entrada (§12.6.3.4) no lo hacen, se realizaEᵢTᵢ una inferencia de tipo de salida (§12.6.3.7). A continuación, se repite la segunda fase.

12.6.3.4 Tipos de entrada

Si E es un grupo de métodos o una función anónima con tipo implícito y T es un tipo delegado o tipo de árbol de expresión, todos los tipos de parámetro de son tipos de T entrada deE tipo con tipo .T

12.6.3.5 Tipos de salida

Si E es un grupo de métodos o una función anónima y T es un tipo de delegado o tipo de árbol de expresión, el tipo de valor devuelto de es un tipo de T salida deE con tipo .T

12.6.3.6 Dependencia

Una variable Xᵢ de tipo sin fijar depende directamente de una variable Xₑ de tipo sin fijar si para algún argumento Eᵥ con tipo Tᵥ Xₑ se produce en un tipo de entrada de Eᵥ con tipo Tᵥ y Xᵢ se produce en un tipo de salida de Eᵥ con tipo Tᵥ.

XₑDependeXᵢ si Xₑ depende directamente deXᵢ o si Xᵢ depende directamenteXᵥ de y Xᵥ depende deXₑ . Por lo tanto, "depende" es el cierre transitivo pero no reflexivo de "depende directamente".

12.6.3.7 Inferencias de tipo de salida

Una inferencia de tipo de salida se realiza desde una expresión E a un tipo T de la siguiente manera:

  • Si E es una función anónima con tipo U de valor devuelto inferido (§12.6.3.13) y T es un tipo de delegado o tipo de árbol de expresión con tipo Tₓde valor devuelto , se realiza una inferencia de límite inferior (§12.6.3.10) desdeU a.Tₓ
  • De lo contrario, si E es un grupo de métodos y T es un tipo delegado o tipo de árbol de expresión con tipos T₁...Tᵥ de parámetros y tipo Tₓde valor devuelto , y la resolución de sobrecarga de E con los tipos T₁...Tᵥ produce un único método con tipo Ude valor devuelto y, a continuación, se realiza una inferencia de límite inferior desdeU a .Tₓ
  • De lo contrario, si E es una expresión con el tipo U, se realiza una inferencia de límite inferior desdeU aT .
  • De lo contrario, no se realizan inferencias.

12.6.3.8 Inferencias de tipo de parámetro explícito

Una inferencia de tipo de parámetro explícita se realiza desde una expresión E a un tipo T de la siguiente manera:

  • Si E es una función anónima explícitamente tipada con tipos U₁...Uᵥ de parámetros y T es un tipo delegado o un tipo de árbol de expresión con tipos V₁...Vᵥ de parámetros, para cada Uᵢ una de las inferencias exactas (§12.6.3.9) se realiza de Uᵢa la correspondiente .Vᵢ

12.6.3.9 Inferencias exactas

Se realiza una inferencia exacta de un tipo U a un tipo V de la siguiente manera:

  • Si V es uno de los sinfijosXᵢ , U se agrega al conjunto de límites exactos para Xᵢ.
  • De lo contrario, los conjuntos V₁...Vₑ y U₁...Uₑ se determinan comprobando si se aplica alguno de los casos siguientes:
    • V es un tipo V₁[...] de matriz y U es un tipo U₁[...] de matriz del mismo rango.
    • V es el tipo V₁? y U es el tipo U₁
    • V es un tipo C<V₁...Vₑ> construido y U es un tipo construido C<U₁...Uₑ>
      Si alguno de estos casos se aplica, se realiza una inferencia exacta de cada Uᵢ una a la correspondiente Vᵢ.
  • De lo contrario, no se realizan inferencias.

12.6.3.10 Inferencias enlazadas a inferior

Una inferencia de límite inferior de un tipo U a un tipo V se realiza de la siguiente manera:

  • Si V es uno de los sinfijosXᵢ , U se agrega al conjunto de límites inferiores para Xᵢ.
  • De lo contrario, si V es el tipo V₁? y U es el tipo U₁? , se realiza una inferencia de límite inferior de U₁ a V₁.
  • De lo contrario, los conjuntos U₁...Uₑ y V₁...Vₑ se determinan comprobando si se aplica alguno de los casos siguientes:
    • V es un tipo V₁[...]de matriz y U es un tipo U₁[...]de matriz del mismo rango.
    • Ves uno de los tipos de matriz unidimensional , IReadOnlyList<V₁>>ICollection<V₁>, IReadOnlyCollection<V₁> o IList<V₁> y U es un tipo de IEnumerable<V₁>matriz unidimensional.U₁[]
    • Ves un tipo C<V₁...Vₑ> construido class, structinterface o delegate y hay un tipo C<U₁...Uₑ> único de modo que U (o, si U es un tipo parameter, su clase base efectiva o cualquier miembro de su conjunto de interfaz efectivo) es idéntico a, inherits desde (directa o indirectamente), o implementa (directa o indirectamente). C<U₁...Uₑ>
    • (La restricción de "unicidad" significa que, en la interfaz C<T>{} class U: C<X>, C<Y>{}de mayúsculas y minúsculas, no se realiza ninguna inferencia cuando se deduce de U a C<T> porque U₁ podría ser X o Y).
      Si alguno de estos casos se aplica, se realiza una inferencia de cada Uᵢ a la correspondiente Vᵢ como se indica a continuación:
    • Si Uᵢ no se sabe que es un tipo de referencia, se realiza una inferencia exacta.
    • De lo contrario, si U es un tipo de matriz, se realiza una inferencia de límite inferior.
    • De lo contrario, si V es C<V₁...Vₑ> entonces la inferencia depende del i-th parámetro de tipo de C:
      • Si es covariante, se realiza una inferencia de límite inferior.
      • Si es contravariante, se realiza una inferencia de límite superior.
      • Si es invariable, se realiza una inferencia exacta.
  • De lo contrario, no se realizan inferencias.

12.6.3.11 Inferencias enlazadas superior

Una inferencia de límite superior de un tipo U a un tipo V se realiza de la siguiente manera:

  • Si V es uno de los sinfijosXᵢ , U se agrega al conjunto de límites superiores para Xᵢ.
  • De lo contrario, los conjuntos V₁...Vₑ y U₁...Uₑ se determinan comprobando si se aplica alguno de los casos siguientes:
    • U es un tipo U₁[...]de matriz y V es un tipo V₁[...]de matriz del mismo rango.
    • Ues uno de los tipos de matriz unidimensional , IReadOnlyList<Uₑ>ICollection<Uₑ>, IReadOnlyCollection<Uₑ> o IList<Uₑ> y V es un tipo de IEnumerable<Uₑ>matriz unidimensional.Vₑ[]
    • U es el tipo U1? y V es el tipo V1?
    • U se construye la clase, estructura, interfaz o tipo C<U₁...Uₑ> delegado y V es un class, struct, interface tipo o delegate que es identical , inherits desde (directa o indirectamente) o implementa (directa o indirectamente) un tipo único. C<V₁...Vₑ>
    • (La restricción de "unicidad" significa que, dado una interfaz C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}, no se realiza ninguna inferencia cuando se deduce de C<U₁> a V<Q>. Las inferencias no se realizan desde U₁ a ni X<Q> Y<Q>.
      Si alguno de estos casos se aplica, se realiza una inferencia de cada Uᵢ a la correspondiente Vᵢ como se indica a continuación:
    • Si Uᵢ no se sabe que es un tipo de referencia, se realiza una inferencia exacta.
    • De lo contrario, si V es un tipo de matriz, se realiza una inferencia de límite superior.
    • De lo contrario, si U es C<U₁...Uₑ> entonces la inferencia depende del i-th parámetro de tipo de C:
      • Si es covariante, se realiza una inferencia de límite superior.
      • Si es contravariante, se realiza una inferencia de límite inferior.
      • Si es invariable, se realiza una inferencia exacta.
  • De lo contrario, no se realizan inferencias.

Corrección 12.6.3.12

Una variable Xᵢ de tipo sinfijo con un conjunto de límites se fija de la siguiente manera:

  • El conjunto de tiposUₑ candidatos comienza como el conjunto de todos los tipos del conjunto de límites para Xᵢ.
  • Cada límite de se Xᵢ examina a su vez: para cada U enlazado exacto de Xᵢ todos los tipos Uₑ que no son idénticos a U se quitan del conjunto candidato. Para cada límite U inferior de Xᵢ todos los tipos Uₑ a los que no hay una conversión implícita de U se quitan del conjunto candidato. Para cada U de límite superior de Xᵢ todos los tipos Uₑ desde los que no hay una conversión implícita a U se quitan del conjunto candidato.
  • Si entre los tipos Uₑ candidatos restantes hay un tipo V único al que hay una conversión implícita de todos los demás tipos candidatos, Xᵢ se fija en V.
  • De lo contrario, se produce un error en la inferencia de tipos.

12.6.3.13 Tipo de valor devuelto inferido

El tipo de valor devuelto inferido de una función F anónima se usa durante la inferencia de tipos y la resolución de sobrecarga. El tipo de valor devuelto inferido solo se puede determinar para una función anónima en la que se conocen todos los tipos de parámetro, ya sea porque se proporcionan explícitamente a través de una conversión de función anónima o se deduce durante la inferencia de tipos en una invocación de método genérico envolvente.

El tipo de valor devuelto efectivo inferido se determina de la siguiente manera:

  • Si el cuerpo de F es una expresión que tiene un tipo, el tipo de valor devuelto efectivo inferido de F es el tipo de esa expresión.
  • Si el cuerpo de F es un bloque y el conjunto de expresiones de las instrucciones del return bloque tiene un tipo Tcomún (§12.6.3.15), el tipo de valor devuelto efectivo inferido de F es T.
  • De lo contrario, no se puede deducir un tipo de valor devuelto efectivo para F.

El tipo de valor devuelto inferido se determina de la siguiente manera:

  • Si F es asincrónico y el cuerpo de es una expresión clasificada como nada (§12.2) o un bloque donde ninguna return instrucción tiene expresiones, el tipo de F valor devuelto inferido es «TaskType» (§15.15.1).
  • Si F es asincrónico y tiene un tipo Tde valor devuelto efectivo inferido , el tipo de valor devuelto inferido es «TaskType»<T>»(§15.15.1).
  • Si F no es asincrónico y tiene un tipo Tde valor devuelto efectivo inferido , el tipo de valor devuelto inferido es T.
  • De lo contrario, no se puede deducir un tipo de valor devuelto para F.

Ejemplo: Como ejemplo de inferencia de tipo que implica funciones anónimas, considere el método de Select extensión declarado en la System.Linq.Enumerable clase :

namespace System.Linq
{
    public static class Enumerable
    {
        public static IEnumerable<TResult> Select<TSource,TResult>(
            this IEnumerable<TSource> source,
            Func<TSource,TResult> selector)
        {
            foreach (TSource element in source)
            {
                yield return selector(element);
            }
        }
   }
}

Suponiendo que el System.Linq espacio de nombres se importó con una using namespace directiva y, dado una clase Customer con una Name propiedad de tipo string, el Select método se puede usar para seleccionar los nombres de una lista de clientes:

List<Customer> customers = GetCustomerList();
IEnumerable<string> names = customers.Select(c => c.Name);

La invocación del método de extensión (§12.8.10.3) de se procesa mediante la reescritura de Select la invocación a una invocación de método estático:

IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);

Dado que los argumentos de tipo no se especificaron explícitamente, la inferencia de tipos se usa para inferir los argumentos de tipo. En primer lugar, el argumento customers está relacionado con el parámetro de origen, lo que deduce TSource que es Customer. A continuación, mediante el proceso de inferencia de tipos de función anónimo descrito anteriormente, c se da el tipo Customery la expresión c.Name está relacionada con el tipo de valor devuelto del parámetro selector, inferencia TResult para que sea string. Por lo tanto, la invocación es equivalente a

Sequence.Select<Customer,string>(customers, (Customer c) => c.Name)

y el resultado es de tipo IEnumerable<string>.

En el ejemplo siguiente se muestra cómo la inferencia de tipos de función anónima permite que la información de tipo "fluya" entre argumentos en una invocación de método genérico. Dado el método y la invocación siguientes:

class A
{
    static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2)
    {
        return f2(f1(value));
    }

    static void M()
    {
        double hours = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalHours);
    }
}

La inferencia de tipos para la invocación continúa de la siguiente manera: En primer lugar, el argumento "1:15:30" está relacionado con el parámetro value, inferencia X para que sea cadena. A continuación, el parámetro de la primera función anónima, s, recibe el tipo stringinferido y la expresión TimeSpan.Parse(s) está relacionada con el tipo de valor devuelto de f1, inferir Y que es System.TimeSpan. Por último, el parámetro de la segunda función anónima, t, recibe el tipo System.TimeSpaninferido y la expresión t.TotalHours está relacionada con el tipo de valor devuelto de f2, inferir Z que debe ser double. Por lo tanto, el resultado de la invocación es de tipo double.

ejemplo final

12.6.3.14 Inferencia de tipos para la conversión de grupos de métodos

De forma similar a las llamadas de métodos genéricos, la inferencia de tipos también se aplicará cuando un grupo M de métodos que contenga un método genérico se convierta en un tipo D delegado determinado (§10.8). Dado un método

Tₑ M<X₁...Xᵥ>(T₁ x₁ ... Tₑ xₑ)

y el grupo M de métodos que se asigna al tipo delegado la tarea de inferencia de tipo D es buscar argumentos S₁...Sᵥ de tipo para que la expresión:

M<S₁...Sᵥ>

se vuelve compatible (§20.2) con D.

A diferencia del algoritmo de inferencia de tipos para las llamadas a métodos genéricos, en este caso, solo hay tipos de argumento, sin expresiones de argumento. En concreto, no hay funciones anónimas y, por tanto, no es necesario realizar varias fases de inferencia.

En su lugar, todos se consideran sin fijar y se realiza una inferencia de límite inferior de a cada tipo Uₑ de argumento del tipo de D parámetro Tₑ correspondiente de M.Xᵢ Si para cualquiera de los límites no se encontraron, se produce un error en la Xᵢ inferencia de tipos. De lo contrario, todos Xᵢ se fijan en correspondientes Sᵢ, que son el resultado de la inferencia de tipos.

12.6.3.15 Búsqueda del mejor tipo común de un conjunto de expresiones

En algunos casos, es necesario inferir un tipo común para un conjunto de expresiones. En concreto, los tipos de elementos de matrices con tipo implícito y los tipos devueltos de funciones anónimas con cuerpos de bloque se encuentran de esta manera.

El mejor tipo común para un conjunto de expresiones E₁...Eᵥ se determina de la siguiente manera:

  • Se introduce una nueva variable X de tipo sin fijar.
  • Para cada expresión Ei , se realiza una inferencia de tipo de salida (§12.6.3.7) desde ella a X.
  • Xes fijo (§12.6.3.12), si es posible, y el tipo resultante es el mejor tipo común.
  • De lo contrario, se produce un error en la inferencia.

Nota: De forma intuitiva, esta inferencia equivale a llamar a un método void M<X>(X x₁ ... X xᵥ) con como Eᵢ argumentos e inferencia .X nota final

12.6.4 Resolución de sobrecarga

12.6.4.1 General

La resolución de sobrecargas es un mecanismo de tiempo de enlace para seleccionar el mejor miembro de función para invocar dado una lista de argumentos y un conjunto de miembros de función candidatos. La resolución de sobrecarga selecciona el miembro de función que se va a invocar en los siguientes contextos distintos en C#:

  • Invocación de un método denominado en un invocation_expression (§12.8.10).
  • Invocación de un constructor de instancia denominado en un object_creation_expression (§12.8.17.2).
  • Invocación de un descriptor de acceso de indexador a través de un element_access (§12.8.12).
  • Invocación de un operador predefinido o definido por el usuario al que se hace referencia en una expresión (§12.4.4 y §12.4.5).

Cada uno de estos contextos define el conjunto de miembros de función candidata y la lista de argumentos de su propia manera única. Por ejemplo, el conjunto de candidatos para una invocación de método no incluye métodos marcados como invalidación (§12.5) y los métodos de una clase base no son candidatos si se aplica algún método de una clase derivada (§12.8.10.2).

Una vez identificados los miembros de la función candidata y la lista de argumentos, la selección del mejor miembro de función es la misma en todos los casos:

  • En primer lugar, el conjunto de miembros de función candidatos se reduce a los miembros de función aplicables con respecto a la lista de argumentos especificada (§12.6.4.2). Si este conjunto reducido está vacío, se produce un error en tiempo de compilación.
  • A continuación, se encuentra el mejor miembro de función del conjunto de miembros de función candidatos aplicables. Si el conjunto contiene solo un miembro de función, ese miembro de función es el mejor miembro de función. De lo contrario, el mejor miembro de función es el miembro de función que es mejor que todos los demás miembros de función con respecto a la lista de argumentos dada, siempre que cada miembro de función se compare con todos los demás miembros de función mediante las reglas de §12.6.4.3. Si no hay exactamente un miembro de función que sea mejor que todos los demás miembros de función, la invocación del miembro de función es ambigua y se produce un error en tiempo de enlace.

Las subclases siguientes definen los significados exactos de los términos miembro de función aplicable y un miembro de función mejor.

12.6.4.2 Miembro de función aplicable

Se dice que un miembro de función es un miembro de función aplicable con respecto a una lista A de argumentos cuando se cumplen todas las siguientes condiciones:

  • Cada argumento de A corresponde a un parámetro de la declaración de miembro de función tal como se describe en §12.6.2.2, como máximo, un argumento corresponde a cada parámetro y cualquier parámetro al que no se corresponda ningún argumento es un parámetro opcional.
  • Para cada argumento de A, el modo de paso de parámetros del argumento es idéntico al modo de paso de parámetros del parámetro correspondiente y
    • para un parámetro de valor o una matriz de parámetros, existe una conversión implícita (§10.2) desde la expresión de argumento al tipo del parámetro correspondiente o
    • para un parámetro de referencia o salida, hay una conversión de identidad entre el tipo de la expresión de argumento (si existe) y el tipo del parámetro correspondiente, o bien
    • para un parámetro de entrada cuando el argumento correspondiente tiene el in modificador , hay una conversión de identidad entre el tipo de la expresión de argumento (si existe) y el tipo del parámetro correspondiente, o bien
    • para un parámetro de entrada cuando el argumento correspondiente omite el in modificador, existe una conversión implícita (§10.2) desde la expresión de argumento al tipo del parámetro correspondiente.

Para un miembro de función que incluya una matriz de parámetros, si el miembro de función es aplicable por las reglas anteriores, se dice que es aplicable en su forma normal. Si un miembro de función que incluye una matriz de parámetros no es aplicable en su forma normal, el miembro de función podría ser aplicable en su forma expandida:

  • El formulario expandido se construye reemplazando la matriz de parámetros de la declaración de miembro de función por cero o más parámetros de valor del tipo de elemento de la matriz de parámetros, de modo que el número de argumentos de la lista A de argumentos coincide con el número total de parámetros. Si A tiene menos argumentos que el número de parámetros fijos en la declaración del miembro de función, la forma expandida del miembro de función no se puede construir y, por tanto, no es aplicable.
  • De lo contrario, el formulario expandido es aplicable si para cada argumento de A, se cumple una de las siguientes condiciones:
    • el modo de paso de parámetros del argumento es idéntico al modo de paso de parámetros del parámetro correspondiente y
      • para un parámetro de valor fijo o un parámetro de valor creado por la expansión, existe una conversión implícita (§10.2) desde la expresión de argumento hasta el tipo del parámetro correspondiente, o bien
      • para un parámetro por referencia, el tipo de la expresión de argumento es idéntico al tipo del parámetro correspondiente.
    • el modo de paso de parámetros del argumento es value y el modo de paso de parámetros del parámetro correspondiente es input y existe una conversión implícita (§10.2) desde la expresión de argumento al tipo del parámetro correspondiente.

Cuando la conversión implícita del tipo de argumento al tipo de parámetro de un parámetro de entrada es una conversión implícita dinámica (§10.2.10), los resultados no están definidos.

Ejemplo: dadas las siguientes declaraciones y llamadas a métodos:

public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }
public static void M2(in int p1) { ... }
public static void Test()
{
    int i = 10; uint ui = 34U;

    M1(in i);   // M1(in int) is applicable
    M1(in ui);  // no exact type match, so M1(in int) is not applicable
    M1(i);      // M1(int) and M1(in int) are applicable
    M1(i + 5);  // M1(int) and M1(in int) are applicable
    M1(100u);   // no implicit conversion exists, so M1(int) is not applicable

    M2(in i);   // M2(in int) is applicable
    M2(i);      // M2(in int) is applicable
    M2(i + 5);  // M2(in int) is applicable
}

ejemplo final

  • Un método estático solo es aplicable si el grupo de métodos da como resultado un simple_name o un member_access a través de un tipo.
  • Un método de instancia solo es aplicable si el grupo de métodos da como resultado un simple_name, un member_access a través de una variable o un valor, o un base_access.
    • Si el grupo de métodos resulta de un simple_name, un método de instancia solo es aplicable si this se permite el acceso §12.8.14.
  • Cuando el grupo de métodos resulta de un member_access que podría ser a través de una instancia o de un tipo, tal como se describe en §12.8.7.2, se aplican los métodos estáticos y de instancia.
  • Un método genérico cuyos argumentos de tipo (especificados explícitamente o inferidos) no satisfacen todas sus restricciones no son aplicables.
  • En el contexto de una conversión de grupo de métodos, existirá una conversión de identidad (§10.2.2) o una conversión de referencia implícita (§10.2.8) desde el tipo de valor devuelto del método al tipo de valor devuelto del delegado. De lo contrario, el método candidato no es aplicable.

12.6.4.3 Miembro de función Better

Para determinar el miembro de función mejor, se construye una lista A de argumentos eliminados que contiene solo las expresiones de argumento en el orden en que aparecen en la lista de argumentos original y se excluyen los out argumentos o ref .

Las listas de parámetros para cada uno de los miembros de función candidatos se construyen de la siguiente manera:

  • El formulario expandido se usa si el miembro de función solo se aplica en el formulario expandido.
  • Los parámetros opcionales sin argumentos correspondientes se quitan de la lista de parámetros
  • Los parámetros de referencia y salida se quitan de la lista de parámetros
  • Los parámetros se reordenan para que se produzcan en la misma posición que el argumento correspondiente en la lista de argumentos.

Dada una lista A de argumentos con un conjunto de expresiones de argumento y dos miembros Mᵥ de función aplicables {E₁, E₂, ..., Eᵥ} y Mₓ con tipos {P₁, P₂, ..., Pᵥ} de parámetro y {Q₁, Q₂, ..., Qᵥ}, Mᵥ se define para ser un miembro de función mejor que Mₓ si

  • para cada argumento, la conversión implícita de a Qᵥ Eᵥ no es mejor que la conversión implícita de Eᵥ a Pᵥy
  • para al menos un argumento, la conversión de Eᵥ a Pᵥ es mejor que la conversión de Eᵥ a Qᵥ.

En caso de que las secuencias de tipo de parámetro y sean equivalentes {P₁, P₂, ..., Pᵥ} (es decir, cada una Pᵢ tiene una conversión de identidad a la correspondiente Qᵢ), se aplican las siguientes reglas de desempate, para determinar el miembro de función mejor.{Q₁, Q₂, ..., Qᵥ}

  • Si Mᵢ es un método no genérico y Mₑ es un método genérico, Mᵢ es mejor que Mₑ.
  • De lo contrario, si Mᵢ es aplicable en su forma normal y Mₑ tiene una matriz params y solo es aplicable en su forma expandida, Mᵢ entonces es mejor que Mₑ.
  • De lo contrario, si ambos métodos tienen matrices de parámetros y solo son aplicables en sus formularios expandidos, y si la matriz de parámetros de Mᵢ tiene menos elementos que la matriz de parámetros de Mₑ, entonces Mᵢ es mejor que Mₑ.
  • De lo contrario, si Mᵥ tiene tipos de parámetro más específicos que Mₓ, Mᵥ es mejor que Mₓ. Deje y {S1, S2, ..., Sn} represente {R1, R2, ..., Rn} los tipos de parámetros no fundamentados y noexpandados de Mᵥ y Mₓ. MᵥLos tipos de parámetro de son más específicos que Mₓs si, para cada parámetro, Rx no es menos específico que Sxy, para al menos un parámetro, Rx es más específico que Sx:
    • Un parámetro de tipo es menos específico que un parámetro que no es de tipo.
    • Recursivamente, un tipo construido es más específico que otro tipo construido (con el mismo número de argumentos de tipo) si al menos un argumento de tipo es más específico y ningún argumento de tipo es menos específico que el argumento de tipo correspondiente en el otro.
    • Un tipo de matriz es más específico que otro tipo de matriz (con el mismo número de dimensiones) si el tipo de elemento del primero es más específico que el tipo de elemento del segundo.
  • De lo contrario, si un miembro es un operador no elevado y el otro es un operador elevado, el no elevado es mejor.
  • Si no se encontró que ninguno de los miembros de función es mejor y todos los parámetros de Mᵥ tienen un argumento correspondiente, mientras que los argumentos predeterminados deben sustituirse por al menos un parámetro opcional en Mₓ, entonces Mᵥ es mejor que Mₓ.
  • Si para al menos un parámetro Mᵥ usa la mejor opción de paso de parámetros (§12.6.4.4) que el parámetro correspondiente en Mₓ y ninguno de los parámetros en Mₓ que se usa la mejor opción de paso de parámetros que Mᵥ, Mᵥ es mejor que Mₓ.
  • De lo contrario, no es mejor ningún miembro de función.

12.6.4.4 Mejor modo de paso de parámetros

Se permite que los parámetros correspondientes en dos métodos sobrecargados solo difieren en el modo de paso de parámetros siempre que uno de los dos parámetros tenga el modo de paso de valores, como se indica a continuación:

public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }

Dado int i = 10;, según §12.6.4.2, las llamadas M1(i) y M1(i + 5) dan lugar a que ambas sobrecargas sean aplicables. En tales casos, el método con el modo de paso de parámetros de valor es la mejor opción del modo de paso de parámetros.

Nota: No es necesario que exista dicha opción para los argumentos de los modos de paso de entrada, salida o referencia, ya que esos argumentos solo coinciden con los mismos modos de paso de parámetros exactos. nota final

12.6.4.5 Mejor conversión de la expresión

Dada una conversión C₁ implícita que convierte de una expresión E a un tipo T₁y una conversión implícita que convierte de una expresión E a un tipo T₂, C₁ es una conversiónC₂ mejor que C₂ si una de las siguientes contiene:

  • ET₁ coincide exactamente y E no coincide T₂ exactamente (§12.6.4.6)
  • Ecoincide exactamente con o ninguno de T₁ y , y T₁ es un destino de conversión mejor que T₂ (§12.6.4.7T₂)
  • E es un grupo de métodos (§12.2), T₁ es compatible (§20.4) con el mejor método del grupo de métodos para la conversión C₁y T₂ no es compatible con el único método del grupo de métodos para la conversión. C₂

12.6.4.6 Expresión coincidente exactamente

Dada una expresión E y un tipo T, E coincide T exactamente con si una de las siguientes suspensiones:

  • E tiene un tipo Sy existe una conversión de identidad de S a . T
  • E es una función anónima, T es un tipo delegado o un tipo D Expression<D> de árbol de expresión y una de las siguientes suspensiones:
    • Existe un tipo X de valor devuelto inferido para E en el contexto de la lista de parámetros de D (§12.6.3.12) y existe una conversión de identidad desde X hasta el tipo de valor devuelto de D
    • E es una async expresión lambda sin valor devuelto y D tiene un tipo de valor devuelto que es un no genérico. «TaskType»
    • No E es asincrónico y D tiene un tipo Y de valor devuelto o E es asincrónico y D tiene un tipo «TaskType»<Y>de valor devuelto (§15.15.1) y una de las siguientes suspensiones:
      • El cuerpo de E es una expresión que coincide exactamente con Y
      • El cuerpo de E es un bloque donde cada instrucción de devolución devuelve una expresión que coincide exactamente con Y

12.6.4.7 Mejor destino de conversión

Dados dos tipos y , es un destino de conversión mejor que T₂ si uno de los siguientes contiene: T₁ T₂T₁

  • Existe una conversión implícita de T₁ a T₂ y no existe ninguna conversión implícita de T₂ a T₁ .
  • T₁ es «TaskType»<S₁>(§15.15.1), T₂ es «TaskType»<S₂>, y S₁ es un destino de conversión mejor que S₂
  • T₁is «TaskType»<S₁>(§15.15.1), T₂ es , y T₁ es «TaskType»<S₂>más especializado queT₂
  • T₁es o S₁? donde S₁ es S₁ un tipo entero con signo y T₂ es S₂ o S₂? donde S₂ es un tipo entero sin signo. En concreto:
    • S₁es y S₂ es bytesbyte , ushort, uintoulong
    • S₁es y S₂ es ushortshort , uintoulong
    • S₁es y S₂ es uintint , oulong
    • S₁ es long y S₂ es ulong

12.6.4.8 Sobrecargado en clases genéricas

Nota: Aunque las firmas declaradas deben ser únicas (§8.6), es posible que la sustitución de argumentos de tipo produzca firmas idénticas. En tal situación, la resolución de sobrecarga elegirá la más específica (§12.6.4.3) de las firmas originales (antes de la sustitución de argumentos de tipo), si existe y, de lo contrario, notificará un error. nota final

Ejemplo: en los ejemplos siguientes se muestran sobrecargas que son válidas y no válidas según esta regla:

public interface I1<T> { ... }
public interface I2<T> { ... }

public abstract class G1<U>
{
    public abstract int F1(U u);           // Overload resolution for G<int>.F1
    public abstract int F1(int i);         // will pick non-generic

    public abstract void F2(I1<U> a);      // Valid overload
    public abstract void F2(I2<U> a);
}

abstract class G2<U,V>
{
    public abstract void F3(U u, V v);     // Valid, but overload resolution for
    public abstract void F3(V v, U u);     // G2<int,int>.F3 will fail

    public abstract void F4(U u, I1<V> v); // Valid, but overload resolution for
    public abstract void F4(I1<V> v, U u); // G2<I1<int>,int>.F4 will fail

    public abstract void F5(U u1, I1<V> v2);   // Valid overload
    public abstract void F5(V v1, U u2);

    public abstract void F6(ref U u);      // Valid overload
    public abstract void F6(out V v);
}

ejemplo final

12.6.5 Comprobación en tiempo de compilación de la invocación de miembros dinámicos

Aunque la resolución de sobrecarga de una operación enlazada dinámicamente tiene lugar en tiempo de ejecución, a veces es posible en tiempo de compilación conocer la lista de miembros de función desde los que se elegirá una sobrecarga:

  • Para una invocación de delegado (§12.8.10.4), la lista es un miembro de función único con la misma lista de parámetros que el delegate_type de la invocación.
  • Para una invocación de método (§12.8.10.2) en un tipo o en un valor cuyo tipo estático no es dinámico, el conjunto de métodos accesibles en el grupo de métodos se conoce en tiempo de compilación.
  • Para una expresión de creación de objetos (§12.8.17.2), el conjunto de constructores accesibles en el tipo se conoce en tiempo de compilación.
  • Para un acceso de indexador (§12.8.12.3), el conjunto de indexadores accesibles en el receptor se conoce en tiempo de compilación.

En estos casos, se realiza una comprobación limitada en tiempo de compilación en cada miembro del conjunto conocido de miembros de función, para ver si se puede saber que no se puede invocar nunca en tiempo de ejecución. Para cada miembro F de función se construye un parámetro modificado y una lista de argumentos:

  • En primer lugar, si F se proporciona un método genérico y argumentos de tipo, se sustituyen por los parámetros de tipo de la lista de parámetros. Sin embargo, si no se proporcionaron argumentos de tipo, no se produce dicha sustitución.
  • A continuación, cualquier parámetro cuyo tipo esté abierto (es decir, contiene un parámetro de tipo; vea §8.4.3) se elided, junto con sus parámetros correspondientes.

Para F pasar la comprobación, todas las siguientes tendrán:

  • La lista de parámetros modificado para F es aplicable a la lista de argumentos modificados en términos de §12.6.4.2.
  • Todos los tipos construidos de la lista de parámetros modificados cumplen sus restricciones (§8.4.5).
  • Si los parámetros de tipo de F se sustituyeron en el paso anterior, se cumplen sus restricciones.
  • Si F es un método estático, el grupo de métodos no habrá resultado de un member_access cuyo receptor se conoce en tiempo de compilación para ser una variable o valor.
  • Si F es un método de instancia, el grupo de métodos no habrá resultado de un member_access cuyo receptor se conoce en tiempo de compilación para ser un tipo.

Si ningún candidato supera esta prueba, se produce un error en tiempo de compilación.

12.6.6 Invocación de miembro de función

12.6.6.1 General

Esta subclausa describe el proceso que tiene lugar en tiempo de ejecución para invocar un miembro de función determinado. Se supone que un proceso de tiempo de enlace ya ha determinado el miembro determinado que se va a invocar, posiblemente aplicando la resolución de sobrecarga a un conjunto de miembros de función candidatos.

Para describir el proceso de invocación, los miembros de función se dividen en dos categorías:

  • Miembros de función estáticos. Estos son métodos estáticos, descriptores de acceso de propiedad estáticos y operadores definidos por el usuario. Los miembros de función estáticos siempre no son virtuales.
  • Miembros de la función de instancia. Estos son métodos de instancia, constructores de instancia, descriptores de acceso de propiedad de instancia y descriptores de acceso del indexador. Los miembros de la función de instancia son no virtuales o virtuales y siempre se invocan en una instancia determinada. La instancia se calcula mediante una expresión de instancia y se vuelve accesible dentro del miembro de función como this (§12.8.14). Para un constructor de instancia, se toma la expresión de instancia para que sea el objeto recién asignado.

El procesamiento en tiempo de ejecución de una invocación de miembro de función consta de los pasos siguientes, donde M es el miembro de función y, si M es un miembro de instancia, E es la expresión de instancia:

  • Si M es un miembro de función estática:

    • La lista de argumentos se evalúa como se describe en §12.6.2.
    • Se invoca a M.
  • De lo contrario, si el tipo de es un tipo de E valor y M se declara o invalida en V:V

    • E se evalúa. Si esta evaluación produce una excepción, no se ejecutan más pasos. Para un constructor de instancia, esta evaluación consta de asignar almacenamiento (normalmente desde una pila de ejecución) para el nuevo objeto. En este caso E se clasifica como una variable.
    • Si E no se clasifica como una variable, o si V no es un tipo de estructura de solo lectura (§16.2.2), y E es uno de los siguientes:
      • un parámetro de entrada (§15.6.2.3.2) o
      • un readonly campo (§15.5.3) o
      • una readonly variable de referencia o un valor devuelto (§9.7),

    a continuación, se crea una variable local temporal del Etipo y el valor de E se asigna a esa variable. E A continuación, se vuelve a clasificar como referencia a esa variable local temporal. La variable temporal es accesible como this dentro Mde , pero no de ninguna otra manera. Por lo tanto, solo cuando E se puede escribir es posible que el autor de la llamada observe los cambios que M realiza en this.

    • La lista de argumentos se evalúa como se describe en §12.6.2.
    • Se invoca a M. La variable a la que hace E referencia se convierte en la variable a la que hace thisreferencia .
  • De lo contrario:

    • E se evalúa. Si esta evaluación produce una excepción, no se ejecutan más pasos.
    • La lista de argumentos se evalúa como se describe en §12.6.2.
    • Si el tipo de E es un value_type, se realiza una conversión boxing (§10.2.9) para convertir E a un class_type y E se considera que es de ese class_type en los pasos siguientes. Si el value_type es un enum_type, el class_type es System.Enum; de lo contrario, es System.ValueType.
    • Se comprueba que el valor de E es válido. Si el valor de E es NULL, se produce una System.NullReferenceException excepción y no se ejecutan más pasos.
    • La implementación del miembro de función que se va a invocar se determina:
      • Si el tipo de tiempo de enlace de E es una interfaz, el miembro de función que se va a invocar es la implementación de proporcionada por el tipo en tiempo de M ejecución de la instancia a la que hace Ereferencia . Este miembro de función se determina aplicando las reglas de asignación de interfaz (§18.6.5) para determinar la implementación de M proporcionada por el tipo en tiempo de ejecución de la instancia a la que hace Ereferencia .
      • De lo contrario, si M es un miembro de función virtual, el miembro de función que se va a invocar es la implementación de proporcionada por el tipo en tiempo de M ejecución de la instancia a la que hace Ereferencia . Este miembro de función se determina aplicando las reglas para determinar la implementación más derivada (§15.6.4) de M con respecto al tipo en tiempo de ejecución de la instancia a la que hace Ereferencia .
      • De lo contrario, M es un miembro de función no virtual y el miembro de función que se va a invocar es M el mismo.
    • Se invoca la implementación del miembro de función determinada en el paso anterior. El objeto al que hace E referencia se convierte en el objeto al que hace referencia.

El resultado de la invocación de un constructor de instancia (§12.8.17.2) es el valor creado. El resultado de la invocación de cualquier otro miembro de función es el valor, si existe, devuelto (§13.10.5) desde su cuerpo.

12.6.6.2 Invocaciones en instancias boxed

Un miembro de función implementado en un value_type se puede invocar a través de una instancia boxeada de ese value_type en las situaciones siguientes:

  • Cuando el miembro de función es una invalidación de un método heredado del tipo class_type y se invoca a través de una expresión de instancia de ese class_type.

    Nota: El class_type siempre será uno de System.Object, System.ValueType o System.Enumnota final

  • Cuando el miembro de función es una implementación de un miembro de función de interfaz y se invoca a través de una expresión de instancia de un interface_type.
  • Cuando el miembro de función se invoca a través de un delegado.

En estas situaciones, la instancia boxed se considera que contiene una variable de la value_type y esta variable se convierte en la variable a la que hace referencia dentro de la invocación del miembro de función.

Nota: En concreto, esto significa que cuando se invoca un miembro de función en una instancia boxed, es posible que el miembro de función modifique el valor contenido en la instancia boxed. nota final

12.7 Deconstrucción

Deconstrucción es un proceso por el que una expresión se convierte en una tupla de expresiones individuales. La deconstrucción se usa cuando el destino de una asignación simple es una expresión de tupla, para obtener valores que se asignan a cada uno de los elementos de esa tupla.

Una expresión se desconstruye en una expresión E de tupla con n elementos de la siguiente manera:

  • Si E es una expresión de tupla con n elementos , el resultado de la deconstrucción es la propia expresión E .
  • De lo contrario, si E tiene un tipo (T1, ..., Tn) de tupla con n elementos, E se evalúa en una variable __vtemporal y el resultado de la deconstrucción es la expresión (__v.Item1, ..., __v.Itemn).
  • De lo contrario, si la expresión E.Deconstruct(out var __v1, ..., out var __vn) se resuelve en tiempo de compilación en una instancia única o método de extensión, esa expresión se evalúa y el resultado de la deconstrucción es la expresión (__v1, ..., __vn). Este método se conoce como deconstructor.
  • De lo contrario, E no se puede desconstruir.

Aquí, __v y __v1, ..., __vn hacen referencia a variables temporales invisibles y inaccesibles.

Nota: No se puede construir una expresión de tipo dynamic . nota final

12.8 Expresiones principales

12.8.1 General

Las expresiones principales incluyen las formas más sencillas de expresiones.

primary_expression
    : primary_no_array_creation_expression
    | array_creation_expression
    ;

primary_no_array_creation_expression
    : literal
    | interpolated_string_expression
    | simple_name
    | parenthesized_expression
    | tuple_expression
    | member_access
    | null_conditional_member_access
    | invocation_expression
    | element_access
    | null_conditional_element_access
    | this_access
    | base_access
    | post_increment_expression
    | post_decrement_expression
    | null_forgiving_expression
    | object_creation_expression
    | delegate_creation_expression
    | anonymous_object_creation_expression
    | typeof_expression
    | sizeof_expression
    | checked_expression
    | unchecked_expression
    | default_value_expression
    | nameof_expression    
    | anonymous_method_expression
    | pointer_member_access     // unsafe code support
    | pointer_element_access    // unsafe code support
    | stackalloc_expression
    ;

Nota: Estas reglas gramaticales no están listas para ANTLR, ya que forman parte de un conjunto de reglas recursivas mutuamente a la izquierda (primary_expression, primary_no_array_creation_expression, member_access, element_accessinvocation_expression, post_increment_expression, post_decrement_expression, , null_forgiving_expressionpointer_member_access y pointer_element_access) que ANTLR no controla. Las técnicas estándar se pueden usar para transformar la gramática para quitar la recursividad mutua a la izquierda. Esto no se ha hecho, ya que no todas las estrategias de análisis lo requieren (por ejemplo, un analizador LALR no) y hacerlo ofuscaría la estructura y la descripción. nota final

pointer_member_access (§23.6.3) y pointer_element_access (§23.6.4) solo están disponibles en código no seguro (§23).

Las expresiones principales se dividen entre array_creation_expressions y primary_no_array_creation_expressions. Tratar array_creation_expression de esta manera, en lugar de enumerarlo junto con las otras formas de expresión simple, permite que la gramática no permita código potencialmente confuso, como

object o = new int[3][1];

que, de lo contrario, se interpretaría como

object o = (new int[3])[1];

12.8.2 Literales

Un primary_expression que consta de un literal (§6.4.5) se clasifica como un valor.

12.8.3 Expresiones de cadena interpoladas

Un interpolated_string_expression consta de , $@o @$, seguido inmediatamente de texto dentro " de $caracteres. Dentro del texto entrecomillado hay cero o más interpolaciones delimitadas por { caracteres y}, cada una de las cuales incluye una expresión y especificaciones de formato opcionales.

Las expresiones de cadena interpoladas tienen dos formas; regular (interpolated_regular_string_expression) y textual (interpolated_verbatim_string_expression); que son léxicamente similares a, pero se diferencian semánticamente de las dos formas de literales de cadena (§6.4.5.6).

interpolated_string_expression
    : interpolated_regular_string_expression
    | interpolated_verbatim_string_expression
    ;

// interpolated regular string expressions

interpolated_regular_string_expression
    : Interpolated_Regular_String_Start Interpolated_Regular_String_Mid?
      ('{' regular_interpolation '}' Interpolated_Regular_String_Mid?)*
      Interpolated_Regular_String_End
    ;

regular_interpolation
    : expression (',' interpolation_minimum_width)?
      Regular_Interpolation_Format?
    ;

interpolation_minimum_width
    : constant_expression
    ;

Interpolated_Regular_String_Start
    : '$"'
    ;

// the following three lexical rules are context sensitive, see details below

Interpolated_Regular_String_Mid
    : Interpolated_Regular_String_Element+
    ;

Regular_Interpolation_Format
    : ':' Interpolated_Regular_String_Element+
    ;

Interpolated_Regular_String_End
    : '"'
    ;

fragment Interpolated_Regular_String_Element
    : Interpolated_Regular_String_Character
    | Simple_Escape_Sequence
    | Hexadecimal_Escape_Sequence
    | Unicode_Escape_Sequence
    | Open_Brace_Escape_Sequence
    | Close_Brace_Escape_Sequence
    ;

fragment Interpolated_Regular_String_Character
    // Any character except " (U+0022), \\ (U+005C),
    // { (U+007B), } (U+007D), and New_Line_Character.
    : ~["\\{}\u000D\u000A\u0085\u2028\u2029]
    ;

// interpolated verbatim string expressions

interpolated_verbatim_string_expression
    : Interpolated_Verbatim_String_Start Interpolated_Verbatim_String_Mid?
      ('{' verbatim_interpolation '}' Interpolated_Verbatim_String_Mid?)*
      Interpolated_Verbatim_String_End
    ;

verbatim_interpolation
    : expression (',' interpolation_minimum_width)?
      Verbatim_Interpolation_Format?
    ;

Interpolated_Verbatim_String_Start
    : '$@"'
    | '@$"'
    ;

// the following three lexical rules are context sensitive, see details below

Interpolated_Verbatim_String_Mid
    : Interpolated_Verbatim_String_Element+
    ;

Verbatim_Interpolation_Format
    : ':' Interpolated_Verbatim_String_Element+
    ;

Interpolated_Verbatim_String_End
    : '"'
    ;

fragment Interpolated_Verbatim_String_Element
    : Interpolated_Verbatim_String_Character
    | Quote_Escape_Sequence
    | Open_Brace_Escape_Sequence
    | Close_Brace_Escape_Sequence
    ;

fragment Interpolated_Verbatim_String_Character
    : ~["{}]    // Any character except " (U+0022), { (U+007B) and } (U+007D)
    ;

// lexical fragments used by both regular and verbatim interpolated strings

fragment Open_Brace_Escape_Sequence
    : '{{'
    ;

fragment Close_Brace_Escape_Sequence
    : '}}'
    ;

Seis de las reglas léxicas definidas anteriormente distinguen el contexto de la siguiente manera:

Regla Requisitos contextuales
Interpolated_Regular_String_Mid Solo se reconoce después de un Interpolated_Regular_String_Start, entre las interpolaciones siguientes y antes del Interpolated_Regular_String_End correspondiente.
Regular_Interpolation_Format Solo se reconoce dentro de un regular_interpolation y cuando los dos puntos iniciales (:) no están anidados dentro de ningún tipo de corchete (paréntesis,llaves/cuadrado).
Interpolated_Regular_String_End Solo se reconoce después de un Interpolated_Regular_String_Start y solo si los tokens intermedios son Interpolated_Regular_String_Mids o tokens que pueden formar parte de regular_interpolations, incluidos los tokens de cualquier interpolated_regular_string_expressioncontenido en dichas interpolaciones.
Interpolated_Verbatim_String_Mid Verbatim_Interpolation_Format Interpolated_Verbatim_String_End El reconocimiento de estas tres reglas sigue al de las reglas correspondientes anteriores con cada regla de gramática regular mencionada reemplazada por la correspondiente textual.

Nota: Las reglas anteriores distinguen el contexto, ya que sus definiciones se superponen con las de otros tokens en el idioma. nota final

Nota: La gramática anterior no está lista para ANTLR debido a las reglas léxicas contextuales. Al igual que con otros generadores de lexer, ANTLR admite reglas léxicas contextuales, por ejemplo usando sus modos léxicos, pero se trata de un detalle de implementación y, por tanto, no forma parte de esta especificación. nota final

Un interpolated_string_expression se clasifica como un valor. Si se convierte inmediatamente en System.IFormattable o System.FormattableString con una conversión implícita de cadena interpolada (§10.2.5), la expresión de cadena interpolada tiene ese tipo. De lo contrario, tiene el tipo string.

Nota: Las diferencias entre los posibles tipos que puede determinar un interpolated_string_expression se pueden determinar de la documentación de System.String (§C.2) y System.FormattableString (§C.3). nota final

El significado de una interpolación, tanto regular_interpolation como verbatim_interpolation, es dar formato al valor de la expresión como un string objeto según el formato especificado por el Regular_Interpolation_Format o Verbatim_Interpolation_Format, o según un formato predeterminado para el tipo de expresión. A continuación, el interpolation_minimum_width modifica la cadena con formato para generar la final string que se va a interpolar en el interpolated_string_expression.

Nota: Cómo se determina el formato predeterminado de un tipo en la documentación de System.String (§C.2) y System.FormattableString (§C.3). Las descripciones de formatos estándar, que son idénticas para Regular_Interpolation_Format y Verbatim_Interpolation_Format, se pueden encontrar en la documentación de System.IFormattable (§C.4) y en otros tipos de la biblioteca estándar (§C). nota final

En un interpolation_minimum_width el constant_expression tendrá una conversión implícita a int. Deje que el ancho del campo sea el valor absoluto de este constant_expression y la alineación sea el signo (positivo o negativo) del valor de este constant_expression:

  • Si el valor del ancho del campo es menor o igual que la longitud de la cadena con formato, la cadena con formato no se modifica.
  • De lo contrario, la cadena con formato se rellena con caracteres de espacio en blanco para que su longitud sea igual al ancho del campo:
    • Si la alineación es positiva, la cadena con formato está alineada a la derecha con el relleno,
    • De lo contrario, está alineado a la izquierda anexando el relleno.

El significado general de un interpolated_string_expression, incluido el formato y el relleno anteriores de interpolaciones, se define mediante una conversión de la expresión a una invocación de método: si el tipo de la expresión es System.IFormattable o System.FormattableString ese método es System.Runtime.CompilerServices.FormattableStringFactory.Create (§C.3) que devuelve un valor de tipo System.FormattableString; de lo contrario, el tipo será string y el método es string.Format (§C.2) que devuelve un valor de tipo string.

En ambos casos, la lista de argumentos de la llamada consta de un literal de cadena de formato con especificaciones de formato para cada interpolación y un argumento para cada expresión correspondiente a las especificaciones de formato.

El literal de cadena de formato se construye de la manera siguiente, donde N es el número de interpolaciones en el interpolated_string_expression. El literal de cadena de formato consta de, en orden:

  • Caracteres del Interpolated_Regular_String_Start o Interpolated_Verbatim_String_Start
  • Caracteres del Interpolated_Regular_String_Mid o Interpolated_Verbatim_String_Mid, si existe
  • A continuación, si N ≥ 1 para cada número I de 0 a N-1:
    • Especificación de marcador de posición:
      • Carácter de llave izquierda ({)
      • Representación decimal de I
      • A continuación, si el regular_interpolation o verbatim_interpolation correspondiente tiene un interpolation_minimum_width, una coma (,) seguida de la representación decimal del valor del constant_expression
      • Caracteres del Regular_Interpolation_Format o Verbatim_Interpolation_Format, si existe, del regular_interpolation o verbatim_interpolation correspondientes.
      • Un carácter de llave derecha (})
    • Los caracteres del Interpolated_Regular_String_Mid o Interpolated_Verbatim_String_Mid inmediatamente después de la interpolación correspondiente, si existe
  • Por último, los caracteres del Interpolated_Regular_String_End o Interpolated_Verbatim_String_End.

Los argumentos siguientes son las expresionesde las interpolaciones, si las hay, en orden.

Cuando un interpolated_string_expression contiene varias interpolaciones, las expresiones de esas interpolaciones se evalúan en orden textual de izquierda a derecha.

Ejemplo:

En este ejemplo se usan las siguientes características de especificación de formato:

  • la X especificación de formato que da formato a enteros como hexadecimal mayúsculas,
  • el formato predeterminado de un string valor es el propio valor,
  • valores de alineación positivos que justifican a la derecha dentro del ancho mínimo de campo especificado,
  • valores de alineación negativos que se justifican a la izquierda dentro del ancho mínimo de campo especificado,
  • constantes definidas para la interpolation_minimum_width y
  • }} y {{ tienen el formato { y } respectivamente.

Con estas premisas:

string text = "red";
int number = 14;
const int width = -4;

Después:

Expresión de cadena interpolada Significado equivalente como string Valor
$"{text}" string.Format("{0}", text) "red"
$"{{text}}" string.Format("{{text}}) "{text}"
$"{ text , 4 }" string.Format("{0,4}", text) " red"
$"{ text , width }" string.Format("{0,-4}", text) "red "
$"{number:X}" string.Format("{0:X}", number) "E"
$"{text + '?'} {number % 3}" string.Format("{0} {1}", text + '?', number % 3) "red? 2"
$"{text + $"[{number}]"}" string.Format("{0}", text + string.Format("[{0}]", number)) "red[14]"
$"{(number==0?"Zero":"Non-zero")}" string.Format("{0}", (number==0?"Zero":"Non-zero")) "Non-zero"

ejemplo final

12.8.4 Nombres simples

Un simple_name consta de un identificador, seguido opcionalmente de una lista de argumentos de tipo:

simple_name
    : identifier type_argument_list?
    ;

Un simple_name es de la forma I o del formulario I<A₁, ..., Aₑ>, donde I es un identificador único y I<A₁, ..., Aₑ> es un type_argument_list opcional. Cuando no se especifica ningún type_argument_list , considere la posibilidad e de ser cero. El simple_name se evalúa y clasifica de la siguiente manera:

  • Si e es cero y el simple_name aparece dentro de un espacio de declaración de variable local (§7.3) que contiene directamente una variable local, un parámetro o una constante con el nombre I, el simple_name hace referencia a esa variable local, parámetro o constante y se clasifica como una variable o valor.
  • Si e es cero y el simple_name aparece dentro de una declaración de método genérico, pero fuera de los atributos de su method_declaration, y si esa declaración incluye un parámetro de tipo con nombre I, el simple_name hace referencia a ese parámetro de tipo.
  • De lo contrario, para cada tipo T de instancia (§15.3.2), empezando por el tipo de instancia de la declaración de tipo envolvente inmediatamente y continuando con el tipo de instancia de cada clase o declaración de estructura envolvente (si existe):
    • Si e es cero y la declaración de incluye un parámetro de T tipo con el nombre I, el simple_name hace referencia a ese parámetro de tipo.
    • De lo contrario, si una búsqueda de miembros (§12.5) de en T con e argumentos de I tipo genera una coincidencia:
      • Si T es el tipo de instancia de la clase o estructura envolvente inmediatamente y la búsqueda identifica uno o varios métodos, el resultado es un grupo de métodos con una expresión de instancia asociada de this. Si se especificó una lista de argumentos de tipo, se usa para llamar a un método genérico (§12.8.10.2).
      • De lo contrario, si es el tipo de instancia de la clase o estructura envolvente inmediatamente, si la búsqueda identifica un miembro de instancia y, si T la referencia se produce dentro del bloque de un constructor de instancia, un método de instancia o un descriptor de acceso de instancia (§12.2.1), el resultado es el mismo que un acceso de miembro (§12.8.7) del formulario this.I. Esto solo puede ocurrir cuando e es cero.
      • De lo contrario, el resultado es el mismo que un acceso de miembro (§12.8.7) del formulario T.I o T.I<A₁, ..., Aₑ>.
  • De lo contrario, para cada espacio de nombres N, empezando por el espacio de nombres en el que se produce el simple_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 e 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 simple_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 simple_name es ambiguo y se produce un error en tiempo de compilación.
      • De lo contrario, el simple_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 e tipo, a continuación:
      • Si e es cero y la ubicación en la que se produce el simple_name se incluye en una declaración de espacio de nombres para N 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, el simple_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 simple_name se incluye en una declaración de espacio de nombres para N:
      • Si e 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 simple_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 e tipo, el simple_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 e tipo, el simple_name es ambiguo y se produce un error en tiempo de compilación.

    Nota: Este paso completo es exactamente paralelo al paso correspondiente en el procesamiento de un namespace_or_type_name (§7.8). nota final

  • De lo contrario, si e es cero y I es el identificador _, el simple_name es un descarte sencillo, que es una forma de expresión de declaración (§12.17).
  • De lo contrario, el simple_name no está definido y se produce un error en tiempo de compilación.

12.8.5 Expresiones entre paréntesis

Un parenthesized_expression consta de una expresión entre paréntesis.

parenthesized_expression
    : '(' expression ')'
    ;

Una parenthesized_expression se evalúa mediante la evaluación de la expresión entre paréntesis. Si la expresión entre paréntesis denota un espacio de nombres o un tipo, se produce un error en tiempo de compilación. De lo contrario, el resultado de la parenthesized_expression es el resultado de la evaluación de la expresión contenida.

12.8.6 Expresiones de tupla

Un tuple_expression representa una tupla y consta de dos o más expresiones separadas por comas y, opcionalmente, con nombre entre paréntesis. Un deconstruction_expression es una sintaxis abreviada para una tupla que contiene expresiones de declaración con tipo implícito.

tuple_expression
    : '(' tuple_element (',' tuple_element)+ ')'
    | deconstruction_expression
    ;
    
tuple_element
    : (identifier ':')? expression
    ;
    
deconstruction_expression
    : 'var' deconstruction_tuple
    ;
    
deconstruction_tuple
    : '(' deconstruction_element (',' deconstruction_element)+ ')'
    ;

deconstruction_element
    : deconstruction_tuple
    | identifier
    ;

Un tuple_expression se clasifica como una tupla.

Una deconstruction_expression var (e1, ..., en) es abreviada para el tuple_expression (var e1, ..., var en) y sigue el mismo comportamiento. Esto se aplica de forma recursiva a cualquier deconstruction_tupleanidado en el deconstruction_expression. Cada identificador anidado dentro de un deconstruction_expression introduce así una expresión de declaración (§12.17). Como resultado, un deconstruction_expression solo puede producirse en el lado izquierdo de una asignación simple.

Una expresión de tupla tiene un tipo si y solo si cada una de sus expresiones Ei de elemento tiene un tipo Ti. El tipo debe ser un tipo de tupla de la misma aridad que la expresión de tupla, donde cada elemento recibe lo siguiente:

  • Si el elemento de tupla en la posición correspondiente tiene un nombre Ni, el elemento de tipo de tupla será Ti Ni.
  • De lo contrario, si Ei tiene el formato Ni o E.Ni E?.Ni , el elemento de tipo de tupla será Ti Ni, a menos que cualquiera de las siguientes suspensiones:
    • Otro elemento de la expresión de tupla tiene el nombre Ni, o
    • Otro elemento de tupla sin un nombre tiene una expresión de elemento de tupla del formulario Ni o E.Ni E?.Nio , o
    • Ni es del formato ItemX, donde X es una secuencia de0 dígitos decimales no iniciados que podrían representar la posición de un elemento de tupla y X no representa la posición del elemento.
  • De lo contrario, el elemento de tipo de tupla será Ti.

Una expresión de tupla se evalúa evaluando cada una de sus expresiones de elemento en orden de izquierda a derecha.

Un valor de tupla se puede obtener de una expresión de tupla si lo convierte en un tipo de tupla (§10.2.13), reclasificándolo como un valor (§12.2.2)) o convirtiéndolo en el destino de una asignación deconstrucción (§12.21.2).

Ejemplo:

(int i, string) t1 = (i: 1, "One");
(long l, string) t2 = (l: 2, null);
var t3 = (i: 3, "Three");          // (int i, string)
var t4 = (i: 4, null);             // Error: no type

En este ejemplo, las cuatro expresiones de tupla son válidas. Los dos t1 primeros y t2, no usan el tipo de la expresión de tupla, sino que aplican una conversión de tupla implícita. En el caso de t2, la conversión de tupla implícita se basa en las conversiones implícitas de 2 a y long desde null a string. La tercera expresión de tupla tiene un tipo (int i, string)y, por tanto, se puede reclasificar como un valor de ese tipo. La declaración de t4, por otro lado, es un error: la expresión de tupla no tiene ningún tipo porque su segundo elemento no tiene ningún tipo.

if ((x, y).Equals((1, 2))) { ... };

En este ejemplo se muestra que las tuplas a veces pueden provocar varias capas de paréntesis, especialmente cuando la expresión de tupla es el único argumento para una invocación de método.

ejemplo final

Acceso a miembros 12.8.7

12.8.7.1 General

Un member_access consta de un primary_expression, un predefined_type o un qualified_alias_member, seguido de un token ".", seguido de un identificador, opcionalmente seguido de un type_argument_list.

member_access
    : primary_expression '.' identifier type_argument_list?
    | predefined_type '.' identifier type_argument_list?
    | qualified_alias_member '.' identifier type_argument_list?
    ;

predefined_type
    : 'bool' | 'byte' | 'char' | 'decimal' | 'double' | 'float' | 'int'
    | 'long' | 'object' | 'sbyte' | 'short' | 'string' | 'uint' | 'ulong'
    | 'ushort'
    ;

La qualified_alias_member producción se define en §14.8.

Un member_access es de la forma E.I o del formulario E.I<A₁, ..., Aₑ>, donde E es un primary_expression, predefined_type o qualified_alias_member, I es un identificador único y <A₁, ..., Aₑ> es un type_argument_list opcional. Cuando no se especifica ningún type_argument_list , considere la posibilidad e de ser cero.

Un member_access con un primary_expression de tipo dynamic está enlazado dinámicamente (§12.3.3). En este caso, el compilador clasifica el acceso de miembro como acceso de propiedad de tipo dynamic. Las reglas siguientes para determinar el significado del member_access se aplican en tiempo de ejecución mediante el tipo en tiempo de ejecución en lugar del tipo en tiempo de compilación del primary_expression. Si esta clasificación en tiempo de ejecución conduce a un grupo de métodos, el acceso de miembro será el primary_expression de un invocation_expression.

El member_access se evalúa y clasifica de la siguiente manera:

  • Si e es cero y E es un espacio de nombres y E contiene un espacio de nombres anidado con el nombre I, el resultado es ese espacio de nombres.
  • De lo contrario, si E es un espacio de nombres y E contiene un tipo accesible que tiene parámetros de nombre I y K tipo, el resultado es ese tipo construido con los argumentos de tipo especificados.
  • Si E se clasifica como un tipo, si E no es un parámetro de tipo y si una búsqueda de miembros (§12.5) de en E con K parámetros de I tipo genera una coincidencia, E.I se evalúa y clasifica de la siguiente manera:

    Nota: Cuando el resultado de dicha búsqueda de miembros es un grupo de métodos y K es cero, el grupo de métodos puede contener métodos que tienen parámetros de tipo. Esto permite que estos métodos se consideren para la inferencia de argumentos de tipo. nota final

    • Si I identifica un tipo, el resultado es ese tipo construido con cualquier argumento de tipo determinado.
    • Si I identifica uno o varios métodos, el resultado es un grupo de métodos sin ninguna expresión de instancia asociada.
    • Si I identifica una propiedad estática, el resultado es un acceso de propiedad sin ninguna expresión de instancia asociada.
    • Si I identifica un campo estático:
      • Si el campo es de solo lectura y la referencia se produce fuera del constructor estático de la clase o estructura en la que se declara el campo, el resultado es un valor, es decir, el valor del campo I estático en E.
      • De lo contrario, el resultado es una variable, es decir, el campo I estático en E.
    • Si I identifica un evento estático:
      • Si la referencia se produce dentro de la clase o estructura en la que se declara el evento y el evento se declaró sin event_accessor_declarations (§15.8.1), se E.I procesa exactamente como si I fuera un campo estático.
      • De lo contrario, el resultado es un acceso a eventos sin ninguna expresión de instancia asociada.
    • Si I identifica una constante, el resultado es un valor, es decir, el valor de esa constante.
    • Si I identifica un miembro de enumeración, el resultado es un valor, es decir, el valor de ese miembro de enumeración.
    • De lo contrario, E.I es una referencia de miembro no válida y se produce un error en tiempo de compilación.
  • Si E es un acceso de propiedad, acceso de indexador, variable o valor, el tipo de que es Ty una búsqueda de miembros (§12.5) de en T con K argumentos de I tipo produce una coincidencia, se E.I evalúa y clasifica de la siguiente manera:
    • En primer lugar, si E es una propiedad o acceso de indexador, se obtiene el valor de la propiedad o el acceso del indexador (§12.2.2) y E se vuelve a clasificar como un valor.
    • Si I identifica uno o varios métodos, el resultado es un grupo de métodos con una expresión de instancia asociada de E.
    • Si I identifica una propiedad de instancia, el resultado es un acceso de propiedad con una expresión de instancia asociada de E y un tipo asociado que es el tipo de la propiedad. Si T es un tipo de clase, el tipo asociado se elige de la primera declaración o invalidación de la propiedad que se encuentra al comenzar con Ty buscar en sus clases base.
    • Si T es un class_type e I identifica un campo de instancia de ese class_type:
      • Si el valor de E es null, se produce una System.NullReferenceException excepción .
      • De lo contrario, si el campo es de solo lectura y la referencia se produce fuera de un constructor de instancia de la clase en la que se declara el campo, el resultado es un valor, es decir, el valor del campo I en el objeto al que hace Ereferencia .
      • De lo contrario, el resultado es una variable, es decir, el campo I del objeto al que hace Ereferencia .
    • Si T es un struct_type e I identifica un campo de instancia de ese struct_type:
      • Si E es un valor o si el campo es de solo lectura y la referencia se produce fuera de un constructor de instancia del struct en el que se declara el campo, el resultado es un valor, es decir, el valor del campo I en la instancia de estructura dada por E.
      • De lo contrario, el resultado es una variable, es decir, el campo I de la instancia de estructura dada por E.
    • Si I identifica un evento de instancia:
  • De lo contrario, se intenta procesar E.I como invocación de método de extensión (§12.8.10.3). Si se produce un error, E.I es una referencia de miembro no válida y se produce un error en tiempo de enlace.

12.8.7.2 Nombres simples y nombres de tipo idénticos

En un acceso de miembro del formulario E.I, si E es un identificador único y si el significado de E como un simple_name (§12.8.4) es una constante, campo, propiedad, variable local o parámetro con el mismo tipo que el significado de E como un type_name (§7.8.1), se permiten ambos significados posibles de E . La búsqueda de miembros de E.I nunca es ambigua, ya que I necesariamente será miembro del tipo E en ambos casos. En otras palabras, la regla simplemente permite el acceso a los miembros estáticos y los tipos anidados de donde se habría producido un error en tiempo de E compilación.

Ejemplo:

struct Color
{
    public static readonly Color White = new Color(...);
    public static readonly Color Black = new Color(...);
    public Color Complement() => new Color(...);
}

class A
{
    public «Color» Color;              // Field Color of type Color

    void F()
    {
        Color = «Color».Black;         // Refers to Color.Black static member
        Color = Color.Complement();  // Invokes Complement() on Color field
    }

    static void G()
    {
        «Color» c = «Color».White;       // Refers to Color.White static member
    }
}

Solo para fines expositores, dentro de la A clase , esas apariciones del Color identificador que hacen referencia al Color tipo están delimitadas por «...»y las que hacen referencia al Color campo no.

ejemplo final

12.8.8 Acceso condicional nulo a miembros

Un null_conditional_member_access es una versión condicional de member_access (§12.8.7) y es un error de hora de enlace si el tipo de resultado es void. Para obtener una expresión condicional nula donde el tipo de resultado puede verse void (§12.8.11).

Un null_conditional_member_access consta de un primary_expression seguido de los dos tokens "?" y ".", seguidos de un identificador con un type_argument_list opcional, seguido de cero o más dependent_accesses cualquiera de los cuales puede preceedido por un null_forgiving_operator.

null_conditional_member_access
    : primary_expression '?' '.' identifier type_argument_list?
      (null_forgiving_operator? dependent_access)*
    ;
    
dependent_access
    : '.' identifier type_argument_list?    // member access
    | '[' argument_list ']'                 // element access
    | '(' argument_list? ')'                // invocation
    ;

null_conditional_projection_initializer
    : primary_expression '?' '.' identifier type_argument_list?
    ;

Una expresión E null_conditional_member_access tiene el formato P?.A. El significado de E se determina de la siguiente manera:

  • Si el tipo de es un tipo de P valor que acepta valores NULL:

    Deje que T sea el tipo de P.Value.A.

    • Si T es un parámetro de tipo que no se sabe que es un tipo de referencia o un tipo de valor que no acepta valores NULL, se produce un error en tiempo de compilación.

    • Si T es un tipo de valor que no acepta valores NULL, el tipo de E es T?y el significado de E es el mismo que el significado de:

      ((object)P == null) ? (T?)null : P.Value.A
      

      Excepto que P se evalúa solo una vez.

    • De lo contrario, el tipo de E es Ty el significado de E es el mismo que el significado de:

      ((object)P == null) ? (T)null : P.Value.A
      

      Excepto que P se evalúa solo una vez.

  • De lo contrario:

    Vamos T a ser el tipo de la expresión P.A.

    • Si T es un parámetro de tipo que no se sabe que es un tipo de referencia o un tipo de valor que no acepta valores NULL, se produce un error en tiempo de compilación.

    • Si T es un tipo de valor que no acepta valores NULL, el tipo de E es T?y el significado de E es el mismo que el significado de:

      ((object)P == null) ? (T?)null : P.A
      

      Excepto que P se evalúa solo una vez.

    • De lo contrario, el tipo de E es Ty el significado de E es el mismo que el significado de:

      ((object)P == null) ? (T)null : P.A
      

      Excepto que P se evalúa solo una vez.

Nota: En una expresión del formulario:

P?.A₀?.A₁

después, si P se null evalúa como ninguna A₀ de las evaluaciones o A₁ se evalúan. Lo mismo sucede si una expresión es una secuencia de null_conditional_member_access o null_conditional_element_access operaciones §12.8.13 .

nota final

Un null_conditional_projection_initializer es una restricción de null_conditional_member_access y tiene la misma semántica. Solo se produce como inicializador de proyección en una expresión de creación de objetos anónimos (§12.8.17.7).

12.8.9 Expresiones que admiten valores NULL

12.8.9.1 General

Un valor, tipo, clasificación (§12.2) y contexto seguro (§16.4.12) es el valor, el tipo, la clasificación y el contexto seguro de su primary_expression.

null_forgiving_expression
    : primary_expression null_forgiving_operator
    ;

null_forgiving_operator
    : '!'
    ;

Nota: Los operadores de negación lógica de prefijos y null postfix (§12.9.4), mientras que representados por el mismo token léxico (!), son distintos. Solo se puede invalidar este último (§15.10), se fija la definición del operador que admite valores NULL. nota final

Se trata de un error en tiempo de compilación para aplicar el operador null-forgiving más de una vez a la misma expresión, sin perjuicio de paréntesis entre paréntesis.

Ejemplo: lo siguiente no es válido:

var p = q!!;            // error: applying null_forgiving_operator more than once
var s = ( ( m(t) ! ) )! // error: null_forgiving_operator applied twice to m(t)

ejemplo final

El resto de esta subclausa y las siguientes subclases del mismo nivel son normativas condicionalmente.

Un compilador que realiza el análisis de estado nulo estático (§8.9.5) debe cumplir la especificación siguiente.

El operador null-forgiving es una pseudooperación en tiempo de compilación que se usa para informar al análisis de estado estático de un compilador. Tiene dos usos: para invalidar la determinación de un compilador de que una expresión puede ser nula y para invalidar un compilador que emite una advertencia relacionada con la nulabilidad.

Aplicar el operador null-forgiving a una expresión para la que el análisis de estado null estático de un compilador no genera ninguna advertencia no es un error.

12.8.9.2 Invalidar una determinación "tal vez nula"

En algunas circunstancias, el análisis de estado null estático de un compilador puede determinar que una expresión tiene el estado NULL tal vez null y emitir una advertencia de diagnóstico cuando otra información indica que la expresión no puede ser null. Al aplicar el operador null-forgiving a dicha expresión, se informa al análisis estático de estado NULL del compilador de que el estado NULL no es NULL; lo que impide la advertencia de diagnóstico y puede informar a cualquier análisis en curso.

Ejemplo: Tenga en cuenta lo siguiente:

#nullable enable
public static void M()
{
    Person? p = Find("John");                  // returns Person?
    if (IsValid(p))
    {
       Console.WriteLine($"Found {p!.Name}");  // p can't be null
    }
}

public static bool IsValid(Person? person) =>
    person != null && person.Name != null;

Si IsValid devuelve true, p se puede desreferenciar de forma segura para tener acceso a su Name propiedad y se puede suprimir la advertencia "desreferenciación de un valor posiblemente NULL" mediante !.

ejemplo final

Ejemplo: El operador null-forgiving debe usarse con precaución, tenga en cuenta lo siguiente:

#nullable enable
int B(int? x)
{
    int y = (int)x!; // quash warning, throw at runtime if x is null
    return y;
}

Aquí se aplica el operador null-forgiving a un tipo de valor y se anula cualquier advertencia en x. Sin embargo, si x está null en tiempo de ejecución, se producirá una excepción, ya null que no se puede convertir a int.

ejemplo final

12.8.9.3 Invalidación de otras advertencias de análisis null

Además de invalidar quizás determinaciones nulas como antes, puede haber otras circunstancias en las que se desee invalidar la determinación de análisis de estado null estático de un compilador que una expresión requiere una o varias advertencias. Aplicar el operador null-forgiving a estas solicitudes de expresión que el compilador no emite ninguna advertencia para la expresión. En respuesta, un compilador puede optar por no emitir advertencias y también modificar su análisis posterior.

Ejemplo: Tenga en cuenta lo siguiente:

#nullable enable
public static void Assign(out string? lv, string? rv) { lv = rv; }

public string M(string? t)
{
    string s;
    Assign(out s!, t ?? "«argument was null»");
    return s;
}

Los tipos de parámetros del método Assign, lv y rv, son string?, con lv ser un parámetro de salida y realiza una asignación simple.

El método M pasa la variable s, de tipo string, como Assignparámetro de salida, el compilador usó una advertencia, ya s que no es una variable que acepta valores NULL. Dado que Assignel segundo argumento no puede ser NULL, se usa el operador null forgiving para anular la advertencia.

ejemplo final

Fin del texto normativo condicionalmente.

12.8.10 Expresiones de invocación

12.8.10.1 General

Se usa un invocation_expression para invocar un método.

invocation_expression
    : primary_expression '(' argument_list? ')'
    ;

El primary_expression puede ser un null_forgiving_expression si y solo si tiene un delegate_type.

Un invocation_expression se enlaza dinámicamente (§12.3.3) si al menos una de las siguientes suspensiones:

  • El primary_expression tiene el tipo dynamicen tiempo de compilación .
  • Al menos un argumento del argument_list opcional tiene el tipo dynamicen tiempo de compilación .

En este caso, el compilador clasifica el invocation_expression como un valor de tipo dynamic. Las reglas siguientes para determinar el significado de la invocation_expression se aplican en tiempo de ejecución, mediante el tipo en tiempo de ejecución en lugar del tipo en tiempo de compilación de los de la primary_expression y los argumentos que tienen el tipo dynamicen tiempo de compilación . Si el primary_expression no tiene el tipo dynamicen tiempo de compilación , la invocación del método se somete a una comprobación limitada en tiempo de compilación, tal como se describe en §12.6.5.

El primary_expression de un invocation_expression será un grupo de métodos o un valor de un delegate_type. Si el primary_expression es un grupo de métodos, el invocation_expression es una invocación de método (§12.8.10.2). Si el primary_expression es un valor de un delegate_type, el invocation_expression es una invocación de delegado (§12.8.10.4). Si el primary_expression no es un grupo de métodos ni un valor de un delegate_type, se produce un error en tiempo de enlace.

El argument_list opcional (§12.6.2) proporciona valores o referencias de variables para los parámetros del método.

El resultado de evaluar un invocation_expression se clasifica de la siguiente manera:

  • Si el invocation_expression invoca un método returns-no-value (§15.6.1) o un delegado returns-no-value, el resultado no es nada. Una expresión que se clasifica como nada solo se permite en el contexto de un statement_expression (§13.7) o como el cuerpo de un lambda_expression (§12.19). De lo contrario, se produce un error en tiempo de enlace.
  • De lo contrario, si el invocation_expression invoca un método return-by-ref (§15.6.1) o un delegado return-by-ref, el resultado es una variable con un tipo asociado del tipo de valor devuelto del método o delegado. Si la invocación es de un método de instancia y el receptor es de un tipo Tde clase , el tipo asociado se elige de la primera declaración o invalidación del método encontrado al comenzar con T y buscar en sus clases base.
  • De lo contrario, el invocation_expression invoca un método return-by-value (§15.6.1) o un delegado devuelto por valor, y el resultado es un valor, con un tipo asociado del tipo de valor devuelto del método o delegado. Si la invocación es de un método de instancia y el receptor es de un tipo Tde clase , el tipo asociado se elige de la primera declaración o invalidación del método encontrado al comenzar con T y buscar en sus clases base.

12.8.10.2 Invocaciones del método

Para una invocación de método, el primary_expression del invocation_expression será un grupo de métodos. El grupo de métodos identifica el método que se va a invocar o el conjunto de métodos sobrecargados desde los que elegir un método específico que se va a invocar. En este último caso, la determinación del método específico que se va a invocar se basa en el contexto proporcionado por los tipos de los argumentos del argument_list.

El procesamiento en tiempo de enlace de una invocación de método del formulario M(A), donde M es un grupo de métodos (posiblemente incluido un type_argument_list) y A es un argument_list opcional, consta de los pasos siguientes:

  • El conjunto de métodos candidatos para la invocación del método se construye. Para cada método F asociado al grupo Mde métodos :
    • Si F no es genérico, F es un candidato cuando:
      • M no tiene ninguna lista de argumentos de tipo y
      • F es aplicable con respecto a A (§12.6.4.2).
    • Si F es genérico y M no tiene ninguna lista de argumentos de tipo, F es un candidato cuando:
      • La inferencia de tipos (§12.6.3) se realiza correctamente, inferir una lista de argumentos de tipo para la llamada y
      • Una vez que los argumentos de tipo inferido se sustituyen por los parámetros de tipo de método correspondientes, todos los tipos construidos de la lista de parámetros de F cumplen sus restricciones (§8.4.5) y la lista de parámetros de F es aplicable con respecto a A (§12.6.4.2)
    • Si F es genérico e M incluye una lista de argumentos de tipo, F es un candidato cuando:
      • F tiene el mismo número de parámetros de tipo de método que se proporcionaron en la lista de argumentos de tipo y
      • Una vez que los argumentos de tipo se sustituyen por los parámetros de tipo de método correspondientes, todos los tipos construidos de la lista de parámetros de F cumplen sus restricciones (§8.4.5) y la lista de parámetros de F es aplicable con respecto a A (§12.6.4.2).
  • El conjunto de métodos candidatos se reduce para contener solo métodos de los tipos más derivados: para cada método C.F del conjunto, donde C es el tipo en el que se declara el método F , todos los métodos declarados en un tipo base de C se quitan del conjunto. Además, si C es un tipo de clase distinto objectde , todos los métodos declarados en un tipo de interfaz se quitan del conjunto.

    Nota: Esta última regla solo tiene un efecto cuando el grupo de métodos era el resultado de una búsqueda de miembros en un parámetro de tipo que tiene una clase base efectiva distinta object de y un conjunto de interfaz efectivo no vacío. nota final

  • Si el conjunto resultante de métodos candidatos está vacío, se abandona el procesamiento posterior a lo largo de los pasos siguientes y, en su lugar, se intenta procesar la invocación como invocación de método de extensión (§12.8.10.3). Si se produce un error, no existen métodos aplicables y se produce un error en tiempo de enlace.
  • El mejor método del conjunto de métodos candidatos se identifica mediante las reglas de resolución de sobrecarga de §12.6.4. Si no se puede identificar un único método mejor, la invocación del método es ambigua y se produce un error en tiempo de enlace. Al realizar la resolución de sobrecarga, los parámetros de un método genérico se consideran después de sustituir los argumentos de tipo (proporcionados o inferidos) para los parámetros de tipo de método correspondientes.

Una vez que se ha seleccionado y validado un método en tiempo de enlace mediante los pasos anteriores, la invocación en tiempo de ejecución real se procesa según las reglas de invocación de miembro de función descritas en §12.6.6.

Nota: El efecto intuitivo de las reglas de resolución descritas anteriormente es el siguiente: Para buscar el método concreto invocado por una invocación de método, comience con el tipo indicado por la invocación del método y continúe con la cadena de herencia hasta que se encuentre al menos una declaración de método accesible, accesible y sin invalidación. A continuación, realice la inferencia de tipos y la resolución de sobrecargas en el conjunto de métodos aplicables, accesibles y no invalidados declarados en ese tipo e invoque el método seleccionado de este modo. Si no se encontró ningún método, intente procesar la invocación como una invocación de método de extensión. nota final

12.8.10.3 Invocaciones de método de extensión

En una invocación de método (§12.6.6.2) de uno de los formularios

«expr» . «identifier» ( )  
«expr» . «identifier» ( «args» )  
«expr» . «identifier» < «typeargs» > ( )  
«expr» . «identifier» < «typeargs» > ( «args» )

si el procesamiento normal de la invocación no encuentra ningún método aplicable, se intenta procesar la construcción como una invocación de método de extensión. Si «expr» o cualquiera de los «args» tiene el tipo dynamicen tiempo de compilación, los métodos de extensión no se aplicarán.

El objetivo es encontrar el mejor type_nameC, de modo que se pueda realizar la invocación del método estático correspondiente:

C . «identifier» ( «expr» )  
C . «identifier» ( «expr» , «args» )  
C . «identifier» < «typeargs» > ( «expr» )  
C . «identifier» < «typeargs» > ( «expr» , «args» )

Un método Cᵢ.Mₑ de extensión es apto si:

  • Cᵢ es una clase no genérica y no anidada
  • El nombre de Mₑ es el identificador
  • Mₑ es accesible y aplicable cuando se aplica a los argumentos como un método estático, como se muestra anteriormente.
  • Existe una conversión implícita de identidad, referencia o conversión boxing de expr al tipo del primer parámetro de Mₑ.

La búsqueda de C las ganancias es la siguiente:

  • A partir de la declaración de espacio de nombres más cercana, continuando con cada declaración de espacio de nombres envolvente y finalizando con la unidad de compilación contenedora, se realizan intentos sucesivos para buscar un conjunto candidato de métodos de extensión:
    • Si el espacio de nombres o la unidad de compilación especificados contiene directamente declaraciones de tipos no genéricos con métodos de extensión aptos Cᵢ Mₑ, el conjunto de esos métodos de extensión es el conjunto candidato.
    • Si los espacios de nombres importados mediante directivas de espacio de nombres en el espacio de nombres o la unidad de compilación especificados contienen directamente declaraciones de tipo no genéricos con métodos Mₑde extensión aptosCᵢ, el conjunto de esos métodos de extensión es el conjunto candidato.
  • Si no se encuentra ningún conjunto de candidatos en ninguna declaración de espacio de nombres o unidad de compilación envolvente, se produce un error en tiempo de compilación.
  • De lo contrario, la resolución de sobrecarga se aplica al conjunto candidato como se describe en §12.6.4. Si no se encuentra ningún método mejor, se produce un error en tiempo de compilación.
  • C es el tipo en el que el mejor método se declara como un método de extensión.

Con C como destino, la llamada al método se procesa como invocación de método estático (§12.6.6).

Nota: A diferencia de una invocación de método de instancia, no se produce ninguna excepción cuando expr se evalúa como una referencia nula. En su lugar, este null valor se pasa al método de extensión, ya que sería a través de una invocación de método estático normal. Es necesario que la implementación del método de extensión decida cómo responder a dicha llamada. nota final

Las reglas anteriores significan que los métodos de instancia tienen prioridad sobre los métodos de extensión, que los métodos de extensión disponibles en las declaraciones de espacio de nombres internos tienen prioridad sobre los métodos de extensión disponibles en declaraciones de espacio de nombres externos y que los métodos de extensión declarados directamente en un espacio de nombres tienen prioridad sobre los métodos de extensión importados en ese mismo espacio de nombres con una directiva de espacio de nombres mediante .

Ejemplo:

public static class E
{
    public static void F(this object obj, int i) { }
    public static void F(this object obj, string s) { }
}

class A { }

class B
{
    public void F(int i) { }
}

class C
{
    public void F(object obj) { }
}

class X
{
    static void Test(A a, B b, C c)
    {
        a.F(1);            // E.F(object, int)
        a.F("hello");      // E.F(object, string)
        b.F(1);            // B.F(int)
        b.F("hello");      // E.F(object, string)
        c.F(1);            // C.F(object)
        c.F("hello");      // C.F(object)
    }
}

En el ejemplo, Bel método tiene prioridad sobre el primer método de extensión y Cel método de tiene prioridad sobre ambos métodos de extensión.

public static class C
{
    public static void F(this int i) => Console.WriteLine($"C.F({i})");
    public static void G(this int i) => Console.WriteLine($"C.G({i})");
    public static void H(this int i) => Console.WriteLine($"C.H({i})");
}

namespace N1
{
    public static class D
    {
        public static void F(this int i) => Console.WriteLine($"D.F({i})");
        public static void G(this int i) => Console.WriteLine($"D.G({i})");
    }
}

namespace N2
{
    using N1;

    public static class E
    {
        public static void F(this int i) => Console.WriteLine($"E.F({i})");
    }

    class Test
    {
        static void Main(string[] args)
        {
            1.F();
            2.G();
            3.H();
        }
    }
}

La salida de este ejemplo es:

E.F(1)
D.G(2)
C.H(3)

D.Gtiene precendece sobre C.Gy tiene prioridad sobre y D.F C.F.E.F

ejemplo final

12.8.10.4 Invocaciones de delegado

Para una invocación de delegado, el primary_expression del invocation_expression será un valor de un delegate_type. Además, considerando la delegate_type ser miembro de función con la misma lista de parámetros que el delegate_type, el delegate_type será aplicable (§12.6.4.2) con respecto a la argument_list del invocation_expression.

El procesamiento en tiempo de ejecución de una invocación de delegado del formulario D(A), donde D es un primary_expression de un delegate_type y A es un argument_list opcional, consta de los pasos siguientes:

  • D se evalúa. Si esta evaluación provoca una excepción, no se ejecutan pasos adicionales.
  • La lista A de argumentos se evalúa. Si esta evaluación provoca una excepción, no se ejecutan pasos adicionales.
  • Se comprueba que el valor de D es válido. Si el valor de D es null, se produce una System.NullReferenceException excepción y no se ejecutan pasos adicionales.
  • De lo contrario, D es una referencia a una instancia de delegado. Las invocaciones de miembro de función (§12.6.6) se realizan en cada una de las entidades invocables de la lista de invocación del delegado. Para las entidades invocables que constan de una instancia y un método de instancia, la instancia de la invocación es la instancia contenida en la entidad invocable.

Consulte §20.6 para obtener más información sobre varias listas de invocación sin parámetros.

12.8.11 Expresión de invocación condicional nula

Un null_conditional_invocation_expression es sintácticamente un null_conditional_member_access (§12.8.8) o null_conditional_element_access (§12.8.13) donde el dependent_access final es una expresión de invocación (§12.8.10).

Un null_conditional_invocation_expression se produce en el contexto de un statement_expression (§13.7), anonymous_function_body (§12.19.1) o method_body (§15.6.1).

A diferencia del null_conditional_member_access o null_conditional_element_access equivalente sintácticamente, un null_conditional_invocation_expression puede clasificarse como nada.

null_conditional_invocation_expression
    : null_conditional_member_access null_forgiving_operator? '(' argument_list? ')'
    | null_conditional_element_access null_forgiving_operator? '(' argument_list? ')'
    ;

El null_forgiving_operator opcional puede incluirse si el null_conditional_member_access o null_conditional_element_access solo tiene un delegate_type.

Una expresión null_conditional_invocation_expression tiene el formato P?A; donde A es el resto del null_conditional_member_access o null_conditional_element_access equivalente sintácticamente, A comenzará por . o [. E Supongamos PA la concatenación de P y A.

Cuando E se produce como un statement_expression el significado de E es el mismo que el significado de la instrucción :

if ((object)P != null) PA

excepto que P se evalúa una sola vez.

Cuando E se produce como un anonymous_function_body o method_body el significado de depende de E su clasificación:

  • Si E se clasifica como nada, su significado es el mismo que el significado del bloque:

    { if ((object)P != null) PA; }
    

    excepto que P se evalúa una sola vez.

  • De lo contrario, el significado de E es el mismo que el significado del bloque:

    { return E; }
    

    y, a su vez, el significado de este bloque depende de si E es sintácticamente equivalente a un null_conditional_member_access (§12.8.8) o null_conditional_element_access (§12.8.13).

Acceso a elementos 12.8.12

12.8.12.1 General

Un element_access consta de un primary_no_array_creation_expression, seguido de un token "[", seguido de un argument_list, seguido de un token "]". El argument_list consta de uno o varios argumentos, separados por comas.

element_access
    : primary_no_array_creation_expression '[' argument_list ']'
    ;

No se permite que la argument_list de un element_access contenga out argumentos o ref .

Un element_access se enlaza dinámicamente (§12.3.3) si al menos una de las siguientes suspensiones:

  • El primary_no_array_creation_expression tiene el tipo dynamicen tiempo de compilación .
  • Al menos una expresión del argument_list tiene un tipo dynamic en tiempo de compilación y el primary_no_array_creation_expression no tiene un tipo de matriz.

En este caso, el compilador clasifica el element_access como un valor de tipo dynamic. Las reglas siguientes para determinar el significado de la element_access se aplican en tiempo de ejecución, utilizando el tipo en tiempo de ejecución en lugar del tipo en tiempo de compilación de los de la primary_no_array_creation_expression y argument_list expresiones que tienen el tipo dynamicen tiempo de compilación . Si el primary_no_array_creation_expression no tiene el tipo dynamicen tiempo de compilación , el acceso al elemento se somete a una comprobación limitada en tiempo de compilación, tal como se describe en §12.6.5.

Si el primary_no_array_creation_expression de un element_access es un valor de un array_type, el element_access es un acceso de matriz (§12.8.12.2). De lo contrario, el primary_no_array_creation_expression será una variable o un valor de una clase, estructura o tipo de interfaz que tenga uno o varios miembros del indexador, en cuyo caso el element_access es un acceso de indexador (§12.8.12.3).

Acceso a la matriz 12.8.12.2

Para el acceso a una matriz, el primary_no_array_creation_expression del element_access será un valor de un array_type. Además, no se permite que la argument_list de acceso a una matriz contenga argumentos con nombre. El número de expresiones de la argument_list será el mismo que el rango de la array_type, y cada expresión será de tipo int, uint, longo ulong, o se podrá convertir implícitamente en uno o varios de estos tipos.

El resultado de evaluar el acceso a una matriz es una variable del tipo de elemento de la matriz, es decir, el elemento de matriz seleccionado por los valores de las expresiones de la argument_list.

El procesamiento en tiempo de ejecución de un acceso de matriz del formulario P[A], donde P es un primary_no_array_creation_expression de un array_type y A es un argument_list, consta de los pasos siguientes:

  • P se evalúa. Si esta evaluación provoca una excepción, no se ejecutan pasos adicionales.
  • Las expresiones de índice del argument_list se evalúan en orden, de izquierda a derecha. Después de la evaluación de cada expresión de índice, se realiza una conversión implícita (§10.2) a uno de los siguientes tipos: int, uint, long, ulong. El primer tipo de esta lista para el que existe una conversión implícita se elige. Por ejemplo, si la expresión de índice es de tipo short , se realiza una conversión implícita a int , ya que las conversiones implícitas de short hacia int y desde short a long son posibles. Si la evaluación de una expresión de índice o la conversión implícita subsiguiente produce una excepción, no se evalúan más expresiones de índice y no se ejecutan más pasos.
  • Se comprueba que el valor de P es válido. Si el valor de P es null, se produce una System.NullReferenceException excepción y no se ejecutan pasos adicionales.
  • El valor de cada expresión del argument_list se comprueba con los límites reales de cada dimensión de la instancia de matriz a la que hace Preferencia . Si uno o varios valores están fuera del intervalo, se produce una System.IndexOutOfRangeException excepción y no se ejecutan más pasos.
  • La ubicación del elemento de matriz proporcionado por las expresiones de índice se calcula y esta ubicación se convierte en el resultado del acceso a la matriz.

Acceso al indexador 12.8.12.3

Para un acceso de indexador, el primary_no_array_creation_expression del element_access será una variable o un valor de una clase, estructura o tipo de interfaz, y este tipo implementará uno o varios indizadores que sean aplicables con respecto al argument_list de la element_access.

El procesamiento en tiempo de enlace de un acceso de indizador del formulario P[A], donde P es un primary_no_array_creation_expression de una clase, estructura o tipo Tde interfaz , y A es un argument_list, consta de los pasos siguientes:

  • El conjunto de indexadores proporcionados por T se construye. El conjunto consta de todos los indexadores declarados en T o un tipo base de que no son declaraciones de T invalidación y son accesibles en el contexto actual (§7.5).
  • El conjunto se reduce a los indexadores aplicables y no ocultos por otros indexadores. Las reglas siguientes se aplican a cada indexador S.I del conjunto, donde S es el tipo en el que se declara el indexador I :
    • Si I no es aplicable con respecto a A (§12.6.4.2), I se quita del conjunto.
    • Si I es aplicable con respecto a A (§12.6.4.2), todos los indexadores declarados en un tipo base de S se quitan del conjunto.
    • Si I es aplicable con respecto a A (§12.6.4.2) y S es un tipo de clase distinto objectde , todos los indexadores declarados en una interfaz se quitan del conjunto.
  • Si el conjunto resultante de indexadores candidatos está vacío, no existen indizadores aplicables y se produce un error en tiempo de enlace.
  • El mejor indexador del conjunto de indexadores candidatos se identifica mediante las reglas de resolución de sobrecarga de §12.6.4. Si no se puede identificar un único mejor indexador, el acceso al indexador es ambiguo y se produce un error en tiempo de enlace.
  • Las expresiones de índice del argument_list se evalúan en orden, de izquierda a derecha. El resultado del procesamiento del acceso al indexador es una expresión clasificada como acceso de indexador. La expresión de acceso del indexador hace referencia al indexador determinado en el paso anterior y tiene una expresión de instancia asociada de P y una lista de argumentos asociada de Ay un tipo asociado que es el tipo del indexador. Si T es un tipo de clase, el tipo asociado se elige de la primera declaración o invalidación del indexador que se encuentra al comenzar con T y buscar en sus clases base.

Dependiendo del contexto en el que se use, un acceso de indexador provoca la invocación del descriptor de acceso get o del descriptor de acceso set del indexador. Si el acceso del indexador es el destino de una asignación, se invoca el descriptor de acceso set para asignar un nuevo valor (§12.21.2). En todos los demás casos, se invoca el descriptor de acceso get para obtener el valor actual (§12.2.2).

12.8.13 Acceso a elementos condicionales NULL

Un null_conditional_element_access consta de un primary_no_array_creation_expression seguido de los dos tokens "?" y "[", seguidos de un argument_list, seguido de un token "]", seguido de cero o más dependent_accesses cualquiera de los cuales puede ir precedido por un null_forgiving_operator.

null_conditional_element_access
    : primary_no_array_creation_expression '?' '[' argument_list ']'
      (null_forgiving_operator? dependent_access)*
    ;

Un null_conditional_element_access es una versión condicional de element_access (§12.8.12) y es un error de tiempo de enlace si el tipo de resultado es void. Para obtener una expresión condicional nula donde el tipo de resultado puede verse void (§12.8.11).

Una expresión E null_conditional_element_access tiene el formato P?[A]B; donde B son los dependent_accesses, si los hay. El significado de E se determina de la siguiente manera:

  • Si el tipo de es un tipo de P valor que acepta valores NULL:

    Vamos T a ser el tipo de la expresión P.Value[A]B.

    • Si T es un parámetro de tipo que no se sabe que es un tipo de referencia o un tipo de valor que no acepta valores NULL, se produce un error en tiempo de compilación.

    • Si T es un tipo de valor que no acepta valores NULL, el tipo de E es T?y el significado de E es el mismo que el significado de:

      ((object)P == null) ? (T?)null : P.Value[A]B
      

      Excepto que P se evalúa solo una vez.

    • De lo contrario, el tipo de E es Ty el significado de E es el mismo que el significado de:

      ((object)P == null) ? null : P.Value[A]B
      

      Excepto que P se evalúa solo una vez.

  • De lo contrario:

    Vamos T a ser el tipo de la expresión P[A]B.

    • Si T es un parámetro de tipo que no se sabe que es un tipo de referencia o un tipo de valor que no acepta valores NULL, se produce un error en tiempo de compilación.

    • Si T es un tipo de valor que no acepta valores NULL, el tipo de E es T?y el significado de E es el mismo que el significado de:

      ((object)P == null) ? (T?)null : P[A]B
      

      Excepto que P se evalúa solo una vez.

    • De lo contrario, el tipo de E es Ty el significado de E es el mismo que el significado de:

      ((object)P == null) ? null : P[A]B
      

      Excepto que P se evalúa solo una vez.

Nota: En una expresión del formulario:

P?[A₀]?[A₁]

si P se null evalúa como ninguna A₀ de las evaluaciones o A₁ se evalúan. Lo mismo sucede si una expresión es una secuencia de null_conditional_element_access o null_conditional_member_access operaciones §12.8.8 .

nota final

12.8.14 Este acceso

Un this_access consta de la palabra clave this.

this_access
    : 'this'
    ;

Solo se permite un this_access en el bloque de un constructor de instancia, un método de instancia, un descriptor de acceso de instancia (§12.2.1) o un finalizador. Tiene uno de los significados siguientes:

  • Cuando this se usa en un primary_expression dentro de un constructor de instancia de una clase, se clasifica como un valor. El tipo del valor es el tipo de instancia (§15.3.2) de la clase en la que se produce el uso y el valor es una referencia al objeto que se está construyendo.
  • Cuando this se usa en un primary_expression dentro de un método de instancia o descriptor de acceso de instancia de una clase, se clasifica como un valor. El tipo del valor es el tipo de instancia (§15.3.2) de la clase en la que se produce el uso y el valor es una referencia al objeto para el que se invocó el método o descriptor de acceso.
  • Cuando this se usa en un primary_expression dentro de un constructor de instancia de un struct, se clasifica como una variable. El tipo de la variable es el tipo de instancia (§15.3.2) del struct en el que se produce el uso y la variable representa la estructura que se va a construir.
    • Si la declaración del constructor no tiene inicializador de constructor, la this variable se comporta exactamente igual que un parámetro de salida del tipo de estructura. En concreto, esto significa que la variable se asignará definitivamente en todas las rutas de acceso de ejecución del constructor de instancia.
    • De lo contrario, la this variable se comporta exactamente igual que un ref parámetro del tipo de estructura. En concreto, esto significa que la variable se considera asignada inicialmente.
  • Cuando this se usa en un primary_expression dentro de un método de instancia o descriptor de acceso de instancia de un struct, se clasifica como una variable. El tipo de la variable es el tipo de instancia (§15.3.2) del struct en el que se produce el uso.
    • Si el método o descriptor de acceso no es un iterador (§15.14) o una función asincrónica (§15.15), la this variable representa la estructura para la que se invocó el método o descriptor de acceso.
      • Si la estructura es , readonly structla this variable se comporta exactamente igual que un parámetro de entrada del tipo de estructura.
      • De lo contrario, la this variable se comporta exactamente igual que un ref parámetro del tipo de estructura.
    • Si el método o descriptor de acceso es un iterador o una función asincrónica, la this variable representa una copia de la estructura para la que se invocó el método o descriptor de acceso, y se comporta exactamente igual que un parámetro de valor del tipo de estructura.

El uso de this en un primary_expression en un contexto distinto de los enumerados anteriormente es un error en tiempo de compilación. En concreto, no es posible hacer referencia a this en un método estático, un descriptor de acceso de propiedad estática o en un variable_initializer de una declaración de campo.

Acceso base 12.8.15

Un base_access consta de la base de palabras clave seguida de un token "." y un identificador y una type_argument_list opcional o un argument_list entre corchetes:

base_access
    : 'base' '.' identifier type_argument_list?
    | 'base' '[' argument_list ']'
    ;

Un base_access se usa para acceder a los miembros de clase base ocultos por miembros con nombre similar en la clase o estructura actual. Solo se permite un base_access en el cuerpo de un constructor de instancia, un método de instancia, un descriptor de acceso de instancia (§12.2.1) o un finalizador. Cuando base.I se produce en una clase o estructura, indicaré un miembro de la clase base de esa clase o estructura. Del mismo modo, cuando base[E] se produce en una clase, un indexador aplicable existirá en la clase base.

En tiempo de enlace, base_access expresiones del formulario base.I y base[E] se evalúan exactamente como si se escribieran ((B)this).I y ((B)this)[E], donde B es la clase base de la clase o estructura en la que se produce la construcción. Por lo tanto, base.I y corresponden a this.I y base[E] this[E], excepto this se ve como una instancia de la clase base.

Cuando un base_access hace referencia a un miembro de función virtual (un método, una propiedad o un indexador), se cambia la determinación del miembro de función que se va a invocar en tiempo de ejecución (§12.6.6). El miembro de función invocado se determina mediante la búsqueda de la implementación más derivada (§15.6.4) del miembro de función con respecto a B (en lugar de con respecto al tipo en tiempo de ejecución de this, como sería habitual en un acceso no base). Por lo tanto, dentro de una invalidación de un miembro de función virtual, se puede usar un base_access para invocar la implementación heredada del miembro de función. Si el miembro de función al que hace referencia un base_access es abstracto, se produce un error en tiempo de enlace.

Nota: A diferencia de this, base no es una expresión en sí misma. Es una palabra clave que solo se usa en el contexto de un base_access o un constructor_initializer (§15.11.2). nota final

12.8.16 Operadores de incremento y decremento postfijo

post_increment_expression
    : primary_expression '++'
    ;

post_decrement_expression
    : primary_expression '--'
    ;

El operando de una operación de incremento o disminución de postfijo será una expresión clasificada como una variable, un acceso a propiedades o un acceso de indizador. El resultado de la operación es un valor del mismo tipo que el operando.

Si el primary_expression tiene el tipo dynamic en tiempo de compilación, el operador se enlaza dinámicamente (§12.3.3), el post_increment_expression o post_decrement_expression tiene el tipo dynamic en tiempo de compilación y las siguientes reglas se aplican en tiempo de ejecución mediante el tipo de tiempo de ejecución del primary_expression.

Si el operando de una operación de incremento o disminución de postfijo es un acceso de propiedad o indexador, la propiedad o el indexador tendrán un descriptor de acceso get y set. Si no es así, se produce un error en tiempo de enlace.

La resolución de sobrecarga del operador unario (§12.4.4) se aplica para seleccionar una implementación específica del operador. Existen operadores predefinidos ++ y -- para los siguientes tipos: sbyte, byte, ushortintuintlongshort, charfloatulongdouble, decimaly cualquier tipo de enumeración. Los operadores predefinidos ++ devuelven el valor generado agregando 1 al operando y los operadores predefinidos -- devuelven el valor generado restando 1 del operando. En un contexto comprobado, si el resultado de esta suma o resta está fuera del intervalo del tipo de resultado y el tipo de resultado es un tipo entero o tipo de enumeración, se produce una System.OverflowException excepción .

Habrá una conversión implícita del tipo de valor devuelto del operador unario seleccionado al tipo del primary_expression; de lo contrario, se produce un error en tiempo de compilación.

El procesamiento en tiempo de ejecución de una operación de incremento o disminución de postfijo del formulario x++ o x-- consta de los pasos siguientes:

  • Si x se clasifica como una variable:
    • x se evalúa para generar la variable.
    • El valor de x se guarda.
    • El valor guardado de x se convierte en el tipo de operando del operador seleccionado y el operador se invoca con este valor como argumento.
    • El valor devuelto por el operador se convierte en el tipo de x y se almacena en la ubicación dada por la evaluación anterior de x.
    • El valor guardado de x se convierte en el resultado de la operación.
  • Si x se clasifica como acceso de propiedad o indexador:
    • La expresión de instancia (si x no statices ) y la lista de argumentos (si x es un acceso de indexador) asociada x a se evalúan y los resultados se usan en las invocaciones de descriptor de acceso get y set posteriores.
    • Se invoca el descriptor de acceso get de x y se guarda el valor devuelto.
    • El valor guardado de x se convierte en el tipo de operando del operador seleccionado y el operador se invoca con este valor como argumento.
    • El valor devuelto por el operador se convierte en el tipo de x y el descriptor de acceso set de x se invoca con este valor como argumento value.
    • El valor guardado de x se convierte en el resultado de la operación.

Los ++ operadores y -- también admiten la notación de prefijo (§12.9.6). El resultado de o x-- es el valor de x++ antes de x la operación, mientras que el resultado de ++x o --x es el valor de después de x la operación. En cualquier caso, x sí mismo tiene el mismo valor después de la operación.

Se puede invocar una implementación de operador ++ o operador -- mediante notación postfijo o prefijo. No es posible tener implementaciones de operador independientes para las dos notaciones.

12.8.17 El nuevo operador

12.8.17.1 General

El new operador se usa para crear nuevas instancias de tipos.

Hay tres formas de expresiones nuevas:

  • Las expresiones de creación de objetos y las expresiones de creación de objetos anónimos se usan para crear nuevas instancias de tipos de clase y tipos de valor.
  • Las expresiones de creación de matrices se usan para crear nuevas instancias de tipos de matriz.
  • Las expresiones de creación de delegados se usan para obtener instancias de tipos delegados.

El new operador implica la creación de una instancia de un tipo, pero no implica necesariamente la asignación de memoria. En concreto, las instancias de tipos de valor no requieren memoria adicional más allá de las variables en las que residen y no se produce ninguna asignación cuando new se usa para crear instancias de tipos de valor.

Nota: Las expresiones de creación del delegado no siempre crean nuevas instancias. Cuando la expresión se procesa de la misma manera que una conversión de grupo de métodos (§10.8) o una conversión de función anónima (§10.7), esto puede dar lugar a que se reutilice una instancia de delegado existente. nota final

12.8.17.2 Expresiones de creación de objetos

Se usa un object_creation_expression para crear una nueva instancia de un class_type o un value_type.

object_creation_expression
    : 'new' type '(' argument_list? ')' object_or_collection_initializer?
    | 'new' type object_or_collection_initializer
    ;

object_or_collection_initializer
    : object_initializer
    | collection_initializer
    ;

El tipo de un object_creation_expression será un class_type, un value_type o un type_parameter. El tipo no puede ser un tuple_type ni una class_type abstracta o estática.

El argument_list opcional (§12.6.2) solo se permite si el tipo es un class_type o un struct_type.

Una expresión de creación de objetos puede omitir la lista de argumentos del constructor y incluir paréntesis proporcionado que incluye un inicializador de objeto o inicializador de colección. Omitir la lista de argumentos del constructor y incluir paréntesis equivale a especificar una lista de argumentos vacía.

El procesamiento de una expresión de creación de objetos que incluye un inicializador de objeto o inicializador de colección consiste en procesar primero el constructor de instancia y, a continuación, procesar las inicializaciones de miembro o elemento especificadas por el inicializador de objeto (§12.8.17.3) o inicializador de colección (§12.8.17.4).

Si alguno de los argumentos del argument_list opcional tiene el tipo dynamic en tiempo de compilación, el object_creation_expression se enlaza dinámicamente (§12.3.3) y las siguientes reglas se aplican en tiempo de ejecución mediante el tipo en tiempo de ejecución de esos argumentos del argument_list que tienen el tipo dynamicen tiempo de compilación . Sin embargo, la creación del objeto se somete a una comprobación limitada en tiempo de compilación, tal como se describe en §12.6.5.

El procesamiento en tiempo de enlace de un object_creation_expression del formulario nuevo T(A), donde T es un class_type, o un value_type, y A es un argument_list opcional, consta de los pasos siguientes:

  • Si T es un value_type y A no está presente:
    • El object_creation_expression es una invocación de constructor predeterminada. El resultado de la object_creation_expression es un valor de tipo T, es decir, el valor predeterminado de T como se define en §8.3.3.
  • De lo contrario, si T es un type_parameter y A no está presente:
    • Si no se ha especificado ninguna restricción de tipo de valor o restricción de constructor (§15.2.5) para T, se produce un error en tiempo de enlace.
    • El resultado del object_creation_expression es un valor del tipo en tiempo de ejecución al que se ha enlazado el parámetro type, es decir, el resultado de invocar el constructor predeterminado de ese tipo. El tipo en tiempo de ejecución puede ser un tipo de referencia o un tipo de valor.
  • De lo contrario, si T es un class_type o un struct_type:
    • Si T es un class_type abstracto o estático, se produce un error en tiempo de compilación.
    • El constructor de instancia que se va a invocar se determina mediante las reglas de resolución de sobrecarga de §12.6.4. El conjunto de constructores de instancias candidatas consta de todos los constructores de instancias accesibles declarados en T, que son aplicables con respecto a A (§12.6.4.2). Si el conjunto de constructores de instancias candidatas está vacío o si no se puede identificar un único constructor de instancia mejor, se produce un error en tiempo de enlace.
    • El resultado del object_creation_expression es un valor de tipo T, es decir, el valor generado invocando el constructor de instancia determinado en el paso anterior.
    • De lo contrario, el object_creation_expression no es válido y se produce un error en tiempo de enlace.

Incluso si el object_creation_expression está enlazado dinámicamente, el tipo en tiempo de compilación sigue siendo T.

El procesamiento en tiempo de ejecución de un object_creation_expression del formulario nuevo T(A), donde T es class_type o un struct_type y A es un argument_list opcional, consta de los pasos siguientes:

  • Si T es un class_type:
    • Se asigna una nueva instancia de clase T . Si no hay suficiente memoria disponible para asignar la nueva instancia, se produce una System.OutOfMemoryException excepción y no se ejecutan más pasos.
    • Todos los campos de la nueva instancia se inicializan en sus valores predeterminados (§9.3).
    • El constructor de instancia se invoca según las reglas de invocación de miembro de función (§12.6.6). Una referencia a la instancia recién asignada se pasa automáticamente al constructor de instancia y se puede acceder a ella desde dentro de ese constructor.
  • Si T es un struct_type:
    • Se crea una instancia de tipo T asignando una variable local temporal. Dado que se requiere un constructor de instancia de un struct_type para asignar definitivamente un valor a cada campo de la instancia que se va a crear, no es necesaria ninguna inicialización de la variable temporal.
    • El constructor de instancia se invoca según las reglas de invocación de miembro de función (§12.6.6). Una referencia a la instancia recién asignada se pasa automáticamente al constructor de instancia y se puede acceder a ella desde dentro de ese constructor.

12.8.17.3 Inicializadores de objeto

Un inicializador de objeto especifica valores para cero o más campos, propiedades o elementos indexados de un objeto.

object_initializer
    : '{' member_initializer_list? '}'
    | '{' member_initializer_list ',' '}'
    ;

member_initializer_list
    : member_initializer (',' member_initializer)*
    ;

member_initializer
    : initializer_target '=' initializer_value
    ;

initializer_target
    : identifier
    | '[' argument_list ']'
    ;

initializer_value
    : expression
    | object_or_collection_initializer
    ;

Un inicializador de objeto consta de una secuencia de inicializadores de miembros, entre { tokens y } separados por comas. Cada member_initializer designará un destino para la inicialización. Un identificador asignará un nombre a un campo o una propiedad accesibles del objeto que se va a inicializar, mientras que un argument_list entre corchetes especificará argumentos para un indexador accesible en el objeto que se va a inicializar. Se trata de un error para que un inicializador de objeto incluya más de un inicializador de miembro para el mismo campo o propiedad.

Nota: Aunque no se permite que un inicializador de objeto establezca el mismo campo o propiedad más de una vez, no hay restricciones para los indexadores. Un inicializador de objeto puede contener varios destinos de inicializador que hacen referencia a indizadores e incluso puede usar los mismos argumentos del indexador varias veces. nota final

Cada initializer_target va seguido de un signo igual y una expresión, un inicializador de objeto o un inicializador de colección. No es posible que las expresiones del inicializador de objeto hagan referencia al objeto recién creado que se está inicializando.

Inicializador de miembro que especifica una expresión después de que el signo igual se procese de la misma manera que una asignación (§12.21.2) al destino.

Inicializador de miembro que especifica un inicializador de objeto después del signo igual es un inicializador de objeto anidado, es decir, una inicialización de un objeto incrustado. En lugar de asignar un nuevo valor al campo o la propiedad, las asignaciones del inicializador de objetos anidados se tratan como asignaciones a los miembros del campo o propiedad. Los inicializadores de objeto anidados no se pueden aplicar a propiedades con un tipo de valor ni a campos de solo lectura con un tipo de valor.

Inicializador de miembro que especifica un inicializador de colección después del signo igual es una inicialización de una colección incrustada. En lugar de asignar una nueva colección al campo de destino, la propiedad o el indexador, los elementos especificados en el inicializador se agregan a la colección a la que hace referencia el destino. El destino será de un tipo de colección que cumpla los requisitos especificados en §12.8.17.4.

Cuando un destino de inicializador hace referencia a un indexador, los argumentos del indexador siempre se evaluarán exactamente una vez. Por lo tanto, incluso si los argumentos terminan nunca siendo utilizados (por ejemplo, debido a un inicializador anidado vacío), se evalúan para sus efectos secundarios.

Ejemplo: La siguiente clase representa un punto con dos coordenadas:

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

Se puede crear e inicializar una instancia de Point como se indica a continuación:

Point a = new Point { X = 0, Y = 1 };

Esto tiene el mismo efecto que

Point __a = new Point();
__a.X = 0;
__a.Y = 1;
Point a = __a;

donde __a es una variable temporal invisible e inaccesible.

La siguiente clase muestra un rectángulo creado a partir de dos puntos y la creación e inicialización de una Rectangle instancia:

public class Rectangle
{
    public Point P1 { get; set; }
    public Point P2 { get; set; }
}

Se puede crear e inicializar una instancia de Rectangle como se indica a continuación:

Rectangle r = new Rectangle
{
    P1 = new Point { X = 0, Y = 1 },
    P2 = new Point { X = 2, Y = 3 }
};

Esto tiene el mismo efecto que

Rectangle __r = new Rectangle();
Point __p1 = new Point();
__p1.X = 0;
__p1.Y = 1;
__r.P1 = __p1;
Point __p2 = new Point();
__p2.X = 2;
__p2.Y = 3;
__r.P2 = __p2;
Rectangle r = __r;

donde __r, __p1 y __p2 son variables temporales que de lo contrario son invisibles e inaccesibles.

Si Rectangleel constructor asigna las dos instancias incrustadas, se pueden usar para inicializar las instancias incrustadas Point Point en lugar de asignar nuevas instancias:

public class Rectangle
{
    public Point P1 { get; } = new Point();
    public Point P2 { get; } = new Point();
}

La siguiente construcción se puede usar para inicializar las instancias incrustadas Point en lugar de asignar nuevas instancias:

Rectangle r = new Rectangle
{
    P1 = { X = 0, Y = 1 },
    P2 = { X = 2, Y = 3 }
};

Esto tiene el mismo efecto que

Rectangle __r = new Rectangle();
__r.P1.X = 0;
__r.P1.Y = 1;
__r.P2.X = 2;
__r.P2.Y = 3;
Rectangle r = __r;

ejemplo final

12.8.17.4 Inicializadores de colección

Un inicializador de colección especifica los elementos de una colección.

collection_initializer
    : '{' element_initializer_list '}'
    | '{' element_initializer_list ',' '}'
    ;

element_initializer_list
    : element_initializer (',' element_initializer)*
    ;

element_initializer
    : non_assignment_expression
    | '{' expression_list '}'
    ;

expression_list
    : expression
    | expression_list ',' expression
    ;

Un inicializador de colección consta de una secuencia de inicializadores de elementos, entre { tokens y } separados por comas. Cada inicializador de elemento especifica un elemento que se va a agregar al objeto de colección que se va a inicializar y consta de una lista de expresiones entre { tokens y } separados por comas. Un inicializador de elemento de expresión única se puede escribir sin llaves, pero no puede ser una expresión de asignación, para evitar ambigüedad con inicializadores de miembro. La non_assignment_expression producción se define en §12.22.

Ejemplo: a continuación se muestra un ejemplo de una expresión de creación de objetos que incluye un inicializador de colección:

List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

ejemplo final

El objeto de colección al que se aplica un inicializador de colección debe ser de un tipo que implementa System.Collections.IEnumerable o se produce un error en tiempo de compilación. Para cada elemento especificado en orden de izquierda a derecha, se aplica la búsqueda de miembros normal para buscar un miembro denominado Add. Si el resultado de la búsqueda de miembros no es un grupo de métodos, se produce un error en tiempo de compilación. De lo contrario, la resolución de sobrecarga se aplica con la lista de expresiones del inicializador de elementos como la lista de argumentos y el inicializador de colección invoca el método resultante. Por lo tanto, el objeto de colección contendrá una instancia o método de extensión aplicable con el nombre Add de cada inicializador de elemento.

Ejemplo: a continuación se muestra una clase que representa un contacto con un nombre y una lista de números de teléfono, y la creación e inicialización de un List<Contact>:

public class Contact
{
    public string Name { get; set; }
    public List<string> PhoneNumbers { get; } = new List<string>();
}

class A
{
    static void M()
    {
        var contacts = new List<Contact>
        {
            new Contact
            {
                Name = "Chris Smith",
                PhoneNumbers = { "206-555-0101", "425-882-8080" }
            },
            new Contact
            {
                Name = "Bob Harris",
                PhoneNumbers = { "650-555-0199" }
            }
        };
    }
}

que tiene el mismo efecto que

var __clist = new List<Contact>();
Contact __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
__clist.Add(__c1);
Contact __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
__clist.Add(__c2);
var contacts = __clist;

donde __clist, __c1 y __c2 son variables temporales que de lo contrario son invisibles e inaccesibles.

ejemplo final

12.8.17.5 Expresiones de creación de matrices

Se usa un array_creation_expression para crear una nueva instancia de un array_type.

array_creation_expression
    : 'new' non_array_type '[' expression_list ']' rank_specifier*
      array_initializer?
    | 'new' array_type array_initializer
    | 'new' rank_specifier array_initializer
    ;

Una expresión de creación de matriz del primer formulario asigna una instancia de matriz del tipo que resulta de eliminar cada una de las expresiones individuales de la lista de expresiones.

Ejemplo: la expresión new int[10,20] de creación de matriz genera una instancia de matriz de tipo int[,]y la expresión de creación de matriz nueva int[10][,] genera una instancia de matriz de tipo int[][,]. ejemplo final

Cada expresión de la lista de expresiones debe ser de tipo int, uint, longo ulong, o implícitamente convertible a uno o varios de estos tipos. El valor de cada expresión determina la longitud de la dimensión correspondiente en la instancia de matriz recién asignada. Dado que la longitud de una dimensión de matriz debe ser no negativa, es un error en tiempo de compilación tener una expresión constante con un valor negativo, en la lista de expresiones.

Excepto en un contexto no seguro (§23.2), no se especifica el diseño de las matrices.

Si una expresión de creación de matriz del primer formulario incluye un inicializador de matriz, cada expresión de la lista de expresiones será una constante y las longitudes de rango y dimensión especificadas por la lista de expresiones coincidirán con las del inicializador de matriz.

En una expresión de creación de matriz del segundo o tercer formulario, el rango del tipo de matriz o especificador de rango coincidirá con el del inicializador de matriz. Las longitudes de dimensión individuales se deducen del número de elementos de cada uno de los niveles de anidamiento correspondientes del inicializador de matriz. Por lo tanto, la expresión del inicializador en la siguiente declaración

var a = new int[,] {{0, 1}, {2, 3}, {4, 5}};

corresponde exactamente a

var a = new int[3, 2] {{0, 1}, {2, 3}, {4, 5}};

Una expresión de creación de matriz del tercer formulario se conoce como expresión de creación de matriz con tipo implícito. Es similar al segundo formulario, excepto que el tipo de elemento de la matriz no se proporciona explícitamente, pero se determina como el mejor tipo común (§12.6.3.15) del conjunto de expresiones en el inicializador de matriz. Para una matriz multidimensional, es decir, una donde el rank_specifier contiene al menos una coma, este conjunto consta de todas las expresionesque se encuentran en array_initializeranidados.

Los inicializadores de matriz se describen más adelante en §17.7.

El resultado de evaluar una expresión de creación de matriz se clasifica como un valor, es decir, una referencia a la instancia de matriz recién asignada. El procesamiento en tiempo de ejecución de una expresión de creación de matriz consta de los pasos siguientes:

  • Las expresiones de longitud de dimensión del expression_list se evalúan en orden, de izquierda a derecha. Después de la evaluación de cada expresión, se realiza una conversión implícita (§10.2) a uno de los siguientes tipos: int, uint, long, ulong. El primer tipo de esta lista para el que existe una conversión implícita se elige. Si la evaluación de una expresión o la posterior conversión implícita provoca una excepción, no se evalúan más expresiones y no se ejecutan más pasos.
  • Los valores calculados para las longitudes de dimensión se validan, como se indica a continuación: Si uno o varios de los valores son menores que cero, se produce un System.OverflowException y no se ejecutan más pasos.
  • Se asigna una instancia de matriz con las longitudes de dimensión especificadas. Si no hay suficiente memoria disponible para asignar la nueva instancia, se produce una System.OutOfMemoryException excepción y no se ejecutan más pasos.
  • Todos los elementos de la nueva instancia de matriz se inicializan en sus valores predeterminados (§9.3).
  • Si la expresión de creación de matriz contiene un inicializador de matriz, cada expresión del inicializador de matriz se evalúa y se asigna a su elemento de matriz correspondiente. Las evaluaciones y asignaciones se realizan en el orden en que las expresiones se escriben en el inicializador de matriz; es decir, los elementos se inicializan en orden de índice creciente, con la dimensión más a la derecha aumentando primero. Si la evaluación de una expresión determinada o la asignación posterior al elemento de matriz correspondiente provoca una excepción, no se inicializan elementos adicionales (y los elementos restantes tendrán sus valores predeterminados).

Una expresión de creación de matriz permite crear instancias de una matriz con elementos de un tipo de matriz, pero los elementos de dicha matriz se inicializarán manualmente.

Ejemplo: La instrucción

int[][] a = new int[100][];

crea una matriz unidimensional con 100 elementos de tipo int[]. El valor inicial de cada elemento es null. No es posible que la misma expresión de creación de matrices cree instancias también de las sub arrays y la instrucción .

int[][] a = new int[100][5]; // Error

produce un error en tiempo de compilación. La creación de instancias de las sub arrays se puede realizar manualmente, como en

int[][] a = new int[100][];
for (int i = 0; i < 100; i++)
{
    a[i] = new int[5];
}

ejemplo final

Nota: Cuando una matriz de matrices tiene una forma "rectangular", es decir, cuando las sub arrays son todas de la misma longitud, es más eficaz usar una matriz multidimensional. En el ejemplo anterior, la creación de instancias de la matriz de matrices crea 101 objetos: una matriz externa y 100 sub matrices. Por el contrario,

int[,] a = new int[100, 5];

crea solo un solo objeto, una matriz bidimensional y realiza la asignación en una sola instrucción.

nota final

Ejemplo: a continuación se muestran ejemplos de expresiones de creación de matrices con tipo implícito:

var a = new[] { 1, 10, 100, 1000 };                     // int[]
var b = new[] { 1, 1.5, 2, 2.5 };                       // double[]
var c = new[,] { { "hello", null }, { "world", "!" } }; // string[,]
var d = new[] { 1, "one", 2, "two" };                   // Error

La última expresión produce un error en tiempo de compilación porque ni int se string puede convertir implícitamente en el otro, por lo que no hay ningún tipo más común. En este caso, se debe usar una expresión de creación de matriz con tipo explícito, por ejemplo, especificando el tipo que se va a ser object[]. Como alternativa, uno de los elementos se puede convertir en un tipo base común, que luego se convertirá en el tipo de elemento inferido.

ejemplo final

Las expresiones de creación de matrices con tipo implícito se pueden combinar con inicializadores de objetos anónimos (§12.8.17.7) para crear estructuras de datos con tipo anónimo.

Ejemplo:

var contacts = new[]
{
    new
    {
        Name = "Chris Smith",
        PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
    },
    new 
    {
        Name = "Bob Harris",
       PhoneNumbers = new[] { "650-555-0199" }
    }
};

ejemplo final

12.8.17.6 Expresiones de creación de delegados

Se usa un delegate_creation_expression para obtener una instancia de un delegate_type.

delegate_creation_expression
    : 'new' delegate_type '(' expression ')'
    ;

El argumento de una expresión de creación de delegados debe ser un grupo de métodos, una función anónima o un valor del tipo dynamic en tiempo de compilación o un delegate_type. Si el argumento es un grupo de métodos, identifica el método y, para un método de instancia, el objeto para el que se va a crear un delegado. Si el argumento es una función anónima, define directamente los parámetros y el cuerpo del método del destino delegado. Si el argumento es un valor, identifica una instancia de delegado de la que se va a crear una copia.

Si la expresión tiene el tipo dynamicen tiempo de compilación , el delegate_creation_expression se enlaza dinámicamente (§12.8.17.6) y las reglas siguientes se aplican en tiempo de ejecución mediante el tipo en tiempo de ejecución de la expresión. De lo contrario, las reglas se aplican en tiempo de compilación.

El procesamiento en tiempo de enlace de un delegate_creation_expression del formulario nuevo D(E), donde D es un delegate_type y E es una expresión, consta de los pasos siguientes:

  • Si E es un grupo de métodos, la expresión de creación del delegado se procesa de la misma manera que una conversión de grupo de métodos (§10.8) de E a D.

  • Si E es una función anónima, la expresión de creación del delegado se procesa de la misma manera que una conversión de función anónima (§10.7) de E a D.

  • Si E es un valor, E será compatible (§20.2) con Dy el resultado es una referencia a un delegado recién creado con una lista de invocación de entrada única que invoca E.

El procesamiento en tiempo de ejecución de un delegate_creation_expression del formulario nuevo D(E), donde D es un delegate_type y E es una expresión, consta de los pasos siguientes:

  • Si E es un grupo de métodos, la expresión de creación del delegado se evalúa como una conversión de grupo de métodos (§10.8) de E a D.
  • Si E es una función anónima, la creación del delegado se evalúa como una conversión de función anónima de E a D (§10.7).
  • Si E es un valor de un delegate_type:
    • E se evalúa. Si esta evaluación provoca una excepción, no se ejecutan pasos adicionales.
    • Si el valor de E es null, se produce una System.NullReferenceException excepción y no se ejecutan pasos adicionales.
    • Se asigna una nueva instancia del tipo D de delegado. Si no hay suficiente memoria disponible para asignar la nueva instancia, se produce una System.OutOfMemoryException excepción y no se ejecutan más pasos.
    • La nueva instancia de delegado se inicializa con una lista de invocación de entrada única que invoca E.

La lista de invocación de un delegado se determina cuando se crea una instancia del delegado y, a continuación, permanece constante durante toda la duración del delegado. En otras palabras, no es posible cambiar las entidades invocables de destino de un delegado una vez que se haya creado.

Nota: Recuerde que cuando se combinan dos delegados o se quita uno de otro, se produce un nuevo delegado; no ha cambiado su contenido. nota final

No es posible crear un delegado que haga referencia a una propiedad, indexador, operador definido por el usuario, constructor de instancia, finalizador o constructor estático.

Ejemplo: Como se ha descrito anteriormente, cuando se crea un delegado a partir de un grupo de métodos, la lista de parámetros y el tipo de valor devuelto del delegado determinan cuáles de los métodos sobrecargados se van a seleccionar. En el ejemplo

delegate double DoubleFunc(double x);

class A
{
    DoubleFunc f = new DoubleFunc(Square);

    static float Square(float x) => x * x;
    static double Square(double x) => x * x;
}

el A.f campo se inicializa con un delegado que hace referencia al segundo Square método porque ese método coincide exactamente con la lista de parámetros y el tipo de valor devuelto de DoubleFunc. Si el segundo Square método no estuviera presente, se habría producido un error en tiempo de compilación.

ejemplo final

12.8.17.7 Expresiones de creación de objetos anónimos

Un anonymous_object_creation_expression se usa para crear un objeto de un tipo anónimo.

anonymous_object_creation_expression
    : 'new' anonymous_object_initializer
    ;

anonymous_object_initializer
    : '{' member_declarator_list? '}'
    | '{' member_declarator_list ',' '}'
    ;

member_declarator_list
    : member_declarator (',' member_declarator)*
    ;

member_declarator
    : simple_name
    | member_access
    | null_conditional_projection_initializer
    | base_access
    | identifier '=' expression
    ;

Un inicializador de objeto anónimo declara un tipo anónimo y devuelve una instancia de ese tipo. Un tipo anónimo es un tipo de clase sin nombre que hereda directamente de object. Los miembros de un tipo anónimo son una secuencia de propiedades de solo lectura inferidas del inicializador de objetos anónimos que se usa para crear una instancia del tipo. En concreto, un inicializador de objeto anónimo del formulario

new {p₁ = e₁ , p Pudin = e Expulsado ,pv = ev }

declara un tipo anónimo del formulario

class __Anonymous1
{
    private readonly «T1» «f1»;
    private readonly «T2» «f2»;
    ...
    private readonly «Tn» «fn»;

    public __Anonymous1(«T1» «a1», «T2» «a2»,..., «Tn» «an»)
    {
        «f1» = «a1»;
        «f2» = «a2»;
        ...
        «fn» = «an»;
    }

    public «T1» «p1» { get { return «f1»; } }
    public «T2» «p2» { get { return «f2»; } }
    ...
    public «Tn» «pn» { get { return «fn»; } }
    public override bool Equals(object __o) { ... }
    public override int GetHashCode() { ... }
}

donde cada «Tx» es el tipo de la expresión correspondiente «ex». La expresión utilizada en un member_declarator tendrá un tipo . Por lo tanto, es un error en tiempo de compilación para que una expresión de un member_declarator sea null o una función anónima.

El compilador genera automáticamente los nombres de un tipo anónimo y del parámetro en su Equals método y no se puede hacer referencia a él en el texto del programa.

Dentro del mismo programa, dos inicializadores de objetos anónimos que especifican una secuencia de propiedades de los mismos nombres y tipos en tiempo de compilación en el mismo orden producirán instancias del mismo tipo anónimo.

Ejemplo: en el ejemplo

var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;

Se permite la asignación en la última línea porque p1 y p2 son del mismo tipo anónimo.

ejemplo final

Los Equals métodos y GetHashcode de los tipos anónimos invalidan los métodos heredados de objecty se definen en términos de y de las Equals GetHashcode propiedades, de modo que dos instancias del mismo tipo anónimo sean iguales si y solo si todas sus propiedades son iguales.

Un declarador de miembro se puede abreviar a un nombre simple (§12.8.4), un acceso de miembro (§12.8.7), un inicializador de proyección condicional NULL §12.8.8 o un acceso base (§12.8.15). Esto se denomina inicializador de proyección y es abreviado para una declaración de y asignación a una propiedad con el mismo nombre. En concreto, declaradores de miembros de los formularios

«identifier», «expr» . «identifier» y «expr» ? . «identifier»

son exactamente equivalentes a lo siguiente, respectivamente:

«identifer» = «identifier», «identifier» = «expr» . «identifier» y «identifier» = «expr» ? . «identifier»

Por lo tanto, en un inicializador de proyección, el identificador selecciona el valor y el campo o la propiedad a los que se asigna el valor. Intuitivamente, un inicializador de proyección proyecta no solo un valor, sino también el nombre del valor.

12.8.18 Operador typeof

El typeof operador se usa para obtener el System.Type objeto de un tipo.

typeof_expression
    : 'typeof' '(' type ')'
    | 'typeof' '(' unbound_type_name ')'
    | 'typeof' '(' 'void' ')'
    ;

unbound_type_name
    : identifier generic_dimension_specifier?
    | identifier '::' identifier generic_dimension_specifier?
    | unbound_type_name '.' identifier generic_dimension_specifier?
    ;

generic_dimension_specifier
    : '<' comma* '>'
    ;

comma
    : ','
    ;

La primera forma de typeof_expression consta de una typeof palabra clave seguida de un tipo entre paréntesis. El resultado de una expresión de este formulario es el System.Type objeto del tipo indicado. Solo hay un System.Type objeto para cualquier tipo determinado. Esto significa que para un tipo T, typeof(T) == typeof(T) siempre es true. El tipo no puede ser dynamic.

La segunda forma de typeof_expression consta de una typeof palabra clave seguida de un unbound_type_name entre paréntesis.

Nota: Un unbound_type_name es muy similar a un type_name (§7.8), excepto que un unbound_type_name contiene generic_dimension_specifiers donde un type_name contiene type_argument_lists. nota final

Cuando el operando de un typeof_expression es una secuencia de tokens que satisface las gramáticas de unbound_type_name y type_name, es decir, cuando no contiene ni un generic_dimension_specifier ni un type_argument_list, la secuencia de tokens se considera una type_name. El significado de un unbound_type_name se determina de la siguiente manera:

  • Convierta la secuencia de tokens en un type_name reemplazando cada generic_dimension_specifier por un type_argument_list que tenga el mismo número de comas y la palabra clave object que cada type_argument.
  • Evalúe el type_name resultante, al tiempo que omite todas las restricciones de parámetros de tipo.
  • El unbound_type_name se resuelve en el tipo genérico sin enlazar asociado al tipo construido resultante (§8.4).

Se trata de un error para que el nombre de tipo sea un tipo de referencia que acepta valores NULL.

El resultado del typeof_expression es el System.Type objeto del tipo genérico sin enlazar resultante.

La tercera forma de typeof_expression consta de una typeof palabra clave seguida de una palabra clave entre paréntesis void . El resultado de una expresión de este formulario es el System.Type objeto que representa la ausencia de un tipo. El objeto de tipo devuelto por typeof(void) es distinto del objeto de tipo devuelto para cualquier tipo.

Nota: Este objeto especial System.Type es útil en las bibliotecas de clases que permiten la reflexión en los métodos del lenguaje, donde esos métodos desean tener una manera de representar el tipo de valor devuelto de cualquier método, incluidos void los métodos, con una instancia de System.Type. nota final

El typeof operador se puede usar en un parámetro de tipo. Se trata de un error en tiempo de compilación si se sabe que el nombre del tipo es un tipo de referencia que acepta valores NULL. El resultado es el System.Type objeto del tipo en tiempo de ejecución enlazado al parámetro type. Si el tipo en tiempo de ejecución es un tipo de referencia que acepta valores NULL, el resultado es el tipo de referencia que no acepta valores NULL correspondiente. El typeof operador también se puede usar en un tipo construido o en un tipo genérico no enlazado (§8.4.4). El System.Type objeto de un tipo genérico sin enlazar no es el mismo que el System.Type objeto del tipo de instancia (§15.3.2). El tipo de instancia siempre es un tipo construido cerrado en tiempo de ejecución, por lo que su System.Type objeto depende de los argumentos de tipo en tiempo de ejecución en uso. El tipo genérico no enlazado, por otro lado, no tiene argumentos de tipo y produce el mismo System.Type objeto independientemente de los argumentos de tipo en tiempo de ejecución.

Ejemplo: El ejemplo

class X<T>
{
    public static void PrintTypes()
    {
        Type[] t =
        {
            typeof(int),
            typeof(System.Int32),
            typeof(string),
            typeof(double[]),
            typeof(void),
            typeof(T),
            typeof(X<T>),
            typeof(X<X<T>>),
            typeof(X<>)
        };
        for (int i = 0; i < t.Length; i++)
        {
            Console.WriteLine(t[i]);
        }
    }
}

class Test
{
    static void Main()
    {
        X<int>.PrintTypes();
    }
}

genera la salida siguiente:

System.Int32
System.Int32
System.String
System.Double[]
System.Void
System.Int32
X`1[System.Int32]
X`1[X`1[System.Int32]]
X`1[T]

Tenga en cuenta que int y System.Int32 son el mismo tipo. El resultado de typeof(X<>) no depende del argumento type, pero el resultado de typeof(X<T>) sí.

ejemplo final

12.8.19 Operador sizeof

El sizeof operador devuelve el número de bytes de 8 bits ocupados por una variable de un tipo determinado. El tipo especificado como operando para sizeof será un unmanaged_type (§8.8).

sizeof_expression
    : 'sizeof' '(' unmanaged_type ')'
    ;

Para determinados tipos predefinidos, el sizeof operador produce un valor constante int como se muestra en la tabla siguiente:

Expression Resultado
sizeof(sbyte) 1
sizeof(byte) 1
sizeof(short) 2
sizeof(ushort) 2
sizeof(int) 4
sizeof(uint) 4
sizeof(long) 8
sizeof(ulong) 8
sizeof(char) 2
sizeof(float) 4
sizeof(double) 8
sizeof(bool) 1
sizeof(decimal) 16

Para un tipo Tde enumeración , el resultado de la expresión sizeof(T) es un valor constante igual al tamaño de su tipo subyacente, como se ha indicado anteriormente. Para todos los demás tipos de operando, el sizeof operador se especifica en §23.6.9.

12.8.20 Operadores activados y sin comprobar

Los checked operadores y unchecked se usan para controlar el contexto de comprobación de desbordamiento para operaciones y conversiones aritméticas de tipo entero.

checked_expression
    : 'checked' '(' expression ')'
    ;

unchecked_expression
    : 'unchecked' '(' expression ')'
    ;

El checked operador evalúa la expresión contenida en un contexto comprobado y el unchecked operador evalúa la expresión contenida en un contexto no comprobado. Un checked_expression o unchecked_expression corresponde exactamente a un parenthesized_expression (§12.8.5), excepto que la expresión contenida se evalúa en el contexto de comprobación de desbordamiento dado.

El contexto de comprobación de desbordamiento también se puede controlar a través de las checked instrucciones y unchecked (§13.12).

Las siguientes operaciones se ven afectadas por el contexto de comprobación de desbordamiento establecido por los operadores y instrucciones comprobados y no comprobados:

  • Los operadores predefinidos ++ y -- (§12.8.16 y §12.9.6), cuando el operando es de un tipo entero o de enumeración.
  • Operador unario predefinido - (§12.9.3), cuando el operando es de un tipo entero.
  • Los operadores binarios predefinidos +, -, *y / (§12.10), cuando ambos operandos son de tipos enteros o de enumeración.
  • Conversiones numéricas explícitas (§10.3.2) de un tipo entero o de enumeración a otro tipo entero o de enumeración, o de float o double a un tipo entero o de enumeración.

Cuando una de las operaciones anteriores produce un resultado demasiado grande para representar en el tipo de destino, el contexto en el que se realiza la operación controla el comportamiento resultante:

  • En un checked contexto, si la operación es una expresión constante (§12.23), se produce un error en tiempo de compilación. De lo contrario, cuando se realiza la operación en tiempo de ejecución, se produce una System.OverflowException excepción .
  • En un unchecked contexto, el resultado se trunca descartando los bits de orden alto que no caben en el tipo de destino.

En el caso de expresiones que no son constantes (§12.23) (expresiones que se evalúan en tiempo de ejecución) que no están incluidas en ninguna checked instrucción o unchecked operador, el contexto de comprobación de desbordamiento predeterminado está desactivado, a menos que los factores externos (como modificadores del compilador y la configuración del entorno de ejecución) llamen a la evaluación comprobada.

Para las expresiones constantes (§12.23) (expresiones que se pueden evaluar completamente en tiempo de compilación), siempre se comprueba el contexto de comprobación de desbordamiento predeterminado. A menos que una expresión constante se coloque explícitamente en un unchecked contexto, los desbordamientos que se producen durante la evaluación en tiempo de compilación de la expresión siempre provocan errores en tiempo de compilación.

El cuerpo de una función anónima no se ve afectado por checked contextos o unchecked en los que se produce la función anónima.

Ejemplo: en el código siguiente

class Test
{
    static readonly int x = 1000000;
    static readonly int y = 1000000;

    static int F() => checked(x * y);    // Throws OverflowException
    static int G() => unchecked(x * y);  // Returns -727379968
    static int H() => x * y;             // Depends on default
}

no se notifican errores en tiempo de compilación, ya que ninguna de las expresiones se puede evaluar en tiempo de compilación. En tiempo de ejecución, el F método produce un System.OverflowExceptiony el G método devuelve –727379968 (los 32 bits inferiores del resultado fuera del intervalo). El comportamiento del H método depende del contexto de comprobación de desbordamiento predeterminado para la compilación, pero es el mismo F que o el mismo que G.

ejemplo final

Ejemplo: en el código siguiente

class Test
{
    const int x = 1000000;
    const int y = 1000000;

    static int F() => checked(x * y);    // Compile-time error, overflow
    static int G() => unchecked(x * y);  // Returns -727379968
    static int H() => x * y;             // Compile-time error, overflow
}

los desbordamientos que se producen al evaluar las expresiones constantes en F y H hacen que se notifiquen errores en tiempo de compilación porque las expresiones se evalúan en un checked contexto. También se produce un desbordamiento al evaluar la expresión constante en G, pero dado que la evaluación tiene lugar en un unchecked contexto, no se notifica el desbordamiento.

ejemplo final

Los checked operadores y unchecked solo afectan al contexto de comprobación de desbordamiento para las operaciones que están textualmente contenidas en los tokens "(" y ")". Los operadores no tienen ningún efecto en los miembros de función que se invocan como resultado de evaluar la expresión contenida.

Ejemplo: en el código siguiente

class Test
{
    static int Multiply(int x, int y) => x * y;

    static int F() => checked(Multiply(1000000, 1000000));
}

El uso de en F no afecta a la evaluación de x * y en Multiply, por lo que x * y se evalúa en el contexto de comprobación de checked desbordamiento predeterminado.

ejemplo final

El unchecked operador es cómodo al escribir constantes de los tipos enteros firmados en notación hexadecimal.

Ejemplo:

class Test
{
    public const int AllBits = unchecked((int)0xFFFFFFFF);
    public const int HighBit = unchecked((int)0x80000000);
}

Las dos constantes hexadecimales anteriores son de tipo uint. Dado que las constantes están fuera del int intervalo, sin el unchecked operador , las conversiones para int generarían errores en tiempo de compilación.

ejemplo final

Nota: Los checked operadores y unchecked las instrucciones permiten a los programadores controlar determinados aspectos de algunos cálculos numéricos. Sin embargo, el comportamiento de algunos operadores numéricos depende de los tipos de datos de sus operandos. Por ejemplo, la multiplicación de dos decimales siempre da como resultado una excepción al desbordamiento incluso dentro de una construcción desactivada explícitamente. De forma similar, la multiplicación de dos floats nunca produce una excepción en el desbordamiento incluso dentro de una construcción activada explícitamente. Además, otros operadores nunca se ven afectados por el modo de comprobación, ya sea predeterminado o explícito. nota final

12.8.21 Expresiones de valor predeterminado

Se usa una expresión de valor predeterminada para obtener el valor predeterminado (§9.3) de un tipo.

default_value_expression
    : explictly_typed_default
    | default_literal
    ;

explictly_typed_default
    : 'default' '(' type ')'
    ;

default_literal
    : 'default'
    ;

Un default_literal representa un valor predeterminado (§9.3). No tiene un tipo, pero se puede convertir a cualquier tipo a través de una conversión literal predeterminada (§10.2.16).

El resultado de un default_value_expression es el valor predeterminado (§9.3) del tipo explícito en un explictly_typed_default o el tipo de destino de la conversión de un default_value_expression.

Un default_value_expression es una expresión constante (§12.23) si el tipo es uno de los siguientes:

  • un tipo de referencia
  • un parámetro de tipo que se sabe que es un tipo de referencia (§8.2);
  • uno de los siguientes tipos de valor: sbyte, byte, ushortlongulongcharfloatuintshortint, double, , , o bool,decimal
  • cualquier tipo de enumeración.

Asignación de pila 12.8.22

Una expresión de asignación de pila asigna un bloque de memoria de la pila de ejecución. La pila de ejecución es un área de memoria donde se almacenan las variables locales. La pila de ejecución no forma parte del montón administrado. La memoria usada para el almacenamiento de variables locales se recupera automáticamente cuando la función actual devuelve.

Las reglas de contexto seguro para una expresión de asignación de pila se describen en §16.4.12.7.

stackalloc_expression
    : 'stackalloc' unmanaged_type '[' expression ']'
    | 'stackalloc' unmanaged_type? '[' constant_expression? ']'
      stackalloc_initializer
    ;

stackalloc_initializer
     : '{' stackalloc_initializer_element_list '}'
     ;

stackalloc_initializer_element_list
     : stackalloc_element_initializer (',' stackalloc_element_initializer)* ','?
     ;
    
stackalloc_element_initializer
    : expression
    ;

Solo se permite un stackalloc_expression en dos contextos:

  1. Expresión de inicialización, , Ede un local_variable_declaration (§13.6.2); y
  2. Expresión de operando derecho, E, de una asignación simple (§12.21.2) que se produce como un expression_statement (§13.7)

En ambos contextos, el stackalloc_expression solo se permite que se produzca como:

El unmanaged_type (§8.8) indica el tipo de los elementos que se almacenarán en la ubicación recién asignada y la expresión indica el número de estos elementos. En conjunto, especifican el tamaño de asignación necesario. El tipo de expresión se podrá convertir implícitamente en el tipo int.

Dado que el tamaño de una asignación de pila no puede ser negativo, se trata de un error en tiempo de compilación para especificar el número de elementos como un constant_expression que se evalúa como un valor negativo.

En tiempo de ejecución si el número de elementos que se van a asignar es un valor negativo, el comportamiento no está definido. Si es cero, no se realiza ninguna asignación y el valor devuelto es definido por la implementación. Si no hay suficiente memoria disponible para asignar los elementos, se produce una System.StackOverflowException excepción .

Cuando hay un stackalloc_initializer presente:

  • Si se omite unmanaged_type , se deduce que sigue las reglas del mejor tipo común (§12.6.3.15) para el conjunto de stackalloc_element_initializers.
  • Si constant_expression se omite, se deduce que es el número de stackalloc_element_initializers.
  • Si constant_expression está presente, será igual al número de stackalloc_element_initializers.

Cada stackalloc_element_initializer tendrá una conversión implícita a unmanaged_type (§10.2). El stackalloc_element_initializerinicializar elementos en la memoria asignada en orden creciente, empezando por el elemento en el índice cero. En ausencia de un stackalloc_initializer, el contenido de la memoria recién asignada no está definido.

El resultado de un stackalloc_expression es una instancia de tipo Span<T>, donde T es el unmanaged_type:

  • Span<T> (§C.3) es un tipo de estructura ref (§16.2.3), que presenta un bloque de memoria, aquí el bloque asignado por el stackalloc_expression, como una colección indexable de elementos con tipo (T).
  • La propiedad del Length resultado devuelve el número de elementos asignados.
  • El indexador del resultado (§15.9) devuelve un variable_reference (§9.5) a un elemento del bloque asignado y se comprueba el intervalo.

Nota: Al producirse en código no seguro, el resultado de un stackalloc_expression puede ser de un tipo diferente, consulte (§23.9). nota final

No se permiten inicializadores de asignación de pila en catch bloques o finally (§13.11).

Nota: No hay ninguna manera de liberar explícitamente memoria asignada mediante stackalloc. nota final

Todos los bloques de memoria asignados a la pila creados durante la ejecución de un miembro de función se descartan automáticamente cuando ese miembro de función devuelve.

Excepto para el stackalloc operador, C# no proporciona construcciones predefinidas para administrar la memoria no recopilada sin elementos no utilizados. Normalmente, estos servicios se proporcionan mediante la compatibilidad con bibliotecas de clases o se importan directamente desde el sistema operativo subyacente.

Ejemplo:

// Memory uninitialized
Span<int> span1 = stackalloc int[3];
// Memory initialized
Span<int> span2 = stackalloc int[3] { -10, -15, -30 };
// Type int is inferred
Span<int> span3 = stackalloc[] { 11, 12, 13 };
// Error; result is int*, not allowed in a safe context
var span4 = stackalloc[] { 11, 12, 13 };
// Error; no conversion from Span<int> to Span<long>
Span<long> span5 = stackalloc[] { 11, 12, 13 };
// Converts 11 and 13, and returns Span<long> 
Span<long> span6 = stackalloc[] { 11, 12L, 13 };
// Converts all and returns Span<long>
Span<long> span7 = stackalloc long[] { 11, 12, 13 };
// Implicit conversion of Span<T>
ReadOnlySpan<int> span8 = stackalloc int[] { 10, 22, 30 };
// Implicit conversion of Span<T>
Widget<double> span9 = stackalloc double[] { 1.2, 5.6 };

public class Widget<T>
{
    public static implicit operator Widget<T>(Span<double> sp) { return null; }
}

En el caso de span8, stackalloc da como resultado un Span<int>, que un operador ReadOnlySpan<int>implícito convierte en . Del mismo modo, para span9, el resultado Span<double> se convierte en el tipo Widget<double> definido por el usuario mediante la conversión, como se muestra. ejemplo final

12.8.23 El operador nameof

Un nameof_expression se usa para obtener el nombre de una entidad de programa como una cadena constante.

nameof_expression
    : 'nameof' '(' named_entity ')'
    ;
    
named_entity
    : named_entity_target ('.' identifier type_argument_list?)*
    ;
    
named_entity_target
    : simple_name
    | 'this'
    | 'base'
    | predefined_type 
    | qualified_alias_member
    ;

Dado que nameof no es una palabra clave, un nameof_expression siempre es ambiguo sintácticamente con una invocación del nombre nameofsimple . Por motivos de compatibilidad, si una búsqueda de nombres (§12.8.4) del nombre nameof se realiza correctamente, la expresión se trata como una invocation_expression , independientemente de si la invocación es válida. De lo contrario, es un nameof_expression.

Las búsquedas de acceso a miembros y nombres simples se realizan en el named_entity en tiempo de compilación, siguiendo las reglas descritas en §12.8.4 y §12.8.7. Sin embargo, cuando la búsqueda descrita en §12.8.4 y §12.8.7 da como resultado un error porque se encontró un miembro de instancia en un contexto estático, un nameof_expression no produce este error.

Se trata de un error en tiempo de compilación para que un named_entity designar un grupo de métodos tenga un type_argument_list. Es un error en tiempo de compilación para que un named_entity_target tenga el tipo dynamic.

Un nameof_expression es una expresión constante de tipo stringy no tiene ningún efecto en tiempo de ejecución. En concreto, su named_entity no se evalúa y se omite para los fines del análisis de asignación definitiva (§9.4.4.22). Su valor es el último identificador del named_entity antes del type_argument_list final opcional, transformado de la siguiente manera:

  • Se quita el prefijo "@", si se usa.
  • Cada unicode_escape_sequence se transforma en su carácter Unicode correspondiente.
  • Se quita cualquier formatting_characters .

Estas son las mismas transformaciones aplicadas en §6.4.3 al probar la igualdad entre identificadores.

Ejemplo: a continuación se muestran los resultados de varias nameof expresiones, suponiendo que un tipo List<T> genérico declarado en el System.Collections.Generic espacio de nombres:

using TestAlias = System.String;

class Program
{
    static void Main()
    {
        var point = (x: 3, y: 4);

        string n1 = nameof(System);                      // "System"
        string n2 = nameof(System.Collections.Generic);  // "Generic"
        string n3 = nameof(point);                       // "point"
        string n4 = nameof(point.x);                     // "x"
        string n5 = nameof(Program);                     // "Program"
        string n6 = nameof(System.Int32);                // "Int32"
        string n7 = nameof(TestAlias);                   // "TestAlias"
        string n8 = nameof(List<int>);                   // "List"
        string n9 = nameof(Program.InstanceMethod);      // "InstanceMethod"
        string n10 = nameof(Program.GenericMethod);      // "GenericMethod"
        string n11 = nameof(Program.NestedClass);        // "NestedClass"

        // Invalid
        // string x1 = nameof(List<>);            // Empty type argument list
        // string x2 = nameof(List<T>);           // T is not in scope
        // string x3 = nameof(GenericMethod<>);   // Empty type argument list
        // string x4 = nameof(GenericMethod<T>);  // T is not in scope
        // string x5 = nameof(int);               // Keywords not permitted
        // Type arguments not permitted for method group
        // string x6 = nameof(GenericMethod<Program>);
    }

    void InstanceMethod() { }

    void GenericMethod<T>()
    {
        string n1 = nameof(List<T>); // "List"
        string n2 = nameof(T);       // "T"
    }

    class NestedClass { }
}

Las partes potencialmente sorprendentes de este ejemplo son la resolución de nameof(System.Collections.Generic) solo "Genérico" en lugar del espacio de nombres completo, y de nameof(TestAlias) a "TestAlias" en lugar de "String". ejemplo final

12.8.24 Expresiones de método anónimo

Un anonymous_method_expression es una de las dos maneras de definir una función anónima. Estos se describen más adelante en §12.19.

12.9 Operadores unarios

12.9.1 General

Los +operadores , -, ! (negación lógica §12.9.4 solo), ~, ++, --cast y await se denominan operadores unarios.

Nota: El operador postfix null-forgiving (§12.8.9), , !debido a su naturaleza de tiempo de compilación y no sobrecargable, se excluye de la lista anterior. nota final

unary_expression
    : primary_expression
    | '+' unary_expression
    | '-' unary_expression
    | logical_negation_operator unary_expression
    | '~' unary_expression
    | pre_increment_expression
    | pre_decrement_expression
    | cast_expression
    | await_expression
    | pointer_indirection_expression    // unsafe code support
    | addressof_expression              // unsafe code support
    ;

pointer_indirection_expression (§23.6.2) y addressof_expression (§23.6.5) solo están disponibles en código no seguro (§23).

Si el operando de un unary_expression tiene el tipo dynamicen tiempo de compilación , se enlaza dinámicamente (§12.3.3). En este caso, el tipo en tiempo de compilación del unary_expression es dynamicy la resolución descrita a continuación tendrá lugar en tiempo de ejecución mediante el tipo en tiempo de ejecución del operando.

12.9.2 Operador Unary plus

Para una operación con el formato +x, la resolución de sobrecarga del operador unario (§12.4.4) se aplica para seleccionar una implementación específica del operador. El operando se convierte en el tipo de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador. Los operadores unarios más predefinidos son:

int operator +(int x);
uint operator +(uint x);
long operator +(long x);
ulong operator +(ulong x);
float operator +(float x);
double operator +(double x);
decimal operator +(decimal x);

Para cada uno de estos operadores, el resultado es simplemente el valor del operando.

Las formas levantadas (§12.4.8) de los operadores unarios predefinidos sin levantar más definidos anteriormente también están predefinidos.

12.9.3 Operador menos unario

Para una operación con el formato –x, la resolución de sobrecarga del operador unario (§12.4.4) se aplica para seleccionar una implementación específica del operador. El operando se convierte en el tipo de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador. Los operadores unarios menos predefinidos son:

  • Negación de enteros:

    int operator –(int x);
    long operator –(long x);
    

    El resultado se calcula restando X de cero. Si el valor de X es el valor representable más pequeño del tipo de operando (-2³¹ para int o -2⁶³ para long), la negación matemática de X no se puede representar dentro del tipo de operando. Si esto ocurre dentro de un checked contexto, se produce una System.OverflowException excepción ; si se produce dentro de un unchecked contexto, el resultado es el valor del operando y no se notifica el desbordamiento.

    Si el operando del operador de negación es de tipo uint, se convierte en el tipo longy el tipo del resultado es long. Una excepción es la regla que permite escribir el int valor −2147483648 (−2³¹) como un literal entero decimal (§6.4.5.3).

    Si el operando del operador de negación es de tipo ulong, se produce un error en tiempo de compilación. Una excepción es la regla que permite escribir el long valor −9223372036854775808 (−2⁶³) como un literal entero decimal (§6.4.5.3)

  • Negación de punto flotante:

    float operator –(float x);
    double operator –(double x);
    

    El resultado es el valor de X con su signo invertido. Si x es NaN, el resultado también NaNes .

  • Negación decimal:

    decimal operator –(decimal x);
    

    El resultado se calcula restando X de cero. La negación decimal es equivalente a usar el operador unario menos del tipo System.Decimal.

Las formas levantadas (§12.4.8) de los operadores unarios predefinidos sin levantar definidos anteriormente también están predefinidos.

Operador de negación lógica 12.9.4

Para una operación con el formato !x, la resolución de sobrecarga del operador unario (§12.4.4) se aplica para seleccionar una implementación específica del operador. El operando se convierte en el tipo de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador. Solo existe un operador de negación lógica predefinido:

bool operator !(bool x);

Este operador calcula la negación lógica del operando: si el operando es true, el resultado es false. Si el operando es false, el resultado es true.

Las formas levantadas (§12.4.8) del operador de negación lógica predefinido no elevado definido anteriormente también están predefinidos.

Nota: Los operadores de negación lógica de prefijo y postfijo nulo (§12.8.9), mientras que representados por el mismo token léxico (!), son distintos. nota final

Operador de complemento bit a bit 12.9.5

Para una operación con el formato ~x, la resolución de sobrecarga del operador unario (§12.4.4) se aplica para seleccionar una implementación específica del operador. El operando se convierte en el tipo de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador. Los operadores de complemento bit a bit predefinidos son:

int operator ~(int x);
uint operator ~(uint x);
long operator ~(long x);
ulong operator ~(ulong x);

Para cada uno de estos operadores, el resultado de la operación es el complemento bit a bit de x.

Cada tipo E de enumeración proporciona implícitamente el siguiente operador de complemento bit a bit:

E operator ~(E x);

El resultado de evaluar ~x, donde X es una expresión de un tipo E de enumeración con un tipo Usubyacente, es exactamente el mismo que evaluar (E)(~(U)x), excepto que la conversión a E siempre se realiza como si estuviera en un unchecked contexto (§12.8.20).

Las formas levantadas (§12.4.8) de los operadores predefinidos de complemento bit a bit predefinidos definidos anteriormente también están predefinidos.

12.9.6 Operadores de incremento y decremento de prefijo

pre_increment_expression
    : '++' unary_expression
    ;

pre_decrement_expression
    : '--' unary_expression
    ;

El operando de una operación de incremento o disminución de prefijo será una expresión clasificada como una variable, un acceso a propiedades o un acceso de indizador. El resultado de la operación es un valor del mismo tipo que el operando.

Si el operando de una operación de incremento o decremento de prefijo es un acceso de propiedad o indexador, la propiedad o el indexador tendrán un descriptor de acceso get y set. Si no es así, se produce un error en tiempo de enlace.

La resolución de sobrecarga del operador unario (§12.4.4) se aplica para seleccionar una implementación específica del operador. Existen operadores predefinidos ++ y -- para los siguientes tipos: sbyte, byte, ushortintuintlongshort, charfloatulongdouble, decimaly cualquier tipo de enumeración. Los operadores predefinidos ++ devuelven el valor generado agregando 1 al operando y los operadores predefinidos -- devuelven el valor generado restando 1 del operando. En un checked contexto, si el resultado de esta suma o resta está fuera del intervalo del tipo de resultado y el tipo de resultado es un tipo entero o tipo de enumeración, se produce una System.OverflowException excepción .

Habrá una conversión implícita del tipo de valor devuelto del operador unario seleccionado al tipo del unary_expression; de lo contrario, se produce un error en tiempo de compilación.

El procesamiento en tiempo de ejecución de una operación de incremento o disminución de prefijo del formulario ++x o --x consta de los pasos siguientes:

  • Si x se clasifica como una variable:
    • x se evalúa para generar la variable.
    • El valor de x se convierte en el tipo de operando del operador seleccionado y el operador se invoca con este valor como argumento.
    • El valor devuelto por el operador se convierte en el tipo de x. El valor resultante se almacena en la ubicación dada por la evaluación de x.
    • y se convierte en el resultado de la operación.
  • Si x se clasifica como acceso de propiedad o indexador:
    • La expresión de instancia (si x no statices ) y la lista de argumentos (si x es un acceso de indexador) asociada x a se evalúan y los resultados se usan en las invocaciones de descriptor de acceso get y set posteriores.
    • Se invoca el descriptor de acceso get de x .
    • El valor devuelto por el descriptor de acceso get se convierte en el tipo de operando del operador seleccionado y el operador se invoca con este valor como argumento.
    • El valor devuelto por el operador se convierte en el tipo de x. El descriptor de acceso set de x se invoca con este valor como argumento value.
    • Este valor también se convierte en el resultado de la operación.

Los ++ operadores y -- también admiten la notación postfijo (§12.8.16). El resultado de o x-- es el valor de x++ antes de x la operación, mientras que el resultado de ++x o --x es el valor de después de x la operación. En cualquier caso, x sí mismo tiene el mismo valor después de la operación.

Se puede invocar una implementación de operador ++ o operador -- mediante notación postfijo o prefijo. No es posible tener implementaciones de operador independientes para las dos notaciones.

También se predefinin las formas levantadas (§12.4.8) de los operadores predefinidos de incremento y decremento predefinidos definidos anteriormente.

12.9.7 Expresiones cast

Un cast_expression se usa para convertir explícitamente una expresión en un tipo determinado.

cast_expression
    : '(' type ')' unary_expression
    ;

Un cast_expression del formulario (T)E, donde T es un tipo y E es un unary_expression, realiza una conversión explícita (§10.3) del valor de E al tipo T. Si no existe ninguna conversión explícita de E a T, se produce un error en tiempo de enlace. De lo contrario, el resultado es el valor generado por la conversión explícita. El resultado siempre se clasifica como un valor, incluso si E denota una variable.

La gramática de una cast_expression conduce a ciertas ambigüedades sintácticas.

Ejemplo: La expresión (x)–y puede interpretarse como un cast_expression (una conversión de –y al tipo x) o como un additive_expression combinado con un parenthesized_expression (que calcula el valor x – y). ejemplo final

Para resolver cast_expression ambigüedades, existe la siguiente regla: una secuencia de uno o varios tokens (§6.4) entre paréntesis se considera el inicio de un cast_expression solo si se cumple al menos uno de los siguientes elementos:

  • La secuencia de tokens es la gramática correcta para un tipo, pero no para una expresión.
  • La secuencia de tokens es la gramática correcta para un tipo y el token inmediatamente después de los paréntesis de cierre es el token "~", el token "!", el token "(", un identificador (§6.4.3), un literal (§6.4.5) o cualquier palabra clave (§6.4.4) excepto as y is.

El término "gramática correcta" anterior significa únicamente que la secuencia de tokens se ajustará a la producción gramatical concreta. En concreto, no tiene en cuenta el significado real de ningún identificador constituyente.

Ejemplo: Si x y y son identificadores, es x.y la gramática correcta para un tipo, incluso si x.y realmente no denota un tipo. ejemplo final

Nota: A partir de la regla de desambiguación, sigue que, si x y y son identificadores, (x)y, (x)(y)y (x)(-y) son cast_expressions, pero (x)-y no es, incluso si x identifica un tipo. Sin embargo, si x es una palabra clave que identifica un tipo predefinido (como int), los cuatro formularios se cast_expressions (porque tal palabra clave no pudo ser posiblemente una expresión por sí misma). nota final

12.9.8 Expresiones await

12.9.8.1 General

El await operador se usa para suspender la evaluación de la función asincrónica envolvente hasta que se haya completado la operación asincrónica representada por el operando.

await_expression
    : 'await' unary_expression
    ;

Solo se permite un await_expression en el cuerpo de una función asincrónica (§15.15). Dentro de la función asincrónica más cercana, no se producirá un await_expression en estos lugares:

  • Dentro de una función anónima anidada (no asincrónica)
  • Dentro del bloque de un lock_statement
  • En una conversión de función anónima a un tipo de árbol de expresión (§10.7.3)
  • En un contexto no seguro

Nota: Una await_expression no puede producirse en la mayoría de los lugares dentro de un query_expression, ya que se transforman sintácticamente para usar expresiones lambda no asincrónicas. nota final

Dentro de una función asincrónica, await no se usará como available_identifier aunque se pueda usar el identificador @await textual. Por lo tanto, no hay ambigüedad sintáctica entre await_expressions y varias expresiones que implican identificadores. Fuera de las funciones asincrónicas, await actúa como un identificador normal.

El operando de un await_expression se denomina tarea. Representa una operación asincrónica que puede o no completarse en el momento en que se evalúa el await_expression . El propósito del await operador es suspender la ejecución de la función asincrónica envolvente hasta que se complete la tarea esperada y, a continuación, obtener su resultado.

12.9.8.2 Expresiones awaitables

La tarea de un await_expression es necesaria para que se pueda esperar. Se puede esperar una expresión t si una de las siguientes suspensiones:

  • t es de tipo en tiempo de compilación dynamic
  • t tiene una instancia o método de extensión accesible llamado GetAwaiter sin parámetros y ningún parámetro de tipo, y un tipo A de valor devuelto para el que se mantienen todas las siguientes operaciones:
    • A implementa la interfaz System.Runtime.CompilerServices.INotifyCompletion (en adelante conocida como INotifyCompletion por motivos de brevedad).
    • A tiene una propiedad IsCompleted de instancia accesible y legible de tipo bool
    • A tiene un método GetResult de instancia accesible sin parámetros y ningún parámetro de tipo

El propósito del GetAwaiter método es obtener un awaiter para la tarea. El tipo A se denomina tipo awaiter para la expresión await.

El propósito de la IsCompleted propiedad es determinar si la tarea ya está completa. Si es así, no es necesario suspender la evaluación.

El propósito del INotifyCompletion.OnCompleted método es registrar una "continuación" en la tarea; es decir, un delegado (de tipo System.Action) que se invocará una vez completada la tarea.

El propósito del GetResult método es obtener el resultado de la tarea una vez completado. Este resultado puede ser una finalización correcta, posiblemente con un valor de resultado, o puede ser una excepción producida por el GetResult método .

12.9.8.3 Clasificación de expresiones await

La expresión await t se clasifica de la misma manera que la expresión (t).GetAwaiter().GetResult(). Por lo tanto, si el tipo de valor devuelto de GetResult es void, el await_expression se clasifica como nada. Si tiene un tipo devoid valor no devuelto , el await_expression se clasifica como un valor de tipo T.T

12.9.8.4 Evaluación en tiempo de ejecución de expresiones await

En tiempo de ejecución, la expresión await t se evalúa de la siguiente manera:

  • Un awaiter a se obtiene mediante la evaluación de la expresión (t).GetAwaiter().
  • Se boolb obtiene mediante la evaluación de la expresión (a).IsCompleted.
  • Si b es false entonces la evaluación depende de si a implementa la interfaz System.Runtime.CompilerServices.ICriticalNotifyCompletion (en adelante conocida como ICriticalNotifyCompletion por brevedad). Esta comprobación se realiza en tiempo de enlace; Es decir, en tiempo de ejecución si a tiene el tipo dynamicen tiempo de compilación y en tiempo de compilación. Anote r el delegado de reanudación (§15.15):
    • Si a no implementa ICriticalNotifyCompletion, la expresión ((a) as INotifyCompletion).OnCompleted(r) se evalúa.
    • Si a implementa ICriticalNotifyCompletion, la expresión ((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r) se evalúa.
    • La evaluación se suspende y el control se devuelve al autor de la llamada actual de la función asincrónica.
  • Inmediatamente después de (si b era true), o después de la invocación posterior del delegado de reanudación (si b es false), se evalúa la expresión (a).GetResult() . Si devuelve un valor, ese valor es el resultado del await_expression. De lo contrario, el resultado no es nada.

La implementación de un awaiter de los métodos INotifyCompletion.OnCompleted de interfaz y ICriticalNotifyCompletion.UnsafeOnCompleted debe hacer que el delegado r se invoque como máximo una vez. De lo contrario, el comportamiento de la función asincrónica envolvente no está definido.

12.10 Operadores aritméticos

12.10.1 General

Los *operadores , /, %, +y - se denominan operadores aritméticos.

multiplicative_expression
    : unary_expression
    | multiplicative_expression '*' unary_expression
    | multiplicative_expression '/' unary_expression
    | multiplicative_expression '%' unary_expression
    ;

additive_expression
    : multiplicative_expression
    | additive_expression '+' multiplicative_expression
    | additive_expression '-' multiplicative_expression
    ;

Si un operando de un operador aritmético tiene el tipo dynamicen tiempo de compilación , la expresión se enlaza dinámicamente (§12.3.3). En este caso, el tipo en tiempo de compilación de la expresión es dynamicy la resolución descrita a continuación tendrá lugar en tiempo de ejecución mediante el tipo en tiempo de ejecución de esos operandos que tienen el tipo dynamicde tiempo de compilación .

Operador de multiplicación 12.10.2

Para una operación con el formato x * y, la resolución de sobrecarga del operador binario (§12.4.5) se aplica para seleccionar una implementación específica del operador. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador.

A continuación se enumeran los operadores de multiplicación predefinidos. Los operadores calculan todo el producto de x y y.

  • Multiplicación de enteros:

    int operator *(int x, int y);
    uint operator *(uint x, uint y);
    long operator *(long x, long y);
    ulong operator *(ulong x, ulong y);
    

    En un checked contexto, si el producto está fuera del intervalo del tipo de resultado, se produce una System.OverflowException excepción . En un unchecked contexto, los desbordamientos no se notifican y se descartan los bits significativos de orden alto fuera del intervalo del tipo de resultado.

  • Multiplicación de punto flotante:

    float operator *(float x, float y);
    double operator *(double x, double y);
    

    El producto se calcula según las reglas de aritmética IEC 60559. En la tabla siguiente se enumeran los resultados de todas las combinaciones posibles de valores finitos distintos de cero, ceros, infinities y NaNs. En la tabla, x e y son valores finitos positivos. z es el resultado de x * y, redondeado al valor representable más cercano. Si la magnitud del resultado es demasiado grande para el tipo de destino, z es infinito. Debido al redondeo, z puede ser cero aunque ni sea x y cero.

    +y -y +0 -0 +∞ -∞ NaN
    +x +z -z +0 -0 +∞ -∞ NaN
    -x -z +z -0 +0 -∞ +∞ NaN
    +0 +0 -0 +0 -0 NaN NaN NaN
    -0 -0 +0 -0 +0 NaN NaN NaN
    +∞ +∞ -∞ NaN NaN +∞ -∞ NaN
    -∞ -∞ +∞ NaN NaN -∞ +∞ NaN
    NaN NaN NaN NaN NaN NaN NaN NaN

    (Excepto si se han indicado lo contrario, en las tablas de punto flotante de §12.10.2§12.10.6 , el uso de "+" significa que el valor es positivo; el uso de "" significa que- el valor es negativo; y la falta de un signo significa que el valor puede ser positivo o negativo o no tiene signo (NaN).

  • Multiplicación decimal:

    decimal operator *(decimal x, decimal y);
    

    Si la magnitud del valor resultante es demasiado grande para representar en formato decimal, se produce una System.OverflowException excepción . Debido al redondeo, el resultado puede ser cero aunque ninguno de los operandos sea cero. La escala del resultado, antes de cualquier redondeo, es la suma de las escalas de los dos operandos. La multiplicación decimal equivale a usar el operador de multiplicación de tipo System.Decimal.

Las formas levantadas (§12.4.8) de los operadores de multiplicación predefinidos sin levantar definidos anteriormente también están predefinidos.

Operador de división 12.10.3

Para una operación con el formato x / y, la resolución de sobrecarga del operador binario (§12.4.5) se aplica para seleccionar una implementación específica del operador. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador.

Los operadores de división predefinidos se enumeran a continuación. Todos los operadores calculan el cociente de x y y.

  • División de enteros:

    int operator /(int x, int y);
    uint operator /(uint x, uint y);
    long operator /(long x, long y);
    ulong operator /(ulong x, ulong y);
    

    Si el valor del operando derecho es cero, se produce una System.DivideByZeroException excepción .

    La división redondea el resultado hacia cero. Por lo tanto, el valor absoluto del resultado es el entero más grande posible que es menor o igual que el valor absoluto del cociente de los dos operandos. El resultado es cero o positivo cuando los dos operandos tienen el mismo signo y cero o negativo cuando los dos operandos tienen signos opuestos.

    Si el operando izquierdo es el menor que se puede representar int o long el valor y el operando derecho es –1, se produce un desbordamiento. En un checked contexto, esto hace System.ArithmeticException que se produzca una (o una subclase de ahí). En un unchecked contexto, se define la implementación en cuanto a si System.ArithmeticException se produce una (o una subclase) o el desbordamiento no se notifica con el valor resultante que es el del operando izquierdo.

  • División de punto flotante:

    float operator /(float x, float y);
    double operator /(double x, double y);
    

    El cociente se calcula según las reglas de aritmética IEC 60559. En la tabla siguiente se enumeran los resultados de todas las combinaciones posibles de valores finitos distintos de cero, ceros, infinities y NaNs. En la tabla, x e y son valores finitos positivos. z es el resultado de x / y, redondeado al valor representable más cercano.

    +y -y +0 -0 +∞ -∞ NaN
    +x +z -z +∞ -∞ +0 -0 NaN
    -x -z +z -∞ +∞ -0 +0 NaN
    +0 +0 -0 NaN NaN +0 -0 NaN
    -0 -0 +0 NaN NaN -0 +0 NaN
    +∞ +∞ -∞ +∞ -∞ NaN NaN NaN
    -∞ -∞ +∞ -∞ +∞ NaN NaN NaN
    NaN NaN NaN NaN NaN NaN NaN NaN
  • División decimal:

    decimal operator /(decimal x, decimal y);
    

    Si el valor del operando derecho es cero, se produce una System.DivideByZeroException excepción . Si la magnitud del valor resultante es demasiado grande para representar en formato decimal, se produce una System.OverflowException excepción . Debido al redondeo, el resultado puede ser cero aunque el primer operando no sea cero. La escala del resultado, antes de cualquier redondeo, es la escala más cercana a la escala preferida que conservará un resultado igual al resultado exacto. La escala preferida es la escala de x menos la escala de y.

    La división decimal es equivalente a usar el operador de división de tipo System.Decimal.

Las formas levantadas (§12.4.8) de los operadores de división predefinidos sin levantar definidos anteriormente también están predefinidos.

Operador de resto 12.10.4

Para una operación con el formato x % y, la resolución de sobrecarga del operador binario (§12.4.5) se aplica para seleccionar una implementación específica del operador. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador.

Los operadores de resto predefinidos se enumeran a continuación. Todos los operadores calculan el resto de la división entre x y y.

  • Resto entero:

    int operator %(int x, int y);
    uint operator %(uint x, uint y);
    long operator %(long x, long y);
    ulong operator %(ulong x, ulong y);
    

    El resultado de x % y es el valor generado por x – (x / y) * y. Si y es cero, se produce una System.DivideByZeroException excepción .

    Si el operando izquierdo es el menor int o long el valor y el operando derecho es –1, se produce una System.OverflowException excepción si y solo si x / y se produciría una excepción.

  • Resto de punto flotante:

    float operator %(float x, float y);
    double operator %(double x, double y);
    

    En la tabla siguiente se enumeran los resultados de todas las combinaciones posibles de valores finitos distintos de cero, ceros, infinities y NaNs. En la tabla, x e y son valores finitos positivos. z es el resultado de x % y y se calcula como x – n * y, donde n es el entero más grande posible que es menor o igual que x / y. Este método de computación del resto es análogo al usado para operandos enteros, pero difiere de la definición iec 60559 (en la que n es el entero más cercano a x / y).

    +y -y +0 -0 +∞ -∞ NaN
    +x +z +z NaN NaN +x +x NaN
    -x -z -z NaN NaN -x -x NaN
    +0 +0 +0 NaN NaN +0 +0 NaN
    -0 -0 -0 NaN NaN -0 -0 NaN
    +∞ NaN NaN NaN NaN NaN NaN NaN
    -∞ NaN NaN NaN NaN NaN NaN NaN
    NaN NaN NaN NaN NaN NaN NaN NaN
  • Resto decimal:

    decimal operator %(decimal x, decimal y);
    

    Si el valor del operando derecho es cero, se produce una System.DivideByZeroException excepción . Se define la implementación cuando System.ArithmeticException se produce una (o una subclase de ahí). Una implementación conforme no producirá una excepción en x % y ningún caso en x / y el que no produzca una excepción. La escala del resultado, antes de cualquier redondeo, es mayor de las escalas de los dos operandos y el signo del resultado, si no es cero, es igual que el de x.

    El resto decimal es equivalente al uso del operador de resto de tipo System.Decimal.

    Nota: Estas reglas garantizan que para todos los tipos, el resultado nunca tiene el signo opuesto del operando izquierdo. nota final

Las formas levantadas (§12.4.8) de los operadores de resto predefinidos sin levantar definidos anteriormente también están predefinidos.

Operador de suma 12.10.5

Para una operación con el formato x + y, la resolución de sobrecarga del operador binario (§12.4.5) se aplica para seleccionar una implementación específica del operador. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador.

A continuación se enumeran los operadores de suma predefinidos. Para los tipos numéricos y de enumeración, los operadores de suma predefinidos calculan la suma de los dos operandos. Cuando uno o ambos operandos son de tipo string, los operadores de suma predefinidos concatenan la representación de cadena de los operandos.

  • Suma de enteros:

    int operator +(int x, int y);
    uint operator +(uint x, uint y);
    long operator +(long x, long y);
    ulong operator +(ulong x, ulong y
    

    En un checked contexto, si la suma está fuera del intervalo del tipo de resultado, se produce una System.OverflowException excepción . En un unchecked contexto, los desbordamientos no se notifican y se descartan los bits significativos de orden alto fuera del intervalo del tipo de resultado.

  • Adición de punto flotante:

    float operator +(float x, float y);
    double operator +(double x, double y);
    

    La suma se calcula según las reglas de aritmética IEC 60559. En la tabla siguiente se enumeran los resultados de todas las combinaciones posibles de valores finitos distintos de cero, ceros, infinities y NaNs. En la tabla, x e y son valores finitos distintos de cero y z es el resultado de x + y. Si x e y tienen la misma magnitud pero signos opuestos, z es cero positivo. Si x + y es demasiado grande para representar en el tipo de destino, z es un infinito con el mismo signo que x + y.

    y +0 -0 +∞ -∞ NaN
    x z x x +∞ -∞ NaN
    +0 y +0 +0 +∞ –∞ NaN
    -0 y +0 -0 +∞ -∞ NaN
    +∞ +∞ +∞ +∞ +∞ NaN NaN
    -∞ -∞ -∞ -∞ NaN -∞ NaN
    NaN NaN NaN NaN NaN NaN NaN
  • Suma decimal:

    decimal operator +(decimal x, decimal y);
    

    Si la magnitud del valor resultante es demasiado grande para representar en formato decimal, se produce una System.OverflowException excepción . La escala del resultado, antes de cualquier redondeo, es mayor de las escalas de los dos operandos.

    La suma decimal es equivalente al uso del operador de suma de tipo System.Decimal.

  • Adición de enumeración. Cada tipo de enumeración proporciona implícitamente los siguientes operadores predefinidos, donde E es el tipo de enumeración y U es el tipo subyacente de E:

    E operator +(E x, U y);
    E operator +(U x, E y);
    

    En tiempo de ejecución, estos operadores se evalúan exactamente como (E)((U)x + (U)y).

  • Concatenación de cadenas:

    string operator +(string x, string y);
    string operator +(string x, object y);
    string operator +(object x, string y);
    

    Estas sobrecargas del operador binario + realizan la concatenación de cadenas. Si un operando de concatenación de cadenas es null, se sustituye una cadena vacía. De lo contrario, cualquierstring operando no se convierte en su representación de cadena invocando el método virtual ToString heredado del tipo object. Si ToString devuelve null, se sustituye una cadena vacía.

    Ejemplo:

    class Test
    {
        static void Main()
        {
            string s = null;
            Console.WriteLine("s = >" + s + "<");  // Displays s = ><
    
            int i = 1;
            Console.WriteLine("i = " + i);         // Displays i = 1
    
            float f = 1.2300E+15F;
            Console.WriteLine("f = " + f);         // Displays f = 1.23E+15
    
            decimal d = 2.900m;
            Console.WriteLine("d = " + d);         // Displays d = 2.900
       }
    }
    

    La salida que se muestra en los comentarios es el resultado típico en un sistema en inglés de EE. UU. La salida precisa puede depender de la configuración regional del entorno de ejecución. El propio operador de concatenación de cadenas se comporta de la misma manera en cada caso, pero los métodos a los que se llama implícitamente durante la ToString ejecución podrían verse afectados por la configuración regional.

    ejemplo final

    El resultado del operador de concatenación de cadena es un string que consta de los caracteres del operando izquierdo seguido de los caracteres del operando derecho. El operador de concatenación de cadenas nunca devuelve un null valor. Se puede producir una System.OutOfMemoryException excepción si no hay suficiente memoria disponible para asignar la cadena resultante.

  • Combinación de delegados. Cada tipo de delegado proporciona implícitamente el siguiente operador predefinido, donde D es el tipo de delegado:

    D operator +(D x, D y);
    

    Si el primer operando es null, el resultado de la operación es el valor del segundo operando (aunque también nullsea ). De lo contrario, si el segundo operando es null, el resultado de la operación es el valor del primer operando. De lo contrario, el resultado de la operación es una nueva instancia de delegado cuya lista de invocación consta de los elementos de la lista de invocación del primer operando, seguido de los elementos de la lista de invocación del segundo operando. Es decir, la lista de invocación del delegado resultante es la concatenación de las listas de invocación de los dos operandos.

    Nota: Para obtener ejemplos de combinación de delegados, consulte §12.10.6 y §20.6. Puesto System.Delegate que no es un tipo delegado, el operador + no está definido para él. nota final

Las formas levantadas (§12.4.8) de los operadores de suma predefinidos sin levantar definidos anteriormente también están predefinidos.

Operador de resta 12.10.6

Para una operación con el formato x – y, la resolución de sobrecarga del operador binario (§12.4.5) se aplica para seleccionar una implementación específica del operador. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador.

A continuación se enumeran los operadores de resta predefinidos. Todos los operadores restan y de x.

  • Resta de enteros:

    int operator –(int x, int y);
    uint operator –(uint x, uint y);
    long operator –(long x, long y);
    ulong operator –(ulong x, ulong y
    

    En un checked contexto, si la diferencia está fuera del intervalo del tipo de resultado, se produce una System.OverflowException excepción . En un unchecked contexto, los desbordamientos no se notifican y se descartan los bits significativos de orden alto fuera del intervalo del tipo de resultado.

  • Resta de punto flotante:

    float operator –(float x, float y);
    double operator –(double x, double y);
    

    La diferencia se calcula según las reglas de aritmética IEC 60559. En la tabla siguiente se enumeran los resultados de todas las combinaciones posibles de valores finitos distintos de cero, ceros, infinities y NaNs. En la tabla, x e y son valores finitos distintos de cero y z es el resultado de x – y. Si x e y son iguales, z es cero positivo. Si x – y es demasiado grande para representar en el tipo de destino, z es un infinito con el mismo signo que x – y.

    y +0 -0 +∞ -∞ NaN
    x z x x -∞ +∞ NaN
    +0 -y +0 +0 -∞ +∞ NaN
    -0 -y -0 +0 -∞ +∞ NaN
    +∞ +∞ +∞ +∞ NaN +∞ NaN
    -∞ -∞ -∞ -∞ -∞ NaN NaN
    NaN NaN NaN NaN NaN NaN NaN

    (En la tabla anterior, las -y entradas indican la negación de y, no que el valor es negativo).

  • Resta decimal:

    decimal operator –(decimal x, decimal y);
    

    Si la magnitud del valor resultante es demasiado grande para representar en formato decimal, se produce una System.OverflowException excepción . La escala del resultado, antes de cualquier redondeo, es mayor de las escalas de los dos operandos.

    La resta decimal es equivalente a usar el operador de resta de tipo System.Decimal.

  • Resta de enumeración. Cada tipo de enumeración proporciona implícitamente el siguiente operador predefinido, donde E es el tipo de enumeración y U es el tipo subyacente de E:

    U operator –(E x, E y);
    

    Este operador se evalúa exactamente como (U)((U)x – (U)y). En otras palabras, el operador calcula la diferencia entre los valores ordinales de x y y, y el tipo del resultado es el tipo subyacente de la enumeración.

    E operator –(E x, U y);
    

    Este operador se evalúa exactamente como (E)((U)x – y). En otras palabras, el operador resta un valor del tipo subyacente de la enumeración, lo que produce un valor de la enumeración.

  • Eliminación del delegado. Cada tipo de delegado proporciona implícitamente el siguiente operador predefinido, donde D es el tipo de delegado:

    D operator –(D x, D y);
    

    La semántica es la siguiente:

    • Si el primer operando es null, el resultado de la operación es null.
    • De lo contrario, si el segundo operando es null, el resultado de la operación es el valor del primer operando.
    • De lo contrario, ambos operandos representan listas de invocación no vacías (§20.2).
      • Si las listas comparan igual, según lo determinado por el operador de igualdad de delegado (§12.12.9), el resultado de la operación es null.
      • De lo contrario, el resultado de la operación es una nueva lista de invocación que consta de la lista del primer operando con las entradas del segundo operando quitadas de ella, siempre que la lista del segundo operando sea una sublist del primero. (Para determinar la igualdad de la sublist, las entradas correspondientes se comparan con el operador de igualdad del delegado). Si la segunda lista del operando coincide con varias sublists de entradas contiguas en la lista del primer operando, se quita la última sublist coincidente de entradas contiguas.
      • De lo contrario, el resultado de la operación es el valor del operando izquierdo.

    Ninguna de las listas de operandos (si las hay) cambia en el proceso.

    Ejemplo:

    delegate void D(int x);
    
    class C
    {
        public static void M1(int i) { ... }
        public static void M2(int i) { ... }
    }
    
    class Test
    {
        static void Main()
        {
            D cd1 = new D(C.M1);
            D cd2 = new D(C.M2);
            D list = null;
    
            list = null - cd1;                             // null
            list = (cd1 + cd2 + cd2 + cd1) - null;         // M1 + M2 + M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - cd1;          // M1 + M2 + M2
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2);  // M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd2);  // M1 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd1);  // M1 + M2
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd1);  // M1 + M2 + M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2 + cd2 + cd1);  // null
        }
    }
    

    ejemplo final

Las formas levantadas (§12.4.8) de los operadores de resta predefinidos no eliminados definidos anteriormente también están predefinidos.

Operadores de desplazamiento 12.11

Los << operadores y >> se usan para realizar operaciones de desplazamiento de bits.

shift_expression
    : additive_expression
    | shift_expression '<<' additive_expression
    | shift_expression right_shift additive_expression
    ;

Si un operando de un shift_expression tiene el tipo dynamicen tiempo de compilación , la expresión se enlaza dinámicamente (§12.3.3). En este caso, el tipo en tiempo de compilación de la expresión es dynamicy la resolución descrita a continuación tendrá lugar en tiempo de ejecución mediante el tipo en tiempo de ejecución de esos operandos que tienen el tipo dynamicde tiempo de compilación .

Para una operación del formulario x << count o x >> count, la resolución de sobrecarga del operador binario (§12.4.5) se aplica para seleccionar una implementación de operador específica. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador.

Al declarar un operador de desplazamiento sobrecargado, el tipo del primer operando siempre será la clase o estructura que contiene la declaración del operador, y el tipo del segundo operando siempre intserá .

Los operadores de desplazamiento predefinidos se enumeran a continuación.

  • Desplazamiento a la izquierda:

    int operator <<(int x, int count);
    uint operator <<(uint x, int count);
    long operator <<(long x, int count);
    ulong operator <<(ulong x, int count);
    

    El << operador cambia de izquierda por un número de bits calculados x como se describe a continuación.

    Los bits de orden superior fuera del intervalo del tipo de resultado de x se descartan, los bits restantes se desplazan a la izquierda y las posiciones de bits vacías de orden bajo se establecen en cero.

  • Desplazamiento a la derecha:

    int operator >>(int x, int count);
    uint operator >>(uint x, int count);
    long operator >>(long x, int count);
    ulong operator >>(ulong x, int count);
    

    El >> operador cambia a la derecha por un número de bits calculados x como se describe a continuación.

    Cuando x es de tipo int o long, los bits de orden bajo de x se descartan, los bits restantes se desplazan hacia la derecha y las posiciones de bits vacías de orden alto se establecen en cero si x no es negativo y se establecen en uno si x es negativo.

    Cuando x es de tipo uint o ulong, los bits de orden bajo de x se descartan, los bits restantes se desplazan hacia la derecha y las posiciones de bits vacías de orden alto se establecen en cero.

Para los operadores predefinidos, el número de bits que se van a desplazar se calcula de la siguiente manera:

  • Cuando el tipo de x es int o uint, el recuento de desplazamientos recibe cinco bits de orden bajo de count. En otras palabras, el recuento de turnos se calcula desde count & 0x1F.
  • Cuando el tipo de x es long o ulong, el recuento de desplazamientos recibe seis bits de orden bajo de count. En otras palabras, el recuento de turnos se calcula desde count & 0x3F.

Si el recuento de desplazamientos resultante es cero, los operadores de desplazamiento simplemente devuelven el valor de x.

Las operaciones de desplazamiento nunca producen desbordamientos y generan los mismos resultados en contextos checked y unchecked.

Cuando el operando izquierdo del >> operador es de un tipo entero firmado, el operador realiza un desplazamiento aritmético a la derecha, donde el valor del bit más significativo (el bit de signo) del operando se propaga a las posiciones de bits vacías de orden alto. Cuando el operando izquierdo del >> operador es de un tipo entero sin signo, el operador realiza un desplazamiento lógico a la derecha, donde las posiciones de bits vacías de orden alto siempre se establecen en cero. Para realizar la operación opuesta de que se deduce del tipo de operando, se pueden usar conversiones explícitas.

Ejemplo: si x es una variable de tipo int, la operación unchecked ((int)((uint)x >> y)) realiza un desplazamiento lógico a la derecha de x. ejemplo final

Las formas levantadas (§12.4.8) de los operadores de desplazamiento predefinidos sin levantar definidos anteriormente también están predefinidos.

12.12 Operadores relacionales y de pruebas de tipos

12.12.1 General

Los ==operadores , !=, <, <=>, >=, , isy as se denominan operadores relacionales y de prueba de tipos.

relational_expression
    : shift_expression
    | relational_expression '<' shift_expression
    | relational_expression '>' shift_expression
    | relational_expression '<=' shift_expression
    | relational_expression '>=' shift_expression
    | relational_expression 'is' type
    | relational_expression 'is' pattern
    | relational_expression 'as' type
    ;

equality_expression
    : relational_expression
    | equality_expression '==' relational_expression
    | equality_expression '!=' relational_expression
    ;

Nota: La búsqueda del operando derecho del is operador debe probar primero como un tipo y, a continuación, como una expresión que puede abarcar varios tokens. En el caso de que el operando sea una expreesión, la expresión de patrón debe tener prioridad al menos tan alta como shift_expression. nota final

El is operador se describe en §12.12.12 y el as operador se describe en §12.12.13.

Los ==operadores , !=, <, >y >= <= son operadores de comparación.

Si se usa un default_literal (§12.8.21) como operando de un <operador , >, <=o >= , se produce un error en tiempo de compilación. Si se usa un default_literal como operandos de un == operador o != , se produce un error en tiempo de compilación. Si se usa un default_literal como operando izquierdo del is operador o as , se produce un error en tiempo de compilación.

Si un operando de un operador de comparación tiene el tipo dynamicen tiempo de compilación , la expresión se enlaza dinámicamente (§12.3.3). En este caso, el tipo en tiempo de compilación de la expresión es dynamicy la resolución descrita a continuación tendrá lugar en tiempo de ejecución mediante el tipo en tiempo de ejecución de esos operandos que tienen el tipo dynamicen tiempo de compilación .

Para una operación del formulario x «op» y, donde «op» es un operador de comparación, se aplica la resolución de sobrecarga (§12.4.5) para seleccionar una implementación de operador específica. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador. Si ambos operandos de un equality_expression son el null literal, no se realiza la resolución de sobrecarga y la expresión se evalúa como un valor constante de true o false según si el operador es == o != .

Los operadores de comparación predefinidos se describen en las subclases siguientes. Todos los operadores de comparación predefinidos devuelven un resultado de tipo bool, como se describe en la tabla siguiente.

Operación Resultado
x == y truesi x es igual a y, de lo contrario, false
x != y truesi x no es igual a y, de lo contrario, false
x < y true si x es menor que y, false en caso contrario
x > y true si x es mayor que y, false en caso contrario
x <= y true si x es menor o igual que y, false en caso contrario
x >= y true si x es mayor o igual que y, false en caso contrario

12.12.2 Operadores de comparación enteros

Los operadores de comparación de enteros predefinidos son:

bool operator ==(int x, int y);
bool operator ==(uint x, uint y);
bool operator ==(long x, long y);
bool operator ==(ulong x, ulong y);

bool operator !=(int x, int y);
bool operator !=(uint x, uint y);
bool operator !=(long x, long y);
bool operator !=(ulong x, ulong y);

bool operator <(int x, int y);
bool operator <(uint x, uint y);
bool operator <(long x, long y);
bool operator <(ulong x, ulong y);

bool operator >(int x, int y);
bool operator >(uint x, uint y);
bool operator >(long x, long y);
bool operator >(ulong x, ulong y);

bool operator <=(int x, int y);
bool operator <=(uint x, uint y);
bool operator <=(long x, long y);
bool operator <=(ulong x, ulong y);

bool operator >=(int x, int y);
bool operator >=(uint x, uint y);
bool operator >=(long x, long y);
bool operator >=(ulong x, ulong y);

Cada uno de estos operadores compara los valores numéricos de los dos operandos enteros y devuelve un bool valor que indica si la relación concreta es true o false.

Las formas levantadas (§12.4.8) de los operadores de comparación de enteros predefinidos no eliminados definidos anteriormente también están predefinidos.

12.12.3 Operadores de comparación de punto flotante

Los operadores de comparación de punto flotante predefinidos son:

bool operator ==(float x, float y);
bool operator ==(double x, double y);

bool operator !=(float x, float y);
bool operator !=(double x, double y);

bool operator <(float x, float y);
bool operator <(double x, double y);

bool operator >(float x, float y);
bool operator >(double x, double y);

bool operator <=(float x, float y);
bool operator <=(double x, double y);

bool operator >=(float x, float y);
bool operator >=(double x, double y);

Los operadores comparan los operandos según las reglas del estándar IEC 60559:

Si cualquiera de los operandos es NaN, el resultado es false para todos los operadores excepto !=, para los que el resultado es true. Para los dos operandos, x != y siempre produce el mismo resultado que !(x == y). Sin embargo, cuando uno o ambos operandos son NaN, los <operadores , >, <=y >= no producen los mismos resultados que la negación lógica del operador opuesto.

Ejemplo: si cualquiera de x y y es NaN, entonces x < y es false, pero !(x >= y) es true. ejemplo final

Cuando ninguno de los operandos es NaN, los operadores comparan los valores de los dos operandos de punto flotante con respecto a la ordenación.

–∞ < –max < ... < –min < –0.0 == +0.0 < +min < ... < +max < +∞

donde min y max son los valores finitos positivos más pequeños y mayores que se pueden representar en el formato de punto flotante determinado. Los efectos importantes de este orden son:

  • Los ceros negativos y positivos se consideran iguales.
  • Un infinito negativo se considera menor que todos los demás valores, pero igual a otro infinito negativo.
  • Un infinito positivo se considera mayor que todos los demás valores, pero igual a otro infinito positivo.

Las formas levantadas (§12.4.8) de los operadores de comparación predefinidos de punto flotante predefinidos definidos anteriormente también están predefinidos.

12.12.4 Operadores de comparación decimales

Los operadores de comparación decimal predefinidos son:

bool operator ==(decimal x, decimal y);
bool operator !=(decimal x, decimal y);
bool operator <(decimal x, decimal y);
bool operator >(decimal x, decimal y);
bool operator <=(decimal x, decimal y);
bool operator >=(decimal x, decimal y);

Cada uno de estos operadores compara los valores numéricos de los dos operandos decimales y devuelve un bool valor que indica si la relación concreta es true o false. Cada comparación decimal equivale a usar el operador relacional o de igualdad correspondiente de tipo System.Decimal.

Las formas levantadas (§12.4.8) de los operadores de comparación decimal predefinidos no eliminados definidos anteriormente también están predefinidos.

12.12.5 Operadores de igualdad booleanos

Los operadores de igualdad booleanos predefinidos son:

bool operator ==(bool x, bool y);
bool operator !=(bool x, bool y);

El resultado de == es true si y x y son true o si ambos x y y son false. De lo contrario, el resultado es false.

El resultado de != es false si y x y son true o si ambos x y y son false. De lo contrario, el resultado es true. Cuando los operandos son de tipo bool, el != operador genera el mismo resultado que el ^ operador .

Las formas levantadas (§12.4.8) de los operadores de igualdad booleanos predefinidos no eliminados definidos anteriormente también están predefinidos.

12.12.6 Operadores de comparación de enumeración

Cada tipo de enumeración proporciona implícitamente los siguientes operadores de comparación predefinidos

bool operator ==(E x, E y);
bool operator !=(E x, E y);

bool operator <(E x, E y);
bool operator >(E x, E y);
bool operator <=(E x, E y);
bool operator >=(E x, E y);

El resultado de evaluar x «op» y, donde x e y son expresiones de un tipo E de enumeración con un tipo Usubyacente , y «op» es uno de los operadores de comparación, es exactamente igual que evaluar ((U)x) «op» ((U)y). En otras palabras, los operadores de comparación de tipos de enumeración simplemente comparan los valores enteros subyacentes de los dos operandos.

Las formas levantadas (§12.4.8) de los operadores de comparación de enumeración predefinidos no eliminados definidos anteriormente también están predefinidos.

12.12.7 Operadores de igualdad de tipos de referencia

Cada tipo de C clase proporciona implícitamente los siguientes operadores de igualdad de tipos de referencia predefinidos:

bool operator ==(C x, C y);
bool operator !=(C x, C y);

a menos que existan operadores de igualdad predefinidos para C (por ejemplo, cuando C es string o System.Delegate).

Los operadores devuelven el resultado de comparar las dos referencias para la igualdad o la no igualdad. operator == devuelve true si y solo si x y y hacen referencia a la misma instancia o son ambas null, mientras que operator != devuelve true si y solo si operator == con los mismos operandos devolverían false.

Además de las reglas de aplicabilidad normales (§12.6.4.2), los operadores predefinidos de igualdad de tipos de referencia requieren uno de los siguientes para poder aplicarse:

  • Ambos operandos son un valor de un tipo conocido como un reference_type o el literal null. Además, existe una conversión de referencia explícita o de identidad (§10.3.5) de cualquiera de los operandos al tipo del otro operando.
  • Un operando es el literal nully el otro operando es un valor de tipo T donde T es un type_parameter que no se sabe que es un tipo de valor y no tiene la restricción de tipo de valor.
    • Si en tiempo de ejecución T es un tipo de valor que no acepta valores NULL, el resultado de == es false y el resultado de != es true.
    • Si en tiempo de ejecución T es un tipo de valor que acepta valores NULL, el resultado se calcula a partir de la HasValue propiedad del operando, como se describe en (§12.12.10).
    • Si en tiempo de ejecución T es un tipo de referencia, el resultado es true si el operando es nully false , de lo contrario, .

A menos que se cumpla una de estas condiciones, se produce un error en tiempo de enlace.

Nota: Las implicaciones importantes de estas reglas son:

  • Se trata de un error en tiempo de enlace para usar los operadores predefinidos de igualdad de tipos de referencia para comparar dos referencias que se sabe que son diferentes en tiempo de enlace. Por ejemplo, si los tipos de tiempo de enlace de los operandos son dos tipos de clase y, si ninguno deriva del otro, sería imposible que los dos operandos hagan referencia al mismo objeto. Por lo tanto, la operación se considera un error en tiempo de enlace.
  • Los operadores predefinidos de igualdad de tipos de referencia no permiten comparar operandos de tipo de valor (excepto cuando los parámetros de tipo se comparan con null, que se controla especialmente).
  • Los operandos de operadores predefinidos de igualdad de tipos de referencia nunca se colocan en conversión boxeada. No tendría sentido realizar estas operaciones boxing, ya que las referencias a las instancias boxing recién asignadas difieren necesariamente de todas las demás referencias.

Para una operación del formulario x == y o x != y, si existe algún usuario definido operator == por operator != el usuario o existe, las reglas de resolución de sobrecarga del operador (§12.4.5) seleccionarán ese operador en lugar del operador de igualdad de tipos de referencia predefinido. Siempre es posible seleccionar el operador de igualdad de tipos de referencia predefinido mediante la conversión explícita de uno o ambos operandos al tipo object.

nota final

Ejemplo: en el ejemplo siguiente se comprueba si un argumento de un tipo de parámetro de tipo sin restricciones es null.

class C<T>
{
   void F(T x)
   {
      if (x == null)
      {
          throw new ArgumentNullException();
      }
      ...
   }
}

La x == null construcción se permite aunque T podría representar un tipo de valor que no acepta valores NULL y el resultado se define simplemente para ser false cuando T es un tipo de valor que no acepta valores NULL.

ejemplo final

Para una operación del formulario x == y o x != y, si procede operator == o operator != existe, las reglas de resolución de sobrecarga del operador (§12.4.5) seleccionarán ese operador en lugar del operador de igualdad de tipos de referencia predefinido.

Nota: Siempre es posible seleccionar el operador de igualdad de tipos de referencia predefinido mediante la conversión explícita de ambos operandos al tipo object. nota final

Ejemplo: El ejemplo

class Test
{
    static void Main()
    {
        string s = "Test";
        string t = string.Copy(s);
        Console.WriteLine(s == t);
        Console.WriteLine((object)s == t);
        Console.WriteLine(s == (object)t);
        Console.WriteLine((object)s == (object)t);
    }
}

genera el resultado

True
False
False
False

Las s variables y t hacen referencia a dos instancias de cadena distintas que contienen los mismos caracteres. La primera comparación genera True porque el operador de igualdad de cadenas predefinido (§12.12.8) se selecciona cuando ambos operandos son de tipo string. El resto de comparaciones de todas las salidas False porque la sobrecarga de operator == en el string tipo no es aplicable cuando ninguno de los operandos tiene un tipo de tiempo de enlace de object.

Tenga en cuenta que la técnica anterior no es significativa para los tipos de valor. En el ejemplo

class Test
{
    static void Main()
    {
        int i = 123;
        int j = 123;
        Console.WriteLine((object)i == (object)j);
    }
}

False salidas porque las conversiones crean referencias a dos instancias independientes de valores boxedint.

ejemplo final

12.12.8 Operadores de igualdad de cadenas

Los operadores de igualdad de cadenas predefinidos son:

bool operator ==(string x, string y);
bool operator !=(string x, string y);

Dos string valores se consideran iguales cuando se cumple uno de los siguientes valores:

  • Ambos valores son null.
  • Ambos valores no sonnull referencias a instancias de cadena que tienen longitudes idénticas y caracteres idénticos en cada posición de carácter.

Los operadores de igualdad de cadenas comparan valores de cadena en lugar de referencias de cadena. Cuando dos instancias de cadena independientes contienen exactamente la misma secuencia de caracteres, los valores de las cadenas son iguales, pero las referencias son diferentes.

Nota: Como se describe en §12.12.7, los operadores de igualdad de tipos de referencia se pueden usar para comparar referencias de cadena en lugar de valores de cadena. nota final

12.12.9 Operadores de igualdad de delegados

Los operadores de igualdad de delegados predefinidos son:

bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y);

Dos instancias de delegado se consideran iguales a las siguientes:

  • Si cualquiera de las instancias de delegado es null, son iguales si y solo si ambos son null.
  • Si los delegados tienen un tipo de tiempo de ejecución diferente, nunca son iguales.
  • Si ambas instancias del delegado tienen una lista de invocación (§20.2), esas instancias son iguales si y solo si sus listas de invocación tienen la misma longitud y cada entrada de la lista de invocación de una es igual (como se define a continuación) a la entrada correspondiente, en orden, en la lista de invocación del otro.

Las siguientes reglas rigen la igualdad de entradas de lista de invocación:

  • Si dos entradas de lista de invocación hacen referencia al mismo método estático, las entradas son iguales.
  • Si dos entradas de lista de invocación hacen referencia al mismo método no estático en el mismo objeto de destino (según lo definido por los operadores de igualdad de referencia), las entradas son iguales.
  • Las entradas de la lista de invocación generadas a partir de la evaluación de funciones anónimas semánticamente idénticas (§12.19) con el mismo conjunto (posiblemente vacío) de instancias de variables externas capturadas pueden ser iguales (pero no necesarios).

Si la resolución de sobrecarga del operador se resuelve en cualquier operador de igualdad de delegados y los tipos de tiempo de enlace de ambos operandos son tipos delegados, como se describe en §20 en lugar System.Delegatede , y no hay ninguna conversión de identidad entre los tipos de operando de tipo de enlace, se produce un error en tiempo de enlace.

Nota: Esta regla impide las comparaciones que nunca pueden considerarnull valores no iguales debido a que son referencias a instancias de diferentes tipos de delegados. nota final

12.12.10 Operadores de igualdad entre tipos de valor que aceptan valores NULL y el literal NULL

Los == operadores y != permiten que un operando sea un valor de un tipo de valor que acepta valores NULL y el otro sea el literal, incluso si no existe ningún operador predefinido o definido por el null usuario (en forma no levantada o levantada) para la operación.

Para una operación de uno de los formularios

x == null    null == x    x != null    null != x

donde x es una expresión de un tipo de valor que acepta valores NULL, si la resolución de sobrecarga del operador (§12.4.5) no encuentra un operador aplicable, el resultado se calcula en su lugar desde la HasValue propiedad de x. En concreto, las dos primeras formas se traducen en !x.HasValuey las dos últimas se traducen en x.HasValue.

12.12.11 Operadores de igualdad de tupla

Los operadores de igualdad de tupla se aplican en pares a los elementos de los operandos de tupla en orden léxico.

Si cada operando x y de un == operador o != se clasifica como una tupla o como un valor con un tipo de tupla (§8.3.11), el operador es un operador de igualdad y de tupla.

Si un operando e se clasifica como una tupla, los elementos e1...en serán los resultados de evaluar las expresiones de elemento de la expresión de tupla. De lo contrario, si e es un valor de un tipo de tupla, los elementos serán t.Item1...t.Itemn donde t es el resultado de evaluar e.

Los operandos x y y de un operador de igualdad de tupla tendrán la misma aridad o se producirá un error en tiempo de compilación. Para cada par de elementos xi y yi, se aplicará el mismo operador de igualdad y producirá un resultado de tipo bool, dynamic, un tipo que tenga una conversión implícita a boolo un tipo que defina los true operadores y false .

El operador x == y de igualdad de tupla se evalúa de la siguiente manera:

  • Se evalúa el operando x izquierdo.
  • Se evalúa el operando y del lado derecho.
  • Para cada par de elementos xi y yi en orden léxico:
    • El operador xi == yi se evalúa y se obtiene un resultado de tipo bool de la siguiente manera:
      • Si la comparación produjo un bool resultado, ese es el resultado.
      • De lo contrario, si la comparación produjo un dynamic , el operador false se invoca dinámicamente en él y el valor resultante bool se niega con el operador de negación lógica (!).
      • De lo contrario, si el tipo de la comparación tiene una conversión implícita a bool, esa conversión se aplica.
      • De lo contrario, si el tipo de la comparación tiene un operador false, ese operador se invoca y el valor resultante bool se niega con el operador de negación lógica (!).
    • Si el resultado bool es false, no se produce ninguna evaluación adicional y el resultado del operador de igualdad de tupla es false.
  • Si todas las comparaciones de elementos produjeron true, el resultado del operador de igualdad de tupla es true.

El operador x != y de igualdad de tupla se evalúa de la siguiente manera:

  • Se evalúa el operando x izquierdo.
  • Se evalúa el operando y del lado derecho.
  • Para cada par de elementos xi y yi en orden léxico:
    • El operador xi != yi se evalúa y se obtiene un resultado de tipo bool de la siguiente manera:
      • Si la comparación produjo un bool resultado, ese es el resultado.
      • De lo contrario, si la comparación produjo un dynamic , el operador true se invoca dinámicamente en él y el valor resultante bool es el resultado.
      • De lo contrario, si el tipo de la comparación tiene una conversión implícita a bool, esa conversión se aplica.
      • De lo contrario, si el tipo de la comparación tiene un operador true, ese operador se invoca y el valor resultante bool es el resultado.
    • Si el resultado bool es true, no se produce ninguna evaluación adicional y el resultado del operador de igualdad de tupla es true.
  • Si todas las comparaciones de elementos produjeron false, el resultado del operador de igualdad de tupla es false.

12.12.12 El operador is

Hay dos formas del is operador. Uno es el operador is-type, que tiene un tipo en el lado derecho. El otro es el operador is-pattern, que tiene un patrón en el lado derecho.

12.12.12.1 Operador is-type

El operador is-type se usa para comprobar si el tipo en tiempo de ejecución de un objeto es compatible con un tipo determinado. La comprobación se realiza en tiempo de ejecución. El resultado de la operación E is T, donde E es una expresión y T es un tipo distinto dynamicde , es un valor booleano que indica si E no es null y se puede convertir correctamente al tipo T mediante una conversión de referencia, una conversión boxing, una conversión de conversión unboxing, una conversión de ajuste o una conversión de desencapsulado.

La operación se evalúa de la siguiente manera:

  1. Si E es una función anónima o un grupo de métodos, se produce un error en tiempo de compilación.
  2. Si E es el null literal o si el valor de E es null, el resultado es false.
  3. De lo contrario:
  4. Deje que R sea el tipo de tiempo de ejecución de E.
  5. Vamos D a derivar de R lo siguiente:
  6. Si R es un tipo de valor que acepta valores NULL, D es el tipo subyacente de R.
  7. En caso contrario, D es R.
  8. El resultado depende D de y T de la manera siguiente:
  9. Si T es un tipo de referencia, el resultado es true si:
    • Existe una conversión de identidad entre D y T,
    • D es un tipo de referencia y una conversión de referencia implícita de D a T existe o
    • Cualquiera de las dos: D es un tipo de valor y una conversión boxing de D a T existe.
      O bien: D es un tipo de valor y T es un tipo de interfaz implementado por D.
  10. Si T es un tipo de valor que acepta valores NULL, el resultado es true si D es el tipo subyacente de T.
  11. Si T es un tipo de valor que no acepta valores NULL, el resultado es true si D y T son del mismo tipo.
  12. De lo contrario, el resultado es false.

El operador no considera las conversiones definidas por el is usuario.

Nota: A medida que el is operador se evalúa en tiempo de ejecución, se han sustituido todos los argumentos de tipo y no hay tipos abiertos (§8.4.3) que se deben tener en cuenta. nota final

Nota: El is operador se puede entender en términos de tipos y conversiones en tiempo de compilación como se indica a continuación, donde C es el tipo en tiempo de compilación de E:

  • Si el tipo en tiempo de compilación de es el mismo Tque e , o si una conversión de referencia implícita (§10.2.8), la conversión boxing (§10.2.9), la conversión de ajuste (§10.6) o una conversión explícita de desencapsulado (§10.6) existe desde el tipo en tiempo de compilación de E a T:
    • Si C es de un tipo de valor que no acepta valores NULL, el resultado de la operación es true.
    • De lo contrario, el resultado de la operación es equivalente a evaluar E != null.
  • De lo contrario, si existe una conversión de referencia explícita (§10.3.5) o una conversión de unboxing (§10.3.7) desde C a To si C o T es un tipo abierto (§8.4.3), se realizarán comprobaciones en tiempo de ejecución como anteriores.
  • De lo contrario, no es posible realizar ninguna referencia, conversión boxing, encapsulado o desencapsulado de E al tipo T y el resultado de la operación es false. Un compilador puede implementar optimizaciones basadas en el tipo de tiempo de compilación.

nota final

12.12.12.2 El operador is-pattern

El operador is-pattern se usa para comprobar si el valor calculado por una expresión coincide con un patrón determinado (§11). La comprobación se realiza en tiempo de ejecución. El resultado del operador is-pattern es true si el valor coincide con el patrón; de lo contrario, es false.

Para una expresión del formulario E is P, donde E es una expresión relacional de tipo T y P es un patrón, es un error en tiempo de compilación si alguno de los siguientes elementos contiene:

  • E no designa un valor o no tiene un tipo.
  • El patrón P no es aplicable (§11.2) al tipo T.

12.12.13 Operador as

El as operador se usa para convertir explícitamente un valor en un tipo de referencia determinado o un tipo de valor que acepta valores NULL. A diferencia de una expresión de conversión (§12.9.7), el as operador nunca produce una excepción. En su lugar, si la conversión indicada no es posible, el valor resultante es null.

En una operación del formulario E as T, E debe ser una expresión y T debe ser un tipo de referencia, un parámetro de tipo conocido como un tipo de referencia o un tipo de valor que acepta valores NULL. Además, al menos uno de los siguientes será true o, de lo contrario, se produce un error en tiempo de compilación:

Si el tipo en tiempo de compilación de E no dynamices , la operación E as T genera el mismo resultado que

E is T ? (T)(E) : (T)null

salvo que E solo se evalúa una vez. Se puede esperar que el compilador optimice E as T para realizar como máximo una comprobación de tipos en tiempo de ejecución en lugar de las dos comprobaciones de tipos en tiempo de ejecución implícitas por la expansión anterior.

Si el tipo de tiempo de compilación de E es , a diferencia del operador de conversión, el as operador de conversión no está enlazado dinámicamente (§12.3.3dynamic). Por lo tanto, la expansión en este caso es:

E is T ? (T)(object)(E) : (T)null

Tenga en cuenta que algunas conversiones, como las conversiones definidas por el usuario, no son posibles con el as operador y, en su lugar, deben realizarse mediante expresiones de conversión.

Ejemplo: en el ejemplo

class X
{
    public string F(object o)
    {
        return o as string;  // OK, string is a reference type
    }

    public T G<T>(object o)
        where T : Attribute
    {
        return o as T;       // Ok, T has a class constraint
    }

    public U H<U>(object o)
    {
        return o as U;       // Error, U is unconstrained
    }
}

Se sabe que el parámetro T de tipo de es un tipo de G referencia, ya que tiene la restricción de clase . El parámetro U type de H no es sin embargo; por lo tanto, no se permite el uso del as operador en H .

ejemplo final

12.13 Operadores lógicos

12.13.1 General

Los &, ^operadores , y | se denominan operadores lógicos.

and_expression
    : equality_expression
    | and_expression '&' equality_expression
    ;

exclusive_or_expression
    : and_expression
    | exclusive_or_expression '^' and_expression
    ;

inclusive_or_expression
    : exclusive_or_expression
    | inclusive_or_expression '|' exclusive_or_expression
    ;

Si un operando de un operador lógico tiene el tipo dynamicen tiempo de compilación , la expresión se enlaza dinámicamente (§12.3.3). En este caso, el tipo en tiempo de compilación de la expresión es dynamicy la resolución descrita a continuación tendrá lugar en tiempo de ejecución mediante el tipo en tiempo de ejecución de esos operandos que tienen el tipo dynamicen tiempo de compilación .

Para una operación del formulario x «op» y, donde «op» es uno de los operadores lógicos, se aplica la resolución de sobrecarga (§12.4.5) para seleccionar una implementación de operador específica. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador.

Los operadores lógicos predefinidos se describen en las subclases siguientes.

12.13.2 Operadores lógicos enteros

Los operadores lógicos enteros predefinidos son:

int operator &(int x, int y);
uint operator &(uint x, uint y);
long operator &(long x, long y);
ulong operator &(ulong x, ulong y);

int operator |(int x, int y);
uint operator |(uint x, uint y);
long operator |(long x, long y);
ulong operator |(ulong x, ulong y);

int operator ^(int x, int y);
uint operator ^(uint x, uint y);
long operator ^(long x, long y);
ulong operator ^(ulong x, ulong y);

El & operador calcula el AND lógico bit a bit de los dos operandos, el | operador calcula el OR lógico bit a bit de los dos operandos, y el ^ operador calcula el or exclusivo lógico bit a bit de los dos operandos. No se pueden desbordar estas operaciones.

Las formas levantadas (§12.4.8) de los operadores lógicos enteros predefinidos sin levantar definidos anteriormente también están predefinidos.

12.13.3 Operadores lógicos de enumeración

Cada tipo E de enumeración proporciona implícitamente los siguientes operadores lógicos predefinidos:

E operator &(E x, E y);
E operator |(E x, E y);
E operator ^(E x, E y);

El resultado de evaluar x «op» y, donde x y y son expresiones de un tipo E de enumeración con un tipo Usubyacente , y «op» es uno de los operadores lógicos, es exactamente igual que evaluar (E)((U)x «op» (U)y). En otras palabras, los operadores lógicos de tipo de enumeración simplemente realizan la operación lógica en el tipo subyacente de los dos operandos.

Las formas levantadas (§12.4.8) de los operadores lógicos de enumeración predefinidos no eliminados definidos anteriormente también están predefinidos.

12.13.4 Operadores lógicos booleanos

Los operadores lógicos booleanos predefinidos son:

bool operator &(bool x, bool y);
bool operator |(bool x, bool y);
bool operator ^(bool x, bool y);

El resultado de x & y es true si tanto x como y son true. De lo contrario, el resultado es false.

El resultado de x | y es true si o x y es true. De lo contrario, el resultado es false.

El resultado de x ^ y es true si x es y y es falsetrue , o x es false y y es true. De lo contrario, el resultado es false. Cuando los operandos son de tipo bool, el ^ operador calcula el mismo resultado que el != operador .

12.13.5 Boolean y | Operadores

El tipo bool? booleano que acepta valores NULL puede representar tres valores, true, falsey null.

Al igual que con los otros operadores binarios, las formas levantadas de los operadores & lógicos y | (§12.13.4) también se definen previamente:

bool? operator &(bool? x, bool? y);
bool? operator |(bool? x, bool? y);

La semántica de los operadores elevados & y | se define en la tabla siguiente:

x y x & y x \| y
true true true true
true false false true
true null null true
false true false true
false false false false
false null false null
null true null true
null false false null
null null null null

Nota: El bool? tipo es conceptualmente similar al tipo de tres valores que se usa para expresiones booleanas en SQL. La tabla anterior sigue la misma semántica que SQL, mientras que aplicar las reglas de §12.4.8 a los & operadores y | no. Las reglas de §12.4.8 ya proporcionan semántica similar a SQL para el operador elevado ^ . nota final

12.14 Operadores lógicos condicionales

12.14.1 General

Los operadores && y || se denominan operadores lógicos condicionales. También se denominan operadores lógicos de cortocircuito.

conditional_and_expression
    : inclusive_or_expression
    | conditional_and_expression '&&' inclusive_or_expression
    ;

conditional_or_expression
    : conditional_and_expression
    | conditional_or_expression '||' conditional_and_expression
    ;

Los && operadores y || son versiones condicionales de los & operadores y | :

  • La operación x && y corresponde a la operación x & y, excepto que y solo se evalúa si x no falsees .
  • La operación x || y corresponde a la operación x | y, excepto que y solo se evalúa si x no truees .

Nota: La razón por la que el cortocircuito usa las condiciones "not true" y "not false" es permitir que los operadores condicionales definidos por el usuario definan cuándo se aplica el cortocircuito. Los tipos definidos por el usuario podrían estar en un estado donde operator true devuelve false y operator false devuelve false. En esos casos, ni && tampoco || cortocircuito. nota final

Si un operando de un operador lógico condicional tiene el tipo dynamicen tiempo de compilación , la expresión se enlaza dinámicamente (§12.3.3). En este caso, el tipo en tiempo de compilación de la expresión es dynamicy la resolución descrita a continuación tendrá lugar en tiempo de ejecución mediante el tipo en tiempo de ejecución de esos operandos que tienen el tipo dynamicen tiempo de compilación .

Una operación del formulario x && y o x || y se procesa aplicando la resolución de sobrecarga (§12.4.5) como si la operación se escribiera x & y o x | y. A continuación,

  • Si la resolución de sobrecarga no encuentra un único operador mejor o si la resolución de sobrecarga selecciona uno de los operadores lógicos enteros predefinidos o operadores lógicos booleanos que aceptan valores NULL (§12.13.5), se produce un error en tiempo de enlace.
  • De lo contrario, si el operador seleccionado es uno de los operadores lógicos booleanos predefinidos (§12.13.4), la operación se procesa como se describe en §12.14.2.
  • De lo contrario, el operador seleccionado es un operador definido por el usuario y la operación se procesa como se describe en §12.14.3.

No es posible sobrecargar directamente los operadores lógicos condicionales. Sin embargo, dado que los operadores lógicos condicionales se evalúan en términos de los operadores lógicos normales, las sobrecargas de los operadores lógicos normales son, con ciertas restricciones, también consideradas sobrecargas de los operadores lógicos condicionales. Esto se describe más adelante en §12.14.3.

12.14.2 Operadores lógicos condicionales booleanos

Cuando los operandos de && o || son de tipo bool, o cuando los operandos son de tipos que no definen un objeto aplicable operator & o operator |, pero definen conversiones implícitas en bool, la operación se procesa de la siguiente manera:

  • La operación x && y se evalúa como x ? y : false. En otras palabras, x primero se evalúa y se convierte en el tipo bool. A continuación, si x es true, y se evalúa y se convierte en el tipo booly se convierte en el resultado de la operación. De lo contrario, el resultado de la operación es false.
  • La operación x || y se evalúa como x ? true : y. En otras palabras, x primero se evalúa y se convierte en el tipo bool. A continuación, si x es true, el resultado de la operación es true. De lo contrario, y se evalúa y se convierte en el tipo booly se convierte en el resultado de la operación.

12.14.3 Operadores lógicos condicionales definidos por el usuario

Cuando los operandos de && o son de tipos que declaran un usuario definido por operator & el usuario aplicable o operator |, ambos de los siguientes serán true, donde T es el tipo en el que se declara el operador || seleccionado:

  • El tipo de valor devuelto y el tipo de cada parámetro del operador seleccionado serán T. Es decir, el operador calculará el AND lógico o el OR lógico de dos operandos de tipo Ty devolverá un resultado de tipo T.
  • T contendrá declaraciones de operator true y operator false.

Se produce un error en tiempo de enlace si no se cumple alguno de estos requisitos. De lo contrario, la && operación o || se evalúa combinando el operador definido operator true por el usuario o operator false con el operador definido por el usuario seleccionado:

  • La operación x && y se evalúa como T.false(x) ? x : T.&(x, y), donde T.false(x) es una invocación del operator false declarado en Ty T.&(x, y) es una invocación del objeto seleccionado operator &. En otras palabras, x se evalúa primero y operator false se invoca en el resultado para determinar si x es definitivamente false. A continuación, si x definitivamente es false, el resultado de la operación es el valor calculado previamente para x. De lo contrario, y se evalúa y se invoca al seleccionado operator & en el valor calculado previamente para x y el valor calculado para generar y el resultado de la operación.
  • La operación x || y se evalúa como T.true(x) ? x : T.|(x, y), donde T.true(x) es una invocación del operator true declarado en Ty T.|(x, y) es una invocación del objeto seleccionado operator |. En otras palabras, x primero se evalúa y operator true se invoca en el resultado para determinar si x es definitivamente true. A continuación, si x definitivamente es true, el resultado de la operación es el valor calculado previamente para x. De lo contrario, y se evalúa y se invoca al seleccionado operator | en el valor calculado previamente para x y el valor calculado para generar y el resultado de la operación.

En cualquiera de estas operaciones, la expresión dada por x solo se evalúa una vez y la expresión dada por y no se evalúa o evalúa exactamente una vez.

12.15 Operador de fusión null

El ?? operador se denomina operador de fusión null.

null_coalescing_expression
    : conditional_or_expression
    | conditional_or_expression '??' null_coalescing_expression
    | throw_expression
    ;

En una expresión de fusión nula del formato a ?? b, si a nonull es , el resultado es a; de lo contrario, el resultado es b. La operación solo se evalúa b si a es null.

El operador de fusión null es asociativo a la derecha, lo que significa que las operaciones se agrupan de derecha a izquierda.

Ejemplo: Una expresión del formulario a ?? b ?? c se evalúa como .?? (b ?? c) En términos generales, una expresión del formulario E1 ?? E2 ?? ... ?? EN devuelve el primero de los operandos que nonull son , o null si todos los operandos son null. ejemplo final

El tipo de la expresión a ?? b depende de las conversiones implícitas disponibles en los operandos. En orden de preferencia, el tipo de a ?? b es A₀, Ao B, donde A es el tipo de (siempre que a tenga un tipo), B es el tipo de b(siempre que b tenga un tipo) y A₀ es el tipo subyacente de A si A es un tipo de a valor que acepta valores NULL o A de otro modo. En concreto, a ?? b se procesa de la siguiente manera:

  • Si A existe y no es un tipo de valor que acepta valores NULL o un tipo de referencia, se produce un error en tiempo de compilación.
  • De lo contrario, si A existe y b es una expresión dinámica, el tipo de resultado es dynamic. En tiempo de ejecución, a se evalúa primero. Si a no nulles , a se convierte en dynamicy se convierte en el resultado. De lo contrario, b se evalúa y se convierte en el resultado.
  • De lo contrario, si A existe y es un tipo de valor que acepta valores NULL y existe una conversión implícita de b a A₀, el tipo de resultado es A₀. En tiempo de ejecución, a se evalúa primero. Si a no nulles , a se desencapsula en el tipo A₀y se convierte en el resultado. De lo contrario, b se evalúa y se convierte en el tipo A₀y se convierte en el resultado.
  • De lo contrario, si A existe y existe una conversión implícita de b a A, el tipo de resultado es A. En tiempo de ejecución, se evalúa por primera vez. Si un objeto no es NULL, se convierte en el resultado. De lo contrario, b se evalúa y se convierte en el tipo Ay se convierte en el resultado.
  • De lo contrario, si A existe y es un tipo de valor que acepta valores NULL, b tiene un tipo B y existe una conversión implícita de A₀ a B, el tipo de resultado es B. En tiempo de ejecución, a se evalúa primero. Si a no es , a se desencapsula al tipo A₀ y se convierte en el tipo By se convierte nullen el resultado. De lo contrario, b se evalúa y se convierte en el resultado.
  • De lo contrario, si b tiene un tipo B y existe una conversión implícita de a a B, el tipo de resultado es B. En tiempo de ejecución, a se evalúa primero. Si a no es , a se convierte en el tipo By se convierte nullen el resultado. De lo contrario, b se evalúa y se convierte en el resultado.

De lo contrario, a y b son incompatibles y a se produce un error en tiempo de compilación.

12.16 Operador de expresión throw

throw_expression
    : 'throw' null_coalescing_expression
    ;

Un throw_expression produce el valor generado mediante la evaluación del null_coalescing_expression. La expresión se convertirá implícitamente en System.Exceptiony el resultado de evaluar la expresión se convierte en System.Exception antes de iniciarse. El comportamiento en tiempo de ejecución de la evaluación de una expresión throw es el mismo que se especifica para una instrucción throw (§13.10.6).

Un throw_expression no tiene ningún tipo. Un throw_expression se puede convertir en cada tipo mediante una conversión de inicio implícita.

Una expresión throw solo se producirá en los siguientes contextos sintácticos:

  • Como segundo o tercer operando de un operador condicional ternario (?:).
  • Como segundo operando de un operador de fusión null (??).
  • Como cuerpo de una expresión lambda o miembro.

12.17 Expresiones de declaración

Una expresión de declaración declara una variable local.

declaration_expression
    : local_variable_type identifier
    ;

local_variable_type
    : type
    | 'var'
    ;

El simple_name _ también se considera una expresión de declaración si la búsqueda de nombres simple no encontró una declaración asociada (§12.8.4). Cuando se usa como expresión de declaración, _ se denomina descarte simple. Es semánticamente equivalente a var _, pero se permite en más lugares.

Una expresión de declaración solo se producirá en los siguientes contextos sintácticos:

  • Como argument_value out en un argument_list.
  • Como descarte _ simple que comprende el lado izquierdo de una asignación simple (§12.21.2).
  • Como tuple_element en uno o varios tuple_expressionanidados recursivamente, el exterior más externo del cual consta del lado izquierdo de una asignación deconstrucción. Un deconstruction_expression da lugar a expresiones de declaración en esta posición, aunque las expresiones de declaración no estén presentes sintácticamente.

Nota: Esto significa que una expresión de declaración no se puede paréntesis. nota final

Se trata de un error para una variable con tipo implícito declarada con un declaration_expression al que se hace referencia dentro de la argument_list donde se declara.

Se trata de un error para una variable declarada con una declaration_expression a la que se hace referencia dentro de la asignación deconstrucción donde se produce.

Expresión de declaración que es un descarte simple o donde el local_variable_type es el identificador var se clasifica como una variable con tipo implícito. La expresión no tiene ningún tipo y el tipo de la variable local se deduce en función del contexto sintáctico como se indica a continuación:

  • En un argument_list el tipo inferido de la variable es el tipo declarado del parámetro correspondiente.
  • Como lado izquierdo de una asignación simple, el tipo inferido de la variable es el tipo del lado derecho de la asignación.
  • En un tuple_expression del lado izquierdo de una asignación simple, el tipo inferido de la variable es el tipo del elemento de tupla correspondiente en el lado derecho (después de la deconstrucción) de la asignación.

De lo contrario, la expresión de declaración se clasifica como una variable con tipo explícito y el tipo de la expresión, así como la variable declarada, será dada por el local_variable_type.

Una expresión de declaración con el identificador _ es un descarte (§9.2.9.1) y no introduce un nombre para la variable. Una expresión de declaración con un identificador _ distinto de introduce ese nombre en el espacio de declaración de variable local más cercano (§7.3).

Ejemplo:

string M(out int i, string s, out bool b) { ... }

var s1 = M(out int i1, "One", out var b1);
Console.WriteLine($"{i1}, {b1}, {s1}");
// Error: i2 referenced within declaring argument list
var s2 = M(out var i2, M(out i2, "Two", out bool b2), out b2);
var s3 = M(out int _, "Three", out var _);

La declaración de muestra expresiones de s1 declaración explícita e implícitamente tipadas. El tipo deducido de b1 es bool porque es el tipo del parámetro de salida correspondiente en M1. El siguiente WriteLine es capaz de acceder i1 a y b1, que se han introducido en el ámbito envolvente.

La declaración de muestra un intento de usar i2 en la llamada anidada a M, que no se permite, porque la referencia se produce dentro de s2 la lista de argumentos donde i2 se declaró. Por otro lado, se permite la referencia a b2 en el argumento final, porque se produce después del final de la lista de argumentos anidados donde b2 se declaró.

La declaración de s3 muestra el uso de expresiones de declaración implícitas y explícitamente tipadas que se descartan. Dado que los descartes no declaran una variable con nombre, se permiten varias apariciones del identificador _ .

(int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5);

En este ejemplo se muestra el uso de expresiones de declaración implícita y explícitamente tipadas para variables y descartes en una asignación de deconstrucción. El simple_name _ es equivalente a var _ cuando no se encuentra ninguna declaración de _ .

void M1(out int i) { ... }

void M2(string _)
{
    M1(out _);      // Error: `_` is a string
    M1(out var _);
}

En este ejemplo se muestra el uso de var _ para proporcionar un descarte con tipo implícito cuando _ no está disponible, ya que designa una variable en el ámbito envolvente.

ejemplo final

Operador condicional 12.18

El ?: operador se denomina operador condicional. También se denomina operador ternario.

conditional_expression
    : null_coalescing_expression
    | null_coalescing_expression '?' expression ':' expression
    | null_coalescing_expression '?' 'ref' variable_reference ':'
      'ref' variable_reference
    ;

No se permite una expresión throw (§12.16) en un operador condicional si ref está presente.

Una expresión condicional del formulario b ? x : y evalúa primero la condición b. A continuación, si b es true, x se evalúa y se convierte en el resultado de la operación. De lo contrario, y se evalúa y se convierte en el resultado de la operación. Una expresión condicional nunca evalúa y x y.

El operador condicional es asociativo a la derecha, lo que significa que las operaciones se agrupan de derecha a izquierda.

Ejemplo: una expresión del formulario a ? b : c ? d : e se evalúa como a ? b : (c ? d : e). ejemplo final

El primer operando del ?: operador será una expresión que se pueda convertir implícitamente en bool, o una expresión de un tipo que implemente operator true. Si no se cumple ninguno de estos requisitos, se produce un error en tiempo de compilación.

Si ref está presente:

  • Debe existir una conversión de identidad entre los tipos de los dos variable_references y el tipo del resultado puede ser cualquier tipo. Si cualquiera de los tipos es dynamic, la inferencia de tipos prefiere dynamic (§8.7). Si cualquiera de los tipos es un tipo de tupla (§8.3.11), la inferencia de tipos incluye los nombres de elemento cuando los nombres de elemento de la misma posición ordinal coinciden en ambas tuplas.
  • El resultado es una referencia variable, que se puede escribir si ambos variable_referencese pueden escribir.

Nota: Cuando ref está presente, el conditional_expression devuelve una referencia de variable, que se puede asignar a una variable de referencia mediante el = ref operador o que se pasa como parámetro reference/input/output. nota final

Si ref no está presente, los operandos segundo y tercero, x y y, del ?: operador controlan el tipo de la expresión condicional:

  • Si x tiene el tipo X y y tiene ese tipo Y ,
    • Si existe una conversión de identidad entre X y Y, el resultado es el mejor tipo común de un conjunto de expresiones (§12.6.3.15). Si cualquiera de los tipos es dynamic, la inferencia de tipos prefiere dynamic (§8.7). Si cualquiera de los tipos es un tipo de tupla (§8.3.11), la inferencia de tipos incluye los nombres de elemento cuando los nombres de elemento de la misma posición ordinal coinciden en ambas tuplas.
    • De lo contrario, si existe una conversión implícita (§10.2) de X a Y, pero no de Y a X, entonces Y es el tipo de la expresión condicional.
    • De lo contrario, si existe una conversión de enumeración implícita (§10.2.4) desde X a Y, entonces Y es el tipo de la expresión condicional.
    • De lo contrario, si existe una conversión de enumeración implícita (§10.2.4) desde Y a X, entonces X es el tipo de la expresión condicional.
    • De lo contrario, si existe una conversión implícita (§10.2) de Y a X, pero no de X a Y, entonces X es el tipo de la expresión condicional.
    • De lo contrario, no se puede determinar ningún tipo de expresión y se produce un error en tiempo de compilación.
  • Si solo uno de x y y tiene un tipo, y ambos x y y se convierten implícitamente en ese tipo, ese es el tipo de la expresión condicional.
  • De lo contrario, no se puede determinar ningún tipo de expresión y se produce un error en tiempo de compilación.

El procesamiento en tiempo de ejecución de una expresión condicional ref del formulario b ? ref x : ref y consta de los pasos siguientes:

  • En primer lugar, b se evalúa y el bool valor de b se determina:
    • Si existe una conversión implícita del tipo de b a bool , esta conversión implícita se realiza para generar un bool valor.
    • De lo contrario, se invoca el operator true definido por el tipo de b para generar un bool valor.
  • Si el bool valor generado por el paso anterior es true, x se evalúa y la referencia de variable resultante se convierte en el resultado de la expresión condicional.
  • De lo contrario, y se evalúa y la referencia de variable resultante se convierte en el resultado de la expresión condicional.

El procesamiento en tiempo de ejecución de una expresión condicional del formulario b ? x : y consta de los pasos siguientes:

  • En primer lugar, b se evalúa y el bool valor de b se determina:
    • Si existe una conversión implícita del tipo de b a bool , esta conversión implícita se realiza para generar un bool valor.
    • De lo contrario, se invoca el operator true definido por el tipo de b para generar un bool valor.
  • Si el bool valor generado por el paso anterior es true, x se evalúa y convierte al tipo de la expresión condicional y se convierte en el resultado de la expresión condicional.
  • De lo contrario, y se evalúa y se convierte en el tipo de la expresión condicional y se convierte en el resultado de la expresión condicional.

12.19 Expresiones de función anónimas

12.19.1 General

Una función anónima es una expresión que representa una definición de método "en línea". Una función anónima no tiene un valor o tipo en y de sí mismo, pero se puede convertir a un tipo de delegado o árbol de expresión compatible. La evaluación de una conversión de función anónima depende del tipo de destino de la conversión: si es un tipo delegado, la conversión se evalúa como un valor delegado que hace referencia al método que define la función anónima. Si es un tipo de árbol de expresión, la conversión se evalúa como un árbol de expresión que representa la estructura del método como una estructura de objeto.

Nota: Por razones históricas, hay dos tipos sintácticos de funciones anónimas, es decir , lambda_expressions y anonymous_method_expressions. Para casi todos los propósitos, lambda_expressions son más concisos y expresivos que anonymous_method_expressions, que permanecen en el lenguaje para la compatibilidad con versiones anteriores. nota final

lambda_expression
    : 'async'? anonymous_function_signature '=>' anonymous_function_body
    ;

anonymous_method_expression
    : 'async'? 'delegate' explicit_anonymous_function_signature? block
    ;

anonymous_function_signature
    : explicit_anonymous_function_signature
    | implicit_anonymous_function_signature
    ;

explicit_anonymous_function_signature
    : '(' explicit_anonymous_function_parameter_list? ')'
    ;

explicit_anonymous_function_parameter_list
    : explicit_anonymous_function_parameter
      (',' explicit_anonymous_function_parameter)*
    ;

explicit_anonymous_function_parameter
    : anonymous_function_parameter_modifier? type identifier
    ;

anonymous_function_parameter_modifier
    : 'ref'
    | 'out'
    | 'in'
    ;

implicit_anonymous_function_signature
    : '(' implicit_anonymous_function_parameter_list? ')'
    | implicit_anonymous_function_parameter
    ;

implicit_anonymous_function_parameter_list
    : implicit_anonymous_function_parameter
      (',' implicit_anonymous_function_parameter)*
    ;

implicit_anonymous_function_parameter
    : identifier
    ;

anonymous_function_body
    : null_conditional_invocation_expression
    | expression
    | 'ref' variable_reference
    | block
    ;

Al reconocer una anonymous_function_body si se aplican las alternativas de null_conditional_invocation_expression y expresión , se elegirá la primera.

Nota: La superposición de, y la prioridad entre, las alternativas aquí son únicamente para comodidad descriptiva; las reglas gramaticales se pueden elaborar para quitar la superposición. ANTLR y otros sistemas gramaticales adoptan la misma comodidad, por lo que anonymous_function_body tiene automáticamente la semántica especificada. nota final

Nota: Cuando se trata como una expresión, una forma sintáctica como x?.M() sería un error si el tipo de resultado de M es void (§12.8.13). Pero cuando se trata como un null_conditional_invocation_expression, se permite que el tipo de resultado sea void. nota final

Ejemplo: el tipo de resultado de List<T>.Reverse es void. En el código siguiente, el cuerpo de la expresión anónima es un null_conditional_invocation_expression, por lo que no es un error.

Action<List<int>> a = x => x?.Reverse();

ejemplo final

El => operador tiene la misma prioridad que la asignación (=) y es asociativo a la derecha.

Una función anónima con el async modificador es una función asincrónica y sigue las reglas descritas en §15.15.

Los parámetros de una función anónima en forma de lambda_expression se pueden escribir explícita o implícitamente. En una lista de parámetros con tipo explícito, el tipo de cada parámetro se indica explícitamente. En una lista de parámetros con tipo implícito, los tipos de los parámetros se deducen del contexto en el que se produce la función anónima, en concreto, cuando la función anónima se convierte en un tipo de delegado compatible o tipo de árbol de expresión, ese tipo proporciona los tipos de parámetro (§10.7).

En un lambda_expression con un único parámetro con tipo implícito, los paréntesis se pueden omitir de la lista de parámetros. En otras palabras, una función anónima del formulario

( «param» ) => «expr»

se puede abreviar a

«param» => «expr»

La lista de parámetros de una función anónima en forma de anonymous_method_expression es opcional. Si se especifica, los parámetros se escribirán explícitamente. Si no es así, la función anónima se puede convertir a un delegado con cualquier lista de parámetros que no contenga parámetros de salida.

Un cuerpo de bloque de una función anónima siempre es accesible (§13.2).

Ejemplo: a continuación se muestran algunos ejemplos de funciones anónimas:

x => x + 1                             // Implicitly typed, expression body
x => { return x + 1; }                 // Implicitly typed, block body
(int x) => x + 1                       // Explicitly typed, expression body
(int x) => { return x + 1; }           // Explicitly typed, block body
(x, y) => x * y                        // Multiple parameters
() => Console.WriteLine()              // No parameters
async (t1,t2) => await t1 + await t2   // Async
delegate (int x) { return x + 1; }     // Anonymous method expression
delegate { return 1 + 1; }             // Parameter list omitted

ejemplo final

El comportamiento de lambda_expressions y anonymous_method_expressions es el mismo, excepto en los puntos siguientes:

  • anonymous_method_expression permite que la lista de parámetros se omita por completo, lo que produce la convertibilidad en tipos delegados de cualquier lista de parámetros de valor.
  • lambda_expression permite que los tipos de parámetro se omitan e infieren, mientras que anonymous_method_expressions requieren que los tipos de parámetro se indiquen explícitamente.
  • El cuerpo de un lambda_expression puede ser una expresión o un bloque, mientras que el cuerpo de un anonymous_method_expression será un bloque.
  • Solo lambda_expressiontienen conversiones a tipos de árbol de expresión compatibles (§8.6).

12.19.2 Firmas de función anónimas

El anonymous_function_signature de una función anónima define los nombres y, opcionalmente, los tipos de los parámetros para la función anónima. El ámbito de los parámetros de la función anónima es el anonymous_function_body (§7.7). Junto con la lista de parámetros (si se indica), el cuerpo del método anónimo constituye un espacio de declaración (§7.3). Por lo tanto, es un error en tiempo de compilación para que el nombre de un parámetro de la función anónima coincida con el nombre de una variable local, una constante local o un parámetro cuyo ámbito incluya el anonymous_method_expression o lambda_expression.

Si una función anónima tiene un explicit_anonymous_function_signature, el conjunto de tipos de delegado y tipos de árbol de expresión compatibles está restringido a los que tienen los mismos tipos de parámetros y modificadores en el mismo orden (§10.7). A diferencia de las conversiones de grupo de métodos (§10.8), no se admite la varianza contra de los tipos de parámetros de función anónimos. Si una función anónima no tiene un anonymous_function_signature, el conjunto de tipos de delegado y tipos de árbol de expresión compatibles está restringido a los que no tienen parámetros de salida.

Tenga en cuenta que un anonymous_function_signature no puede incluir atributos ni una matriz de parámetros. Sin embargo, un anonymous_function_signature puede ser compatible con un tipo de delegado cuya lista de parámetros contiene una matriz de parámetros.

Tenga en cuenta también que la conversión a un tipo de árbol de expresión, incluso si es compatible, puede producirse un error en tiempo de compilación (§8.6).

12.19.3 Cuerpos de función anónimos

El cuerpo (expresión o bloque) de una función anónima está sujeto a las siguientes reglas:

  • Si la función anónima incluye una firma, los parámetros especificados en la firma están disponibles en el cuerpo. Si la función anónima no tiene ninguna firma, se puede convertir en un tipo delegado o un tipo de expresión que tenga parámetros (§10.7), pero no se puede tener acceso a los parámetros en el cuerpo.
  • Excepto los parámetros por referencia especificados en la firma (si existe) de la función anónima más cercana, es un error en tiempo de compilación para que el cuerpo acceda a un parámetro by-reference.
  • Excepto en el caso de los parámetros especificados en la firma (si existe) de la función anónima más cercana, se trata de un error en tiempo de compilación para que el cuerpo acceda a un parámetro de un ref struct tipo.
  • Cuando el tipo de this es un tipo de estructura, es un error en tiempo de compilación para que el cuerpo acceda thisa . Esto es cierto si el acceso es explícito (como en this.x) o implícito (como en donde x x es un miembro de instancia de la estructura). Esta regla simplemente prohíbe este acceso y no afecta a si la búsqueda de miembros da como resultado un miembro de la estructura.
  • El cuerpo tiene acceso a las variables externas (§12.19.6) de la función anónima. El acceso de una variable externa hará referencia a la instancia de la variable que está activa en el momento en que se evalúa el lambda_expression o anonymous_method_expression (§12.19.7).
  • Es un error en tiempo de compilación para que el cuerpo contenga una goto instrucción, una break instrucción o una continue instrucción cuyo destino está fuera del cuerpo o dentro del cuerpo de una función anónima independiente.
  • Una return instrucción del cuerpo devuelve el control de una invocación de la función anónima más cercana, no del miembro de función envolvente.

No se especifica explícitamente si hay alguna manera de ejecutar el bloque de una función anónima que no sea a través de la evaluación e invocación de la lambda_expression o anonymous_method_expression. En concreto, el compilador puede optar por implementar una función anónima mediante la síntesis de uno o varios métodos o tipos con nombre. Los nombres de cualquier elemento sintetizado de este tipo serán de un formulario reservado para el uso del compilador (§6.4.3).

12.19.4 Resolución de sobrecarga

Las funciones anónimas de una lista de argumentos participan en la inferencia de tipos y la resolución de sobrecargas. Consulte §12.6.3 y §12.6.4 para conocer las reglas exactas.

Ejemplo: en el ejemplo siguiente se muestra el efecto de las funciones anónimas en la resolución de sobrecargas.

class ItemList<T> : List<T>
{
    public int Sum(Func<T, int> selector)
    {
        int sum = 0;
        foreach (T item in this)
        {
            sum += selector(item);
        }
        return sum;
    }

    public double Sum(Func<T, double> selector)
    {
        double sum = 0;
        foreach (T item in this)
        {
            sum += selector(item);
        }
        return sum;
    }
}

La ItemList<T> clase tiene dos Sum métodos. Cada toma un selector argumento, que extrae el valor para sumar de un elemento de lista. El valor extraído puede ser o int y double la suma resultante es del mismo modo o int .double

Los Sum métodos pueden usarse, por ejemplo, para calcular sumas de una lista de líneas de detalle en un orden.

class Detail
{
    public int UnitCount;
    public double UnitPrice;
    ...
}

class A
{
    void ComputeSums()
    {
        ItemList<Detail> orderDetails = GetOrderDetails( ... );
        int totalUnits = orderDetails.Sum(d => d.UnitCount);
        double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount);
        ...
    }

    ItemList<Detail> GetOrderDetails( ... )
    {
        ...
    }
}

En la primera invocación de orderDetails.Sum, ambos Sum métodos son aplicables porque la función d => d.UnitCount anónima es compatible con y Func<Detail,int> Func<Detail,double>. Sin embargo, la resolución de sobrecarga elige el primer Sum método porque la conversión a Func<Detail,int> es mejor que la conversión a Func<Detail,double>.

En la segunda invocación de orderDetails.Sum, solo se aplica el segundo Sum método porque la función d => d.UnitPrice * d.UnitCount anónima genera un valor de tipo double. Por lo tanto, la resolución de sobrecarga elige el segundo Sum método para esa invocación.

ejemplo final

12.19.5 Funciones anónimas y enlace dinámico

Una función anónima no puede ser un receptor, argumento o operando de una operación enlazada dinámicamente.

12.19.6 Variables externas

12.19.6.1 General

Cualquier variable local, parámetro de valor o matriz de parámetros cuyo ámbito incluya el lambda_expression o anonymous_method_expression se denomina variable externa de la función anónima. En un miembro de función de instancia de una clase, este valor se considera un parámetro value y es una variable externa de cualquier función anónima contenida en el miembro de función.

12.19.6.2 Variables externas capturadas

Cuando una función anónima hace referencia a una variable externa, se dice que la función anónima capturó la variable externa. Normalmente, la duración de una variable local se limita a la ejecución del bloque o instrucción con la que está asociado (§9.2.9). Sin embargo, la duración de una variable externa capturada se extiende al menos hasta que el árbol delegado o de expresión creado a partir de la función anónima es apto para la recolección de elementos no utilizados.

Ejemplo: en el ejemplo

delegate int D();

class Test
{
    static D F()
    {
        int x = 0;
        D result = () => ++x;
        return result;
    }

    static void Main()
    {
        D d = F();
        Console.WriteLine(d());
        Console.WriteLine(d());
        Console.WriteLine(d());
    }
}

La función anónima captura la variable x local y la duración de x se extiende al menos hasta que el delegado devuelto de F se convierte en apto para la recolección de elementos no utilizados. Dado que cada invocación de la función anónima funciona en la misma instancia de x, la salida del ejemplo es:

1
2
3

ejemplo final

Cuando una función anónima captura una variable local o un parámetro de valor, la variable local o el parámetro ya no se considera una variable fija (§23.4), pero en su lugar se considera una variable desplazable. Sin embargo, las variables externas capturadas no se pueden usar en una fixed instrucción (§23.7), por lo que no se puede tomar la dirección de una variable externa capturada.

Nota: A diferencia de una variable no capturada, una variable local capturada se puede exponer simultáneamente a varios subprocesos de ejecución. nota final

12.19.6.3 Creación de instancias de variables locales

Se considera que se crea una instancia de una variable local cuando la ejecución entra en el ámbito de la variable.

Ejemplo: por ejemplo, cuando se invoca el método siguiente, se crea una instancia de la variable x local e se inicializa tres veces, una vez para cada iteración del bucle.

static void F()
{
    for (int i = 0; i < 3; i++)
    {
        int x = i * 2 + 1;
        ...
    }
}

Sin embargo, mover la declaración de x fuera del bucle da como resultado una única creación de instancias de x:

static void F()
{
    int x;
    for (int i = 0; i < 3; i++)
    {
        x = i * 2 + 1;
        ...
    }
}

ejemplo final

Cuando no se captura, no hay ninguna manera de observar exactamente la frecuencia con la que se crea una instancia de una variable local, ya que las duraciones de las instancias son desasociadas, es posible que cada instancia use simplemente la misma ubicación de almacenamiento. Sin embargo, cuando una función anónima captura una variable local, los efectos de la creación de instancias se vuelven evidentes.

Ejemplo: El ejemplo

delegate void D();
class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        for (int i = 0; i < 3; i++)
        {
            int x = i * 2 + 1;
            result[i] = () => Console.WriteLine(x);
        }
        return result;
    }

    static void Main()
    {
        foreach (D d in F())
        {
            d();
        }
    }
}

genera el resultado:

1
3
5

Sin embargo, cuando la declaración de x se mueve fuera del bucle:

delegate void D();

class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        int x;
        for (int i = 0; i < 3; i++)
        {
            x = i * 2 + 1;
            result[i] = () => Console.WriteLine(x);
        }
        return result;
   }

   static void Main()
   {
       foreach (D d in F())
       {
           d();
       }
   }
}

la salida es:

5
5
5

Tenga en cuenta que el compilador está permitido (pero no necesario) para optimizar las tres instancias en una sola instancia de delegado (§10.7.2).

ejemplo final

Si un bucle for declara una variable de iteración, esa variable se considera declarada fuera del bucle.

Ejemplo: Por lo tanto, si se cambia el ejemplo para capturar la propia variable de iteración:

delegate void D();

class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        for (int i = 0; i < 3; i++)
        {
            result[i] = () => Console.WriteLine(i);
        }
        return result;
   }

   static void Main()
   {
       foreach (D d in F())
       {
           d();
       }
   }
}

solo se captura una instancia de la variable de iteración, que genera la salida:

3
3
3

ejemplo final

Es posible que los delegados de función anónimos compartan algunas variables capturadas pero tienen instancias independientes de otras.

Ejemplo: por ejemplo, si F se cambia a

static D[] F()
{
    D[] result = new D[3];
    int x = 0;
    for (int i = 0; i < 3; i++)
    {
        int y = 0;
        result[i] = () => Console.WriteLine($"{++x} {++y}");
    }
    return result;
}

los tres delegados capturan la misma instancia de x pero separan instancias de yy la salida es:

1 1
2 1
3 1

ejemplo final

Las funciones anónimas independientes pueden capturar la misma instancia de una variable externa.

Ejemplo: en el ejemplo:

delegate void Setter(int value);
delegate int Getter();

class Test
{
    static void Main()
    {
        int x = 0;
        Setter s = (int value) => x = value;
        Getter g = () => x;
        s(5);
        Console.WriteLine(g());
        s(10);
        Console.WriteLine(g());
    }
}

las dos funciones anónimas capturan la misma instancia de la variable xlocal y, por tanto, pueden "comunicarse" a través de esa variable. La salida del ejemplo es:

5
10

ejemplo final

12.19.7 Evaluación de expresiones de función anónimas

Una función F anónima siempre se convertirá en un tipo D delegado o en un tipo Ede árbol de expresión, ya sea directamente o a través de la ejecución de una expresión new D(F)de creación de delegados . Esta conversión determina el resultado de la función anónima, como se describe en §10.7.

Ejemplo de implementación 12.19.8

Esta subclausa es informativa.

Esta subclause describe una posible implementación de conversiones de funciones anónimas en términos de otras construcciones de C#. La implementación descrita aquí se basa en los mismos principios que usa un compilador comercial de C#, pero no es por ningún medio una implementación obligatoria, ni es la única posible. Solo menciona brevemente conversiones a árboles de expresión, ya que su semántica exacta está fuera del ámbito de esta especificación.

El resto de esta subclausa proporciona varios ejemplos de código que contiene funciones anónimas con características diferentes. Para cada ejemplo, se proporciona una traducción correspondiente al código que usa solo otras construcciones de C#. En los ejemplos, se supone que el identificador D representa el siguiente tipo de delegado:

public delegate void D();

La forma más sencilla de una función anónima es una que no captura variables externas:

delegate void D();

class Test
{
    static void F()
    {
        D d = () => Console.WriteLine("test");
    }
}

Esto se puede traducir a una instancia de delegado que hace referencia a un método estático generado por el compilador en el que se coloca el código de la función anónima:

delegate void D();

class Test
{
    static void F()
    {
        D d = new D(__Method1);
    }

    static void __Method1()
    {
        Console.WriteLine("test");
    }
}

En el ejemplo siguiente, la función anónima hace referencia a los miembros de instancia de this:

delegate void D();

class Test
{
    int x;

    void F()
    {
        D d = () => Console.WriteLine(x);
    }
}

Esto se puede traducir a un método de instancia generado por el compilador que contiene el código de la función anónima:

delegate void D();

class Test
{
   int x;

   void F()
   {
       D d = new D(__Method1);
   }

   void __Method1()
   {
       Console.WriteLine(x);
   }
}

En este ejemplo, la función anónima captura una variable local:

delegate void D();

class Test
{
    void F()
    {
        int y = 123;
        D d = () => Console.WriteLine(y);
    }
}

La duración de la variable local debe extenderse ahora al menos a la duración del delegado de función anónima. Esto se puede lograr mediante la "elevación" de la variable local en un campo de una clase generada por el compilador. La creación de instancias de la variable local (§12.19.6.3) corresponde a la creación de una instancia de la clase generada por el compilador y el acceso a la variable local corresponde al acceso a un campo en la instancia de la clase generada por el compilador. Además, la función anónima se convierte en un método de instancia de la clase generada por el compilador:

delegate void D();

class Test
{
    void F()
    {
        __Locals1 __locals1 = new __Locals1();
        __locals1.y = 123;
        D d = new D(__locals1.__Method1);
    }

    class __Locals1
    {
        public int y;

        public void __Method1()
        {
            Console.WriteLine(y);
        }
    }
}

Por último, la siguiente función anónima captura this , así como dos variables locales con distintas duraciones:

delegate void D();

class Test
{
   int x;

   void F()
   {
       int y = 123;
       for (int i = 0; i < 10; i++)
       {
           int z = i * 2;
           D d = () => Console.WriteLine(x + y + z);
       }
   }
}

En este caso, se crea una clase generada por el compilador para cada bloque en el que se capturan las variables locales, de modo que las variables locales de los distintos bloques pueden tener duraciones independientes. Una instancia de __Locals2, la clase generada por el compilador para el bloque interno, contiene la variable z local y un campo que hace referencia a una instancia de __Locals1. Una instancia de __Locals1, la clase generada por el compilador para el bloque externo, contiene la variable y local y un campo que hace referencia this al miembro de función envolvente. Con estas estructuras de datos, es posible llegar a todas las variables externas capturadas a través de una instancia de __Local2y, por tanto, el código de la función anónima se puede implementar como un método de instancia de esa clase.

delegate void D();

class Test
{
    int x;

    void F()
    {
        __Locals1 __locals1 = new __Locals1();
        __locals1.__this = this;
        __locals1.y = 123;
        for (int i = 0; i < 10; i++)
        {
            __Locals2 __locals2 = new __Locals2();
            __locals2.__locals1 = __locals1;
            __locals2.z = i * 2;
            D d = new D(__locals2.__Method1);
        }
    }

    class __Locals1
    {
        public Test __this;
        public int y;
    }

    class __Locals2
    {
        public __Locals1 __locals1;
        public int z;

        public void __Method1()
        {
            Console.WriteLine(__locals1.__this.x + __locals1.y + z);
        }
    }
}

La misma técnica que se aplica aquí para capturar variables locales también se puede usar al convertir funciones anónimas en árboles de expresión: las referencias a los objetos generados por el compilador se pueden almacenar en el árbol de expresiones y el acceso a las variables locales se puede representar como acceso de campo en estos objetos. La ventaja de este enfoque es que permite compartir las variables locales "levantadas" entre delegados y árboles de expresión.

Fin del texto informativo.

12.20 Expresiones de consulta

12.20.1 General

Las expresiones de consulta proporcionan una sintaxis integrada en el lenguaje para las consultas que son similares a los lenguajes de consulta relacionales y jerárquicos , como SQL y XQuery.

query_expression
    : from_clause query_body
    ;

from_clause
    : 'from' type? identifier 'in' expression
    ;

query_body
    : query_body_clauses? select_or_group_clause query_continuation?
    ;

query_body_clauses
    : query_body_clause
    | query_body_clauses query_body_clause
    ;

query_body_clause
    : from_clause
    | let_clause
    | where_clause
    | join_clause
    | join_into_clause
    | orderby_clause
    ;

let_clause
    : 'let' identifier '=' expression
    ;

where_clause
    : 'where' boolean_expression
    ;

join_clause
    : 'join' type? identifier 'in' expression 'on' expression
      'equals' expression
    ;

join_into_clause
    : 'join' type? identifier 'in' expression 'on' expression
      'equals' expression 'into' identifier
    ;

orderby_clause
    : 'orderby' orderings
    ;

orderings
    : ordering (',' ordering)*
    ;

ordering
    : expression ordering_direction?
    ;

ordering_direction
    : 'ascending'
    | 'descending'
    ;

select_or_group_clause
    : select_clause
    | group_clause
    ;

select_clause
    : 'select' expression
    ;

group_clause
    : 'group' expression 'by' expression
    ;

query_continuation
    : 'into' identifier query_body
    ;

Una expresión de consulta comienza con una from cláusula y termina con una select cláusula o group . La cláusula inicial from puede ir seguida de cero o más fromcláusulas , let, whereo orderby join . Cada from cláusula es un generador que presenta una variable de rango que abarca los elementos de una secuencia. Cada let cláusula introduce una variable de rango que representa un valor calculado mediante variables de intervalo anteriores. Cada where cláusula es un filtro que excluye los elementos del resultado. Cada join cláusula compara las claves especificadas de la secuencia de origen con claves de otra secuencia, lo que produce pares coincidentes. Cada orderby cláusula reordena los elementos según los criterios especificados. La cláusula final select o group especifica la forma del resultado en términos de las variables de intervalo. Por último, se puede usar una into cláusula para "splice" consultas tratando los resultados de una consulta como generador en una consulta posterior.

12.20.2 Ambigüedades en expresiones de consulta

Las expresiones de consulta usan una serie de palabras clave contextuales (§6.4.4): ascending, descendingfromequalsby, groupintojoinlet, on, orderbyy . select where

Para evitar ambigüedades que podrían surgir del uso de estos identificadores como palabras clave y nombres simples, estos identificadores se consideran palabras clave en cualquier parte de una expresión de consulta, a menos que tengan el prefijo "@" (§6.4.4) en cuyo caso se consideran identificadores. Para ello, una expresión de consulta es cualquier expresión que comience por "fromidentificador" seguida de cualquier token excepto "", "=;" o ",".

12.20.3 Traducción de expresiones de consulta

12.20.3.1 General

El lenguaje C# no especifica la semántica de ejecución de expresiones de consulta. En su lugar, las expresiones de consulta se traducen en invocaciones de métodos que cumplen el patrón query-expression (§12.20.4). En concreto, las expresiones de consulta se traducen en invocaciones de métodos denominados Where, , ThenByGroupJoinJoinSelectManyOrderByDescendingSelectOrderBy, ThenByDescending, , GroupByy .Cast Se espera que estos métodos tengan firmas y tipos de valor devuelto concretos, como se describe en §12.20.4. Estos métodos pueden ser métodos de instancia del objeto que se consultan o métodos de extensión que son externos al objeto . Estos métodos implementan la ejecución real de la consulta.

La traducción de expresiones de consulta a invocaciones de método es una asignación sintáctica que se produce antes de que se haya realizado cualquier enlace de tipo o resolución de sobrecarga. Después de la traducción de expresiones de consulta, las invocaciones de método resultantes se procesan como invocaciones de método normales y esto puede, a su vez, descubrir errores en tiempo de compilación. Estas condiciones de error incluyen, entre otros, los métodos que no existen, los argumentos de los tipos incorrectos y los métodos genéricos en los que se produce un error en la inferencia de tipos.

Una expresión de consulta se procesa aplicando repetidamente las siguientes traducciones hasta que no se pueden reducir aún más. Las traducciones se enumeran en orden de aplicación: cada sección supone que las traducciones de las secciones anteriores se han realizado exhaustivamente y, una vez agotadas, una sección no se volverá a consultar más adelante en el procesamiento de la misma expresión de consulta.

Es un error en tiempo de compilación para que una expresión de consulta incluya una asignación a una variable de intervalo o el uso de una variable de rango como argumento para una referencia o parámetro de salida.

Algunas traducciones insertan variables de rango con identificadores transparentes indicados por *. Estos se describen más adelante en §12.20.3.8.

12.20.3.2 Expresiones de consulta con continuaciones

Expresión de consulta con una continuación después de su cuerpo de consulta

from «x1» in «e1» «b1» into «x2» «b2»

se traduce en

from «x2» in ( from «x1» in «e1» «b1» ) «b2»

Las traducciones de las secciones siguientes suponen que las consultas no tienen continuación.

Ejemplo: Ejemplo:

from c in customers
group c by c.Country into g
select new { Country = g.Key, CustCount = g.Count() }

se traduce en:

from g in
   (from c in customers
   group c by c.Country)
select new { Country = g.Key, CustCount = g.Count() }

la traducción final de la cual es:

customers.
GroupBy(c => c.Country).
Select(g => new { Country = g.Key, CustCount = g.Count() })

ejemplo final

12.20.3.3 Tipos de variables de rango explícitos

Cláusula from que especifica explícitamente un tipo de variable de rango

from «T» «x» in «e»

se traduce en

from «x» in ( «e» ) . Cast < «T» > ( )

Cláusula join que especifica explícitamente un tipo de variable de rango

join «T» «x» in «e» on «k1» equals «k2»

se traduce en

join «x» in ( «e» ) . Cast < «T» > ( ) on «k1» equals «k2»

Las traducciones de las secciones siguientes suponen que las consultas no tienen tipos de variables de intervalo explícitos.

Ejemplo: El ejemplo

from Customer c in customers
where c.City == "London"
select c

se traduce en

from c in (customers).Cast<Customer>()
where c.City == "London"
select c

la traducción final de la cual es

customers.
Cast<Customer>().
Where(c => c.City == "London")

ejemplo final

Nota: Los tipos de variables de intervalo explícitos son útiles para consultar colecciones que implementan la interfaz no genérica IEnumerable , pero no la interfaz genérica IEnumerable<T> . En el ejemplo anterior, este sería el caso si los clientes fueran de tipo ArrayList. nota final

12.20.3.4 Degenerar expresiones de consulta

Expresión de consulta del formulario

from «x» in «e» select «x»

se traduce en

( «e» ) . Select ( «x» => «x» )

Ejemplo: El ejemplo

from c in customers
select c

se traduce en

(customers).Select(c => c)

ejemplo final

Una expresión de consulta degenerada es una que selecciona trivialmente los elementos del origen.

Nota: Las fases posteriores de la traducción (§12.20.3.6 y §12.20.3.7) quitan las consultas degeneradas introducidas por otros pasos de traducción reemplazandolas por su origen. Sin embargo, es importante asegurarse de que el resultado de una expresión de consulta nunca es el propio objeto de origen. De lo contrario, devolver el resultado de dicha consulta podría exponer accidentalmente datos privados (por ejemplo, una matriz de elementos) a un autor de la llamada. Por lo tanto, este paso protege las consultas degeneradas escritas directamente en el código fuente llamando Select explícitamente al origen. A continuación, es necesario que los implementadores de y otros operadores de Select consulta se aseguren de que estos métodos nunca devuelvan el propio objeto de origen. nota final

12.20.3.5 Desde, let, where, join y orderby cláusulas

Expresión de consulta con una segunda from cláusula seguida de una select cláusula

from «x1» in «e1»  
from «x2» in «e2»  
select «v»

se traduce en

( «e1» ) . SelectMany( «x1» => «e2» , ( «x1» , «x2» ) => «v» )

Ejemplo: El ejemplo

from c in customers
from o in c.Orders
select new { c.Name, o.OrderID, o.Total }

se traduce en

(customers).
SelectMany(c => c.Orders,
(c,o) => new { c.Name, o.OrderID, o.Total }
)

ejemplo final

Expresión de consulta con una segunda from cláusula seguida de un cuerpo Q de consulta que contiene un conjunto no vacío de cláusulas de cuerpo de consulta:

from «x1» in «e1»
from «x2» in «e2»
Q

se traduce en

from * in («e1») . SelectMany( «x1» => «e2» ,
                              ( «x1» , «x2» ) => new { «x1» , «x2» } )
Q

Ejemplo: El ejemplo

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

se traduce en

from * in (customers).
   SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

la traducción final de la cual es

customers.
SelectMany(c => c.Orders, (c,o) => new { c, o }).
OrderByDescending(x => x.o.Total).
Select(x => new { x.c.Name, x.o.OrderID, x.o.Total })

donde x es un identificador generado por el compilador que, de lo contrario, es invisible y inaccesible.

ejemplo final

Expresión let junto con su cláusula anterior from :

from «x» in «e»  
let «y» = «f»  
...

se traduce en

from * in ( «e» ) . Select ( «x» => new { «x» , «y» = «f» } )  
...

Ejemplo: El ejemplo

from o in orders
let t = o.Details.Sum(d => d.UnitPrice * d.Quantity)
where t >= 1000
select new { o.OrderID, Total = t }

se traduce en

from * in (orders).Select(
    o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
where t >= 1000
select new { o.OrderID, Total = t }

la traducción final de la cual es

orders
    .Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
    .Where(x => x.t >= 1000)
    .Select(x => new { x.o.OrderID, Total = x.t })

donde x es un identificador generado por el compilador que, de lo contrario, es invisible y inaccesible.

ejemplo final

Expresión where junto con su cláusula anterior from :

from «x» in «e»  
where «f»  
...

se traduce en

from «x» in ( «e» ) . Where ( «x» => «f» )  
...

Una join cláusula seguida inmediatamente de una select cláusula

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
select «v»

se traduce en

( «e1» ) . Join( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «x2» ) => «v» )

Ejemplo: El ejemplo

from c in customersh
join o in orders on c.CustomerID equals o.CustomerID
select new { c.Name, o.OrderDate, o.Total }

se traduce en

(customers).Join(
   orders,
   c => c.CustomerID, o => o.CustomerID,
   (c, o) => new { c.Name, o.OrderDate, o.Total })

ejemplo final

Una join cláusula seguida de una cláusula de cuerpo de consulta:

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
...

se traduce en

from * in ( «e1» ) . Join(  
«e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «x2» ) => new { «x1» , «x2» })  
...

Una join-into cláusula seguida inmediatamente de una select cláusula

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into «g»  
select «v»

se traduce en

( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» ,
                     ( «x1» , «g» ) => «v» )

Una join into cláusula seguida de una cláusula de cuerpo de consulta

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into *g»  
...

se traduce en

from * in ( «e1» ) . GroupJoin(  
   «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «g» ) => new { «x1» , «g» })
...

Ejemplo: El ejemplo

from c in customers
join o in orders on c.CustomerID equals o.CustomerID into co
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }

se traduce en

from * in (customers).GroupJoin(
    orders,
    c => c.CustomerID,
    o => o.CustomerID,
    (c, co) => new { c, co })
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }

la traducción final de la cual es

customers
    .GroupJoin(
        orders,
        c => c.CustomerID,
        o => o.CustomerID,
        (c, co) => new { c, co })
    .Select(x => new { x, n = x.co.Count() })
    .Where(y => y.n >= 10)
    .Select(y => new { y.x.c.Name, OrderCount = y.n })

donde x y y son identificadores generados por el compilador que, de lo contrario, son invisibles e inaccesibles.

ejemplo final

Una orderby cláusula y su cláusula anterior from :

from «x» in «e»  
orderby «k1» , «k2» , ... , «kn»  
...

se traduce en

from «x» in ( «e» ) .
OrderBy ( «x» => «k1» ) .
ThenBy ( «x» => «k2» ) .
... .
ThenBy ( «x» => «kn» )
...

Si una ordering cláusula especifica un indicador de dirección descendente, se genera una invocación de OrderByDescending o ThenByDescending en su lugar.

Ejemplo: El ejemplo

from o in orders
orderby o.Customer.Name, o.Total descending
select o

tiene la traducción final

(orders)
    .OrderBy(o => o.Customer.Name)
    .ThenByDescending(o => o.Total)

ejemplo final

En las traducciones siguientes se supone que no hay cláusulas let, whereo join orderby y que no hay más de una cláusula inicial from en cada expresión de consulta.

12.20.3.6 Cláusulas Select

Expresión de consulta del formulario

from «x» in «e» select «v»

se traduce en

( «e» ) . Select ( «x» => «v» )

excepto cuando «v» es el identificador «x», la traducción es simplemente

( «e» )

Ejemplo: El ejemplo

from c in customers.Where(c => c.City == "London")
select c

se traduce simplemente en

(customers).Where(c => c.City == "London")

ejemplo final

12.20.3.7 Cláusulas group

Una group cláusula

from «x» in «e» group «v» by «k»

se traduce en

( «e» ) . GroupBy ( «x» => «k» , «x» => «v» )

excepto cuando «v» es el identificador «x», la traducción es

( «e» ) . GroupBy ( «x» => «k» )

Ejemplo: El ejemplo

from c in customers
group c.Name by c.Country

se traduce en

(customers).GroupBy(c => c.Country, c => c.Name)

ejemplo final

12.20.3.8 Identificadores transparentes

Algunas traducciones insertan variables de rango con identificadores transparentes indicados por *. Los identificadores transparentes solo existen como un paso intermedio en el proceso de traducción de expresiones de consulta.

Cuando una traducción de consultas inserta un identificador transparente, los pasos de traducción adicionales propagan el identificador transparente a funciones anónimas y inicializadores de objetos anónimos. En esos contextos, los identificadores transparentes tienen el siguiente comportamiento:

  • Cuando se produce un identificador transparente como parámetro en una función anónima, los miembros del tipo anónimo asociado se encuentran automáticamente en el ámbito del cuerpo de la función anónima.
  • Cuando un miembro con un identificador transparente está en el ámbito, los miembros de ese miembro también están en el ámbito.
  • Cuando un identificador transparente se produce como un declarador de miembro en un inicializador de objeto anónimo, introduce un miembro con un identificador transparente.

En los pasos de traducción descritos anteriormente, los identificadores transparentes siempre se presentan junto con tipos anónimos, con la intención de capturar varias variables de rango como miembros de un único objeto. Se permite que una implementación de C# use un mecanismo diferente al de los tipos anónimos para agrupar varias variables de intervalo. En los ejemplos de traducción siguientes se supone que se usan tipos anónimos y se muestra una posible traducción de identificadores transparentes.

Ejemplo: El ejemplo

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.Total }

se traduce en

from * in (customers).SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.Total }

que se traduce aún más en

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(* => o.Total)
    .Select(\* => new { c.Name, o.Total })

que, cuando se borran los identificadores transparentes, equivale a

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(x => x.o.Total)
    .Select(x => new { x.c.Name, x.o.Total })

donde x es un identificador generado por el compilador que, de lo contrario, es invisible y inaccesible.

En el ejemplo

from c in customers
join o in orders on c.CustomerID equals o.CustomerID
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

se traduce en

from * in (customers).Join(
    orders,
    c => c.CustomerID,
    o => o.CustomerID,
    (c, o) => new { c, o })
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

que se reduce aún más a

customers
    .Join(orders, c => c.CustomerID,
        o => o.CustomerID, (c, o) => new { c, o })
    .Join(details, * => o.OrderID, d => d.OrderID, (*, d) => new { *, d })
    .Join(products, * => d.ProductID, p => p.ProductID,
        (*, p) => new { c.Name, o.OrderDate, p.ProductName })

la traducción final de la cual es

customers
    .Join(orders, c => c.CustomerID,
        o => o.CustomerID, (c, o) => new { c, o })
    .Join(details, x => x.o.OrderID, d => d.OrderID, (x, d) => new { x, d })
    .Join(products, y => y.d.ProductID, p => p.ProductID,
        (y, p) => new { y.x.c.Name, y.x.o.OrderDate, p.ProductName })

donde x y y son identificadores generados por el compilador que, de lo contrario, son invisibles e inaccesibles. ejemplo final

12.20.4 El patrón query-expression

El patrón Query-expression establece un patrón de métodos que los tipos pueden implementar para admitir expresiones de consulta.

Un tipo C<T> genérico admite el patrón query-expression-pattern si sus métodos de miembro público y los métodos de extensión accesibles públicamente podrían reemplazarse por la siguiente definición de clase. Los miembros y los métodos extensos accesibles se conocen como la "forma" de un tipo C<T>genérico . Se usa un tipo genérico para ilustrar las relaciones adecuadas entre los tipos de parámetro y de valor devuelto, pero también es posible implementar el patrón para tipos no genéricos.

delegate R Func<T1,R>(T1 arg1);
delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);

class C
{
    public C<T> Cast<T>() { ... }
}

class C<T> : C
{
    public C<T> Where(Func<T,bool> predicate) { ... }
    public C<U> Select<U>(Func<T,U> selector) { ... }
    public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
        Func<T,U,V> resultSelector) { ... }
    public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,U,V> resultSelector) { ... }
    public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector) { ... }
    public O<T> OrderBy<K>(Func<T,K> keySelector) { ... }
    public O<T> OrderByDescending<K>(Func<T,K> keySelector) { ... }
    public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector) { ... }
    public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
        Func<T,E> elementSelector) { ... }
}

class O<T> : C<T>
{
    public O<T> ThenBy<K>(Func<T,K> keySelector) { ... }
    public O<T> ThenByDescending<K>(Func<T,K> keySelector) { ... }
}

class G<K,T> : C<T>
{
    public K Key { get; }
}

Los métodos anteriores usan los tipos Func<T1, R> de delegado genéricos y Func<T1, T2, R>, pero podrían haber usado igualmente otros tipos de árbol de expresiones o delegados con las mismas relaciones en los tipos de parámetro y valor devuelto.

Nota: La relación recomendada entre C<T> y O<T> que garantiza que los ThenBy métodos y ThenByDescending solo están disponibles en el resultado de o OrderBy OrderByDescending. nota final

Nota: La forma recomendada del resultado de GroupBy: una secuencia de secuencias, donde cada secuencia interna tiene una propiedad adicional Key . nota final

Nota: Dado que las expresiones de consulta se traducen a invocaciones de método mediante una asignación sintáctica, los tipos tienen una flexibilidad considerable en la forma en que implementan cualquiera o todos los patrones de expresión de consulta. Por ejemplo, los métodos del patrón se pueden implementar como métodos de instancia o como métodos de extensión porque los dos tienen la misma sintaxis de invocación, y los métodos pueden solicitar delegados o árboles de expresión porque las funciones anónimas se pueden convertir en ambas. Los tipos que implementan solo algunos de los patrones de expresión de consulta solo admiten traducciones de expresiones de consulta que se asignan a los métodos que admiten el tipo. nota final

Nota: El System.Linq espacio de nombres proporciona una implementación del patrón query-expression para cualquier tipo que implemente la System.Collections.Generic.IEnumerable<T> interfaz. nota final

12.21 Operadores de asignación

12.21.1 General

Todos los operadores de asignación, pero uno de los operadores de asignación, asigna un nuevo valor a una variable, una propiedad, un evento o un elemento indexador. La excepción , = refasigna una referencia de variable (§9.5) a una variable de referencia (§9.7).

assignment
    : unary_expression assignment_operator expression
    ;

assignment_operator
    : '=' 'ref'? | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<='
    | right_shift_assignment
    ;

El operando izquierdo de una asignación debe ser una expresión clasificada como una variable, o, excepto , = refun acceso de propiedad, un acceso de indizador, un acceso a eventos o una tupla. Una expresión de declaración no se permite directamente como operando izquierdo, pero puede producirse como un paso en la evaluación de una asignación deconstrucción.

El = operador se denomina operador de asignación simple. Asigna el valor o los valores del operando derecho a la variable, propiedad, elemento indexador o elementos de tupla proporcionados por el operando izquierdo. El operando izquierdo del operador de asignación simple no será un acceso a eventos (excepto como se describe en §15.8.2). El operador de asignación simple se describe en §12.21.2.

El operador = ref se denomina operador de asignación de referencias. Hace que el operando derecho, que será un variable_reference (§9.5), el referente de la variable de referencia designada por el operando izquierdo. El operador de asignación ref se describe en §12.21.3.

Los operadores de asignación distintos de los = operadores y = ref se denominan operadores de asignación compuestos. Estos operadores realizan la operación indicada en los dos operandos y, a continuación, asignan el valor resultante a la variable, propiedad o elemento indexador proporcionado por el operando izquierdo. Los operadores de asignación compuesta se describen en §12.21.4.

Los += operadores y -= con una expresión de acceso a eventos como operando izquierdo se denominan operadores de asignación de eventos. Ningún otro operador de asignación es válido con un acceso a eventos como operando izquierdo. Los operadores de asignación de eventos se describen en §12.21.5.

Los operadores de asignación son asociativos a la derecha, lo que significa que las operaciones se agrupan de derecha a izquierda.

Ejemplo: una expresión del formulario a = b = c se evalúa como a = (b = c). ejemplo final

12.21.2 Asignación simple

El = operador se denomina operador de asignación simple.

Si el operando izquierdo de una asignación simple es del formulario o donde tiene el tipo dynamicen tiempo de compilación , la asignación se enlaza dinámicamente (§12.3.3).E E[Ei] E.P En este caso, el tipo en tiempo de compilación de la expresión de asignación es dynamicy la resolución descrita a continuación tendrá lugar en tiempo de ejecución en función del tipo de tiempo de ejecución de E. Si el operando izquierdo tiene el formato E[Ei] donde al menos un elemento de Ei tiene el tipo dynamicen tiempo de compilación y el tipo de tiempo de compilación de E no es una matriz, el acceso al indexador resultante se enlaza dinámicamente, pero con comprobación limitada en tiempo de compilación (§12.6.5).

Una asignación simple donde el operando izquierdo se clasifica como una tupla también se denomina asignación deconstrucción. Si alguno de los elementos de tupla del operando izquierdo tiene un nombre de elemento, se produce un error en tiempo de compilación. Si alguno de los elementos de tupla del operando izquierdo es un declaration_expression y cualquier otro elemento no es un declaration_expression o un descarte simple, se produce un error en tiempo de compilación.

El tipo de una asignación x = y simple es el tipo de una asignación a x de y, que se determina de forma recursiva como se indica a continuación:

  • Si x es una expresión (x1, ..., xn)de tupla y y se puede descompilar en una expresión (y1, ..., yn) de tupla con n elementos (§12.7) y cada asignación a xi de yi tiene el tipo Ti, la asignación tiene el tipo (T1, ..., Tn).
  • De lo contrario, si x se clasifica como una variable, la variable no readonlyes , x tiene un tipo Ty y tiene una conversión implícita a T, la asignación tiene el tipo T.
  • De lo contrario, si x se clasifica como una variable con tipo implícito (es decir, una expresión de declaración con tipo implícito) y y tiene un tipo , el tipo Tinferido de la variable es Ty la asignación tiene el tipo T.
  • De lo contrario, si x se clasifica como una propiedad o acceso al indexador, la propiedad o el indexador tiene un descriptor de acceso set accesible, x tiene un tipo Ty y tiene una conversión implícita en T, la asignación tiene el tipo T.
  • De lo contrario, la asignación no es válida y se produce un error en tiempo de enlace.

El procesamiento en tiempo de ejecución de una asignación simple del formulario x = y con tipo T se realiza como una asignación de y x con tipo T, que consta de los siguientes pasos recursivos:

  • x se evalúa si aún no lo era.
  • Si x se clasifica como una variable, y se evalúa y, si es necesario, se convierte a T a través de una conversión implícita (§10.2).
    • Si la variable dada por x es un elemento de matriz de un reference_type, se realiza una comprobación en tiempo de ejecución para asegurarse de que el valor calculado para y es compatible con la instancia de matriz de la que x es un elemento. La comprobación se realiza correctamente si y es , o si existe una conversión de referencia implícita (§10.2.8) del tipo de la instancia a la que hace y referencia el tipo de elemento real de la instancia de matriz que contiene xnull. De lo contrario, se produce una excepción System.ArrayTypeMismatchException.
    • El valor resultante de la evaluación y conversión de y se almacena en la ubicación dada por la evaluación de xy se produce como resultado de la asignación.
  • Si x se clasifica como acceso de propiedad o indexador:
    • y se evalúa y, si es necesario, se convierte a T a través de una conversión implícita (§10.2).
    • El descriptor de acceso set de x se invoca con el valor resultante de la evaluación y conversión de y como argumento value.
    • El valor resultante de la evaluación y conversión de y se produce como resultado de la asignación.
  • Si x se clasifica como una tupla (x1, ..., xn) con aridad n:
    • y se descontruye con n elementos en una expresión ede tupla .
    • Se crea una tupla t de resultado mediante la conversión e a T mediante una conversión de tupla implícita.
    • para cada xi uno en orden de izquierda a derecha, se realiza una asignación a xi de t.Itemi , excepto que xi no se evalúa de nuevo.
    • t se produce como resultado de la asignación.

Nota: si el tipo de tiempo de compilación de x es dynamic y hay una conversión implícita del tipo de tiempo de compilación de y a dynamic, no se requiere ninguna resolución en tiempo de ejecución. nota final

Nota: Las reglas de covarianza de matriz (§17.6) permiten que un valor de un tipo A[] de matriz sea una referencia a una instancia de un tipo B[]de matriz, siempre que exista una conversión de referencia implícita desde B a A. Debido a estas reglas, la asignación a un elemento de matriz de un reference_type requiere una comprobación en tiempo de ejecución para asegurarse de que el valor asignado es compatible con la instancia de matriz. En el ejemplo

string[] sa = new string[10];
object[] oa = sa;
oa[0] = null;              // OK
oa[1] = "Hello";           // OK
oa[2] = new ArrayList();   // ArrayTypeMismatchException

la última asignación hace que se produzca un System.ArrayTypeMismatchException elemento porque una referencia a un ArrayList no se puede almacenar en un elemento de .string[]

nota final

Cuando una propiedad o indexador declarada en un struct_type es el destino de una asignación, la expresión de instancia asociada a la propiedad o el acceso al indexador se clasificará como una variable. Si la expresión de instancia se clasifica como un valor, se produce un error en tiempo de enlace.

Nota: Debido a §12.8.7, la misma regla también se aplica a los campos. nota final

Ejemplo: dadas las declaraciones:

struct Point
{
   int x, y;

   public Point(int x, int y)
   {
      this.x = x;
      this.y = y;
   }

   public int X
   {
      get { return x; }
      set { x = value; }
   }

   public int Y {
      get { return y; }
      set { y = value; }
   }
}

struct Rectangle
{
    Point a, b;

    public Rectangle(Point a, Point b)
    {
        this.a = a;
        this.b = b;
    }

    public Point A
    {
        get { return a; }
        set { a = value; }
    }

    public Point B
    {
        get { return b; }
        set { b = value; }
    }
}

en el ejemplo

Point p = new Point();
p.X = 100;
p.Y = 100;
Rectangle r = new Rectangle();
r.A = new Point(10, 10);
r.B = p;

Las asignaciones a p.X, p.Y, r.Ay r.B se permiten porque p y r son variables. Sin embargo, en el ejemplo

Rectangle r = new Rectangle();
r.A.X = 10;
r.A.Y = 10;
r.B.X = 100;
r.B.Y = 100;

las asignaciones no son válidas, ya que r.A y r.B no son variables.

ejemplo final

12.21.3 Asignación ref

El = ref operador se conoce como operador de asignación ref.

El operando izquierdo debe ser una expresión que se enlaza a una variable de referencia (§9.7), un parámetro de referencia (distinto thisde ), un parámetro de salida o un parámetro de entrada. El operando derecho debe ser una expresión que produce un variable_reference designando un valor del mismo tipo que el operando izquierdo.

Es un error en tiempo de compilación si el contexto ref-safe-context (§9.7.2) del operando izquierdo es más amplio que el contexto ref-safe-context del operando derecho.

El operando derecho se asignará definitivamente en el punto de la asignación ref.

Cuando el operando izquierdo se enlaza a un parámetro de salida, se trata de un error si ese parámetro de salida no se ha asignado definitivamente al principio del operador de asignación ref.

Si el operando izquierdo es una referencia que se puede escribir (es decir, designa algo distinto de un ref readonly parámetro local o de entrada), el operando derecho será un variable_reference que se pueda escribir. Si la variable de operando derecho es grabable, el operando izquierdo puede ser una referencia de solo lectura o escritura.

La operación hace que el operando izquierdo sea un alias de la variable de operando derecho. El alias se puede convertir en de solo lectura incluso si la variable de operando derecho es grabable.

El operador de asignación de referencia produce un variable_reference del tipo asignado. Se puede escribir si el operando izquierdo es grabable.

El operador de asignación ref no leerá la ubicación de almacenamiento a la que hace referencia el operando derecho.

Ejemplo: Estos son algunos ejemplos de uso = refde :

public static int M1() { ... }
public static ref int M2() { ... }
public static ref uint M2u() { ... }
public static ref readonly int M3() { ... }
public static void Test()
{
int v = 42;
ref int r1 = ref v; // OK, r1 refers to v, which has value 42
r1 = ref M1();      // Error; M1 returns a value, not a reference
r1 = ref M2();      // OK; makes an alias
r1 = ref M2u();     // Error; lhs and rhs have different types
r1 = ref M3();    // error; M3 returns a ref readonly, which r1 cannot honor
ref readonly int r2 = ref v; // OK; make readonly alias to ref
r2 = ref M2();      // OK; makes an alias, adding read-only protection
r2 = ref M3();      // OK; makes an alias and honors the read-only
r2 = ref (r1 = ref M2());  // OK; r1 is an alias to a writable variable,
              // r2 is an alias (with read-only access) to the same variable
}

ejemplo final

Nota: Al leer código mediante un = ref operador, puede resultar tentador leer la ref parte como parte del operando. Esto resulta especialmente confuso cuando el operando es una expresión condicional ?: . Por ejemplo, al leerlo ref int a = ref b ? ref x : ref y; , es importante leerlo como = ref operador y b ? ref x : ref y ser el operando derecho: ref int a = ref (b ? ref x : ref y);. Lo importante es que la expresión ref b no forma parte de esa instrucción, aunque puede aparecer tan de primera vista. nota final

12.21.4 Asignación compuesta

Si el operando izquierdo de una asignación compuesta tiene el formato o donde tiene el tipo dynamicen tiempo de compilación, la asignación se enlaza dinámicamente (§12.3.3).E E[Ei] E.P En este caso, el tipo en tiempo de compilación de la expresión de asignación es dynamicy la resolución descrita a continuación tendrá lugar en tiempo de ejecución en función del tipo de tiempo de ejecución de E. Si el operando izquierdo tiene el formato E[Ei] donde al menos un elemento de Ei tiene el tipo dynamicen tiempo de compilación y el tipo de tiempo de compilación de E no es una matriz, el acceso al indexador resultante se enlaza dinámicamente, pero con comprobación limitada en tiempo de compilación (§12.6.5).

Una operación del formulario x «op»= y se procesa aplicando la resolución de sobrecarga del operador binario (§12.4.5) como si la operación se escribiera x «op» y. A continuación,

  • Si el tipo de valor devuelto del operador seleccionado se puede convertir implícitamente al tipo de x, la operación se evalúa como x = x «op» y, excepto que x se evalúa solo una vez.
  • De lo contrario, si el operador seleccionado es un operador predefinido, si el tipo de valor devuelto del operador seleccionado se puede convertir explícitamente al tipo de x y si y se convierte implícitamente en el tipo de x o el operador es un operador de desplazamiento, la operación se evalúa como x = (T)(x «op» y), donde T es el tipo de x, excepto que x se evalúa solo una vez.
  • De lo contrario, la asignación compuesta no es válida y se produce un error en tiempo de enlace.

El término "evaluado solo una vez" significa que en la evaluación de x «op» y, los resultados de cualquier expresión constituyente de x se guardan temporalmente y, a continuación, se reutilizan al realizar la asignación a x.

Ejemplo: En la asignación A()[B()] += C(), donde A es un método que devuelve int[]y y B C son métodos que devuelven int, los métodos se invocan solo una vez, en el orden A, B, C. ejemplo final

Cuando el operando izquierdo de una asignación compuesta es un acceso a propiedades o acceso de indexador, la propiedad o el indexador tendrán un descriptor de acceso get y un descriptor de acceso set. Si no es así, se produce un error en tiempo de enlace.

La segunda regla anterior permite x «op»= y evaluarse como x = (T)(x «op» y) en determinados contextos. La regla existe de modo que los operadores predefinidos se pueden usar como operadores compuestos cuando el operando izquierdo es de tipo sbyte, , shortbyte, ushorto char. Incluso cuando ambos argumentos son de uno de esos tipos, los operadores predefinidos generan un resultado de tipo int, como se describe en §12.4.7.3. Por lo tanto, sin una conversión no sería posible asignar el resultado al operando izquierdo.

El efecto intuitivo de la regla para los operadores predefinidos es simplemente que x «op»= y se permite si se permiten ambos x «op» y y x = y .

Ejemplo: en el código siguiente

byte b = 0;
char ch = '\0';
int i = 0;
b += 1;           // OK
b += 1000;        // Error, b = 1000 not permitted
b += i;           // Error, b = i not permitted
b += (byte)i;     // OK
ch += 1;          // Error, ch = 1 not permitted
ch += (char)1;    // OK

la razón intuitiva de cada error es que una asignación simple correspondiente también habría sido un error.

ejemplo final

Nota: Esto también significa que las operaciones de asignación compuestas admiten operadores elevados. Dado que una asignación x «op»= y compuesta se evalúa como x = x «op» y o x = (T)(x «op» y), las reglas de evaluación cubren implícitamente operadores elevados. nota final

12.21.5 Asignación de eventos

Si el operando izquierdo del a += or -= operador se clasifica como acceso a eventos, la expresión se evalúa de la siguiente manera:

  • La expresión de instancia, si existe, del acceso al evento se evalúa.
  • Se evalúa el += operando derecho del operador o -= y, si es necesario, se convierte al tipo del operando izquierdo a través de una conversión implícita (§10.2).
  • Se invoca un descriptor de acceso de eventos del evento, con una lista de argumentos que consta del valor calculado en el paso anterior. Si el operador era +=, se invoca el descriptor de acceso add; si el operador era -=, se invoca el descriptor de acceso remove.

Una expresión de asignación de eventos no produce un valor. Por lo tanto, una expresión de asignación de eventos solo es válida en el contexto de un statement_expression (§13.7).

Expresión 12.22

Una expresión es un non_assignment_expression o una asignación.

expression
    : non_assignment_expression
    | assignment
    ;

non_assignment_expression
    : declaration_expression
    | conditional_expression
    | lambda_expression
    | query_expression
    ;

12.23 Expresiones constantes

Una expresión constante es una expresión que se evaluará completamente en tiempo de compilación.

constant_expression
    : expression
    ;

Una expresión constante tendrá el valor null o uno de los siguientes tipos:

  • sbyte, byte, short, ushort, , uintint, long, ulongdoublefloatdecimalchar, , ; stringbool
  • un tipo de enumeración; o
  • una expresión de valor predeterminada (§12.8.21) para un tipo de referencia.

Solo se permiten las siguientes construcciones en expresiones constantes:

  • Literales (incluido el null literal).
  • Referencias a const miembros de tipos de clase y estructura.
  • Referencias a miembros de tipos de enumeración.
  • Referencias a constantes locales.
  • Subexpresiones entre paréntesis, que son expresiones constantes.
  • Expresiones de conversión.
  • checked y unchecked expresiones.
  • nameof Expresiones.
  • Los operadores predefinidos +, -, ! (negación lógica) y ~ unarios.
  • Los operadores binarios predefinidos +, -, >><&|<<^%&&||/*!=>==<=y .>=
  • Operador ?: condicional.
  • Operador ! null-forgiving (§12.8.9).
  • sizeof expresiones, siempre que el tipo no administrado sea uno de los tipos especificados en §23.6.9 para los que sizeof devuelve un valor constante.
  • Expresiones de valor predeterminadas, siempre que el tipo sea uno de los tipos enumerados anteriormente.

Las conversiones siguientes se permiten en expresiones constantes:

  • Conversiones de identidad
  • Conversiones numéricas
  • Conversiones de enumeración
  • Conversiones de expresiones constantes
  • Conversiones de referencia implícitas y explícitas, siempre que el origen de las conversiones sea una expresión constante que se evalúa como el null valor.

Nota: No se permiten otras conversiones que incluyen conversión boxing, unboxing y conversiones de referencia implícitas de valores quenull no son en expresiones constantes. nota final

Ejemplo: en el código siguiente

class C
{
    const object i = 5;         // error: boxing conversion not permitted
    const object str = "hello"; // error: implicit reference conversion
}

la inicialización de i es un error porque se requiere una conversión boxing. La inicialización de str es un error porque se requiere una conversión de referencia implícita de un valor que nonull es .

ejemplo final

Cada vez que una expresión cumple los requisitos enumerados anteriormente, la expresión se evalúa en tiempo de compilación. Esto es cierto incluso si la expresión es una subexpresión de una expresión mayor que contiene construcciones que no son constantes.

La evaluación en tiempo de compilación de expresiones constantes usa las mismas reglas que la evaluación en tiempo de ejecución de expresiones no constantes, salvo que cuando la evaluación en tiempo de ejecución habría producido una excepción, la evaluación en tiempo de compilación provoca un error en tiempo de compilación.

A menos que una expresión constante se coloque explícitamente en un unchecked contexto, los desbordamientos que se producen en operaciones aritméticas de tipo entero y conversiones durante la evaluación en tiempo de compilación de la expresión siempre provocan errores en tiempo de compilación (§12.8.20).

Las expresiones constantes son necesarias en los contextos que se enumeran a continuación y se indica en la gramática mediante constant_expression. En estos contextos, se produce un error en tiempo de compilación si una expresión no se puede evaluar completamente en tiempo de compilación.

  • Declaraciones de constantes (§15.4)
  • Declaraciones de miembro de enumeración (§19.4)
  • Argumentos predeterminados de listas de parámetros (§15.6.2)
  • case etiquetas de una switch instrucción (§13.8.3).
  • goto case instrucciones (§13.10.4)
  • Longitudes de dimensión en una expresión de creación de matriz (§12.8.17.5) que incluye un inicializador.
  • Atributos (§22)
  • En un constant_pattern (§11.2.3)

Una conversión de expresión constante implícita (§10.2.11) permite convertir sbyteuna expresión constante de tipo int en , , byteshort, ushort, uinto ulong, siempre que el valor de la expresión constante esté dentro del intervalo del tipo de destino.

12.24 Expresiones booleanas

Un boolean_expression es una expresión que produce un resultado de tipo bool; ya sea directamente o a través de la aplicación de operator true en determinados contextos, tal como se especifica en lo siguiente:

boolean_expression
    : expression
    ;

La expresión condicional de control de un if_statement (§13.8.2), while_statement (§13.9.2), do_statement (§13.9.3) o for_statement (§13.9.4) es un boolean_expression. La expresión condicional de control del ?: operador (§12.18) sigue las mismas reglas que una boolean_expression, pero por motivos de precedencia del operador se clasifica como un null_coalescing_expression.

Se requiere un boolean_expressionE para poder generar un valor de tipo bool, como se indica a continuación:

  • Si E se convierte implícitamente en bool entonces en tiempo de ejecución, se aplica la conversión implícita.
  • De lo contrario, la resolución de sobrecarga del operador unario (§12.4.4) se usa para encontrar una mejor implementación única de operator true en Ey esa implementación se aplica en tiempo de ejecución.
  • Si no se encuentra ningún operador de este tipo, se produce un error en tiempo de enlace.