Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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 los operandos y operadores, y el significado de las expresiones.
12.2 Clasificación de las expresiones
12.2.1 General
El resultado de una expresión se clasifica como uno de los siguientes:
- Un valor. Todos los valores tienen un tipo asociado.
- Una variable. A menos que se especifique lo contrario, una variable está explícitamente tipada y tiene un tipo asociado, a saber, el tipo declarado de la variable. Una variable implícitamente tipada no tiene tipo asociado.
- Un null-literal. 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 nulos.
- Una función anónima. Una expresión con esta clasificación puede convertirse implícitamente a un tipo delegado o a un tipo de árbol de expresión compatible.
- Una tupla. Cada tupla tiene un número fijo de elementos, cada uno con una expresión y un nombre opcional para el elemento de la tupla.
- Un acceso a propiedad. Todo acceso a una propiedad tiene un tipo asociado, a saber, el tipo de la propiedad. Además, un acceso a una propiedad puede tener asociada una expresión de instancia. Cuando se invoca un accessor de un acceso de propiedad de instancia, el resultado de evaluar la expresión de instancia se convierte en la instancia representada por
this(sección 12.8.14). - Un acceso a un indexador. Todo acceso a un indexador tiene un tipo asociado, a saber, el tipo de elemento del indexador. Además, un acceso a un indexador tiene una expresión de instancia asociada y una lista de argumentos asociada. Cuando se invoca un accessor de un access indexer, el resultado de evaluar la expresión de la instancia se convierte en la instancia representada por
this(sección 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 a un método cuyo tipo de retorno es
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.21).
Para las expresiones que aparecen como subexpresiones de expresiones mayores, con las restricciones indicadas, el resultado también puede clasificarse como uno de los siguientes:
- Espacio de nombres. Una expresión con esta clasificación solo puede aparecer como lado izquierdo de un member_access (sección 12.8.7). En cualquier otro contexto, una expresión clasificada como espacio de nombres provoca un error de compilación.
- Un tipo. Una expresión con esta clasificación solo puede aparecer como lado izquierdo de un member_access (sección 12.8.7). En cualquier otro contexto, una expresión clasificada como tipo provoca un error 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 asociada. Cuando se invoca un método de instancia, el resultado de la evaluación de la expresión de instancia se convierte en la instancia representada por
this(sección 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.5) y se puede convertir implícitamente en un tipo de delegado compatible (§10.8). En cualquier otro contexto, una expresión clasificada como grupo de métodos provoca un error de compilación. - Un acceso a un evento. Cada acceso a un evento tiene un tipo asociado, a saber, el tipo del evento. Además, un acceso de evento puede tener una expresión de instancia asociada. Un acceso a eventos puede aparecer como el operando izquierdo de los
+=operadores y-=(§12.23.5). En cualquier otro contexto, una expresión clasificada como acceso de evento provoca un error de compilación. Cuando se invoca un descriptor de acceso de un acceso de instancia, el resultado de la evaluación de la expresión de instancia se convierte en la instancia representada porthis(§12.8.14). - Expresión throw, que se puede usar en varios contextos para producir una excepción en una expresión. Una expresión throw puede convertirse mediante una conversión implícita a cualquier tipo.
El acceso a una propiedad o a un indexador siempre se reclasifica como un valor al invocar el método de acceso get o el método 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.23.2). En caso contrario, se invoca al descriptor de acceso get para obtener el valor actual (sección 12.2.2).
Un descriptor de acceso de instancia es un acceso a una propiedad de una instancia, un acceso a un evento de una instancia o un acceso a un indexador.
12.2.2 Valores de las expresiones
La mayoría de las construcciones que implican una expresión requieren en última instancia que la expresión indique un valor. En estos casos, si la expresión denota un espacio de nombres, un tipo, un grupo de métodos o nada, se produce un error de compilación. Sin embargo, si la expresión denota un acceso a una propiedad, un acceso a un indexador o una variable, el valor de la propiedad, indexador o 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 debe considerarse definitivamente asignada (sección 9.4) antes de que pueda obtenerse su valor, o de lo contrario se produce un error en tiempo de compilación.
- El valor de una expresión de acceso a una propiedad se obtiene invocando al descriptor de acceso get de la propiedad. Si la propiedad no tiene descriptor de acceso get, se produce un error de compilación. En caso contrario, se realiza una invocación a un 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 a la propiedad.
- El valor de una expresión de acceso a un indexador se obtiene invocando al descriptor de acceso get del indexador. Si el indexador no tiene descriptor de acceso get, se produce un error de compilación. En caso contrario, se realiza una invocación a un miembro de función (sección 12.6.6) con la lista de argumentos asociada a la expresión de acceso al indexador, y el resultado de la invocación se convierte en el valor de la expresión de acceso al indexador.
- El valor de una expresión de tupla se obtiene mediante la aplicación de una conversión implícita de tupla (§10.2.13) al tipo de expresión de tupla. Es un error obtener el valor de una expresión de tupla que no tiene un tipo.
12.3 Vinculación estática y dinámica
12.3.1 General
Vinculación 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, la vinculación de una llamada a un método se determina en función del tipo del receptor y de los argumentos. La vinculación de un operador se determina en función del tipo de sus operandos.
En C#, la vinculación de una operación suele determinarse en tiempo de compilación, basándose en el tipo en tiempo de compilación de sus subexpresiones. Del mismo modo, si una expresión contiene un error, este se detecta y se notifica en tiempo de compilación. Este enfoque se conoce como vinculación estática.
Sin embargo, si una expresión es una expresión dinámica (es decir, tiene el tipo dynamic) esto indica que cualquier vinculación en la que participe debe basarse en su tipo en tiempo de ejecución en lugar del tipo que tiene en tiempo de compilación. Por lo tanto, la vinculación de dicha operación se pospone hasta el momento en que la operación deba ejecutarse durante la ejecución del programa. Esto se denomina vinculación dinámica.
Cuando una operación se vincula dinámicamente, se realiza poca o ninguna comprobación en tiempo de compilación. En cambio, si la vinculación en tiempo de ejecución falla, los errores se notifican como excepciones en tiempo de ejecución.
Las siguientes operaciones en C# están sujetas a vinculación:
- Acceso a miembros:
e.M - Invocación de métodos:
e.M(e₁,...,eᵥ) - Invocación de delegados:
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, C# utiliza por defecto la vinculación estática, lo que significa que los tipos de las subexpresiones en tiempo de compilación 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 vincula dinámicamente.
Es un error en tiempo de compilación si la invocación a un método está ligada dinámicamente y cualquiera de los parámetros, incluido el receptor, son parámetros de entrada.
12.3.2 Tiempo de enlace
La vinculación estática se produce en tiempo de compilación, mientras que la dinámica tiene lugar en tiempo de ejecución. En las siguientes subcláusulas, el término tiempo de vinculación se refiere al tiempo de compilación o al tiempo de ejecución, dependiendo de cuándo tenga lugar la vinculación.
Ejemplo: A continuación se ilustran las nociones de vinculación estática y dinámica y de tiempo de vinculación:
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 están vinculadas estáticamente: la sobrecarga de
Console.WriteLinese elige en función del tipo en tiempo de compilación de su argumento. Por lo tanto, el tiempo de enlace es el tiempo de compilación.La tercera llamada está vinculada dinámicamente: la sobrecarga de
Console.WriteLinese elige en función del tipo de su argumento en tiempo de ejecución. 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 el tiempo de ejecución.ejemplo final
12.3.3 Vinculación dinámica
Esta subcláusula es informativa.
La vinculación dinámica 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 diferentes 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 mediante el cual un objeto dinámico implementa su propia semántica está definido por la implementación. Los objetos dinámicos implementan una interfaz determinada (también definida por la implementación) para indicar al motor de ejecución de C# que tienen una 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 especificadas en esta especificación para C#, prevalecen.
Aunque el propósito de la vinculación dinámica es permitir la interoperabilidad con objetos dinámicos, C# permite la vinculación dinámica en todos los objetos, sean dinámicos o no. Esto permite una integración más fluida de objetos dinámicos, ya que los resultados de las operaciones en ellos no siempre son objetos dinámicos, pero siguen siendo de un tipo desconocido para el programador en tiempo de compilación. Además, la vinculación dinámica puede ayudar a eliminar el código basado en la reflexión, propenso a errores, incluso cuando los objetos implicados no son objetos dinámicos.
12.3.4 Tipos de subexpresiones
Cuando una operación está ligada estáticamente, el tipo de una subexpresión (por ejemplo, un receptor, un argumento, un índice o un operando) se considera siempre el tipo de dicha expresión en tiempo de compilación.
Cuando una operación está ligada dinámicamente, el tipo de una subexpresión se determina de distintas maneras en función del tipo en tiempo de compilación de la subexpresión:
- Se considera que una subexpresión de tipo dinámico en tiempo de compilación tiene el tipo del valor real al que se evalúa la expresión en tiempo de ejecución.
- Se considera que una subexpresión cuyo tipo en tiempo de compilación es un parámetro de tipo tiene el tipo al que está ligado el parámetro de tipo en tiempo de ejecución.
- De lo contrario, se considera que la subexpresión tiene su tipo de tiempo de compilación.
12.4 Operadores
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
+,-,*,/ynew. Algunos ejemplos de operandos son literales, campos, variables locales y expresiones. ejemplo final
Existen tres tipos de operadores:
- Operadores unarios. Los operadores unarios toman un operando y utilizan notación de prefijo (como
–x) o notación de sufijo (comox++). - Operadores binarios. Los operadores binarios toman dos operandos y todos utilizan la notación infija (como
x + y). - Operador ternario. Solo existe un operador ternario,
?:, que toma tres operandos y utiliza la notación infija (c ? x : y).
El orden de evaluación de los operadores en una expresión viene determinado por la precedencia y 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étodoFusando el valor antiguo dei, luego se llama al métodoGcon el valor antiguo dei, y, finalmente, se llama al métodoHcon el nuevo valor de i. Esto es independiente y no está relacionado con la precedencia de operadores. ejemplo final
Algunos operadores pueden sobrecargarse. La sobrecarga de operadores (sección 12.4.3) permite especificar implementaciones de operadores definidas por el usuario para operaciones en las que uno o ambos operandos son de una clase o tipo struct definidos por el usuario.
12.4.2 Precedencia y asociatividad de operadores
Cuando una expresión contiene varios operadores, la precedencia de los operadores controla el orden en que se evalúan los operadores individuales.
Nota: Por ejemplo, la expresión
x + y * zse evalúa comox + (y * z)porque el operador*tiene mayor precedencia 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 consiste en una secuencia de multiplicative_expressionseparada por operadores
+o-, lo que proporciona a los operadores+y-menor precedencia que los operadores*,/y%. nota final
Nota: la siguiente tabla resume todos los operadores en orden de precedencia de mayor a menor:
Subcláusula Categoría Operadores §12.8 Principal x.yx?.yf(x)a[x]a?[x]x++x--x!newtypeofdefaultcheckeduncheckeddelegatestackallocsección 12.9 Unario +-!x~^++x--x(T)xawait x§12.10 Intervalo ..§12.11 Conmutador switch { … }§12.12 Multiplicativo */%§12.12 Aditivo +-sección 12.13 Mayús <<>>sección 12.14 Comprobación de tipos y relacional <><=>=isassección 12.14 Igualdad ==!=§12.15 Y lógico &§12.15 XOR lógico ^§12.15 O lógico \|§12.16 AND condicional &&§12.16 OR condicional \|\|§12.17 y §12.18 Operación de fusión de NULL y expresión throw ??throw x§12.20 Condicional ?:§12.23 y §12.21 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 para los operadores de asignación, el operador de intervalo 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 + zse evalúa como(x + y) + z. ejemplo final - Los operadores de asignación, el operador de fusión NULL y el operador condicional (
?:) son de asociación a la derecha, lo que significa que las operaciones se realizan de derecha a izquierda.Ejemplo:
x = y = zse evalúa comox = (y = z). ejemplo final - El operador range no es asociativo, lo que significa que ni el operando izquierdo ni derecho de un operador de intervalo puede ser un range_expression.
Ejemplo: y
x..y..zx..(y..z)no son válidos, ya..que no son asociativos. ejemplo final
La precedencia y la asociatividad pueden controlarse mediante paréntesis.
Ejemplo:
x + y * zprimero multiplicayporzy luego suma el resultado porx, pero(x + y) * zprimero sumaxyyy luego multiplica el resultado porz. ejemplo final
12.4.3 Sobrecarga de operadores
Todos los operadores unarios y binarios tienen implementaciones predefinidas. Además, se pueden introducir implementaciones definidas por el usuario incluyendo declaraciones de operadores (sección 15.10) en clases y structs. Las implementaciones de operadores definidas por el usuario siempre tienen prioridad sobre las implementaciones de operadores predefinidas: Solo cuando no existan implementaciones aplicables de operadores definidos por el usuario se considerarán las implementaciones de operadores predefinidos, tal como se describe en la sección 12.4.4 y sección 12.4.5.
Los operadores unarios sobrecargablesson:
+ - !(solo negación lógica)~ ++ -- true false
Solo los operadores enumerados anteriormente pueden sobrecargarse. En concreto, no es posible sobrecargar el operador null-forgiving (postfix !, §12.8.9) o el operador unario from-end (prefijo ^, (§12.9.6)).
Nota: Aunque
trueyfalseno 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.26) y expresiones que implican los operadores lógicos condicionales (§12.20) y condicionales (§12.16). nota final
Los operadores binarios sobrecargablesson:
+ - * / % & | ^ << >> == != > < <= >=
Solo los operadores enumerados anteriormente pueden sobrecargarse. En concreto, no es posible sobrecargar el acceso a miembros, la invocación de método o los ..operadores , =, &&||???:=>checkeduncheckednewtypeofdefault, asy .is
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 de operador*=. Esto se describe más adelante en §12.23. ejemplo final
El operador de asignación (=) no puede sobrecargarse. Una asignación siempre realiza un almacén sencillo de un valor en una variable (§12.23.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 operadores
isoas. nota final
El acceso a elementos, como a[x], no se considera un operador sobrecargable. En cambio, la indexación definida por el usuario se soporta a través de indexadores (sección 15.9).
En las expresiones, los operadores se referencian utilizando la notación de operador, y en las declaraciones, los operadores se referencian utilizando la notación funcional. La siguiente tabla muestra la relación entre las notaciones de operador y funcional para los operadores unarios y binarios. En la primera entrada, "op" denota cualquier operador de prefijo unario sobrecargable. En la segunda entrada, "op" denota el postfijo unario y los operadores ++ y --. En la tercera entrada, "op" denota cualquier operador binario sobrecargable.
Nota: Para un ejemplo de sobrecarga de los operadores
++y--, consulte sección 15.10.2. nota final
| Notación de operador | Notación funcional |
|---|---|
«op» x |
operator «op»(x) |
x «op» |
operator «op»(x) |
x «op» y |
operator «op»(x, y) |
Las declaraciones de operadores definidos 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 operadores definidos por el usuario no pueden modificar la sintaxis, precedencia o asociatividad de un operador.
Ejemplo: El operador
/es siempre un operador binario, tiene siempre el nivel de precedencia especificado en la sección 12.4.2 y es siempre asociativo a la izquierda. ejemplo final
Nota: Aunque es posible que un operador definido por el usuario realice cualquier cálculo que desee, se desaconsejan firmemente las implementaciones que producen resultados diferentes de los que se esperan de forma intuitiva. Por ejemplo, una implementación de operador
==debe comparar los dos operandos para la igualdad y devolver un resultado apropiadobool. nota final
Las descripciones de operadores individuales de §12.9 a §12.23 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 de operador unario, resolución de sobrecarga de operador binario, promoción numérica y operador elevado, cuyas definiciones se encuentran en las siguientes subcláusulas.
12.4.4 Resolución de sobrecarga de operadores unarios
Una operación de la forma «op» x o x «op», donde "op" es un operador unario sobrecargable, y x es una expresión de tipo X, se procesa como sigue:
- El conjunto de operadores candidatos definidos por el usuario proporcionado por para
Xla operaciónoperator «op»(x)se determina utilizando las reglas de sección 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. En caso contrario, las implementaciones binarias predefinidas
operator «op», incluidas sus formas elevadas, se convierten en el conjunto de operadores candidatos para la operación. Las implementaciones predefinidas de un operador dado se especifican en la descripción del operador. Los operadores predefinidos proporcionados por un tipo de enumeración o un tipo delegado solo se incluyen en este conjunto cuando el tipo en tiempo de enlace de cualquiera de los operandos, o el tipo subyacente si es un tipo anulable, es el tipo de enumeración o el tipo delegado. - Las reglas de resolución de sobrecargas de sección 12.6.4 se aplican al conjunto de operadores candidatos para seleccionar el mejor operador con respecto a la lista de argumentos
(x), y este operador se convierte en el resultado del proceso de resolución de sobrecargas. Si la resolución de sobrecarga no consigue seleccionar el mejor operador, se produce un error de vinculación.
12.4.5 Resolución de sobrecarga de operadores binarios
Una operación de la forma 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 como sigue:
- Se determina el conjunto de operadores candidatos definidos por el usuario proporcionados por
XyYpara la operaciónoperator «op»(x, y). El conjunto consiste en la unión de los operadores candidatos proporcionados porXy los operadores candidatos proporcionados porY, cada uno determinado utilizando las reglas de sección 12.4.6. Para el conjunto combinado, los candidatos se fusionan de la siguiente manera:- Si
XyYson convertibles en identidad, o siXyYse derivan de un tipo base común, entonces los operadores candidatos compartidos solo aparecen en el conjunto combinado una vez. - Si hay una conversión de identidad entre
XyY, un operador«op»Yproporcionado porYtiene el mismo tipo de retorno que un«op»Xproporcionado porXy los tipos de operando de«op»Ytienen una conversión de identidad a los correspondientes tipos de operando«op»Xde entonces solo«op»Xse produce en el conjunto.
- Si
- 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. En caso contrario, las implementaciones binarias predefinidas
operator «op», incluidas sus formas elevadas, se convierten en el conjunto de operadores candidatos para la operación. Las implementaciones predefinidas de un operador dado se especifican en la descripción del operador. Para los operadores predefinidos de enumeración y delegado, los únicos operadores considerados son los proporcionados por un tipo enumeración o delegado que sea el tipo vinculante de uno de los operandos. - Las reglas de resolución de sobrecargas de sección 12.6.4 se aplican al conjunto de operadores candidatos para seleccionar el mejor operador con respecto a la lista de argumentos
(x, y), y este operador se convierte en el resultado del proceso de resolución de sobrecargas. Si la resolución de sobrecarga no consigue seleccionar el mejor operador, se produce un error de vinculación.
12.4.6 Operadores candidatos definidos por el usuario
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 como sigue:
- Determinar el tipo
T₀. SiTes un tipo de valor anulable,T₀es su tipo subyacente; en caso contrario,T₀es igual aT. - Para todas
operator «op»las declaraciones enT₀y todas las formas elevadas de dichos operadores, si al menos un operador es aplicable (sección 12.6.4.2) con respecto a la lista de argumentosA, entonces el conjunto de operadores candidatos está formado por todos los operadores aplicables enT₀. - En caso contrario, si
T₀esobject, el conjunto de operadores candidatos está vacío. - En caso contrario, el conjunto de operadores candidatos proporcionado por
T₀es el conjunto de operadores candidatos proporcionado por la clase base directa deT₀, o la clase base efectiva deT₀siT₀es un parámetro de tipo.
12.4.7 Promociones numéricas
12.4.7.1 General
Esta subcláusula es informativa.
Sección 12.4.7 y sus subcláusulas son un resumen del efecto combinado de:
- las reglas para conversiones numéricas implícitas (sección 10.2.3);
- las reglas para una mejor conversión (sección 12.6.4.7); y
- los operadores aritméticos disponibles (§12.12), relacionales (§12.14) e integrales (§12.15.2).
La promoción numérica consiste en realizar automáticamente ciertas conversiones implícitas de los operandos de los operadores numéricos unarios y binarios predefinidos. La promoción numérica no es un mecanismo distinto, sino un efecto de la aplicación de la resolución de sobrecarga a los operadores predefinidos. La promoción numérica no afecta específicamente a la evaluación de los operadores definidos por el usuario, aunque los operadores definidos por el usuario pueden implementarse 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 las reglas de resolución de sobrecarga (sección 12.6.4) a este conjunto de operadores, el efecto es seleccionar el primero de los operadores para los que existen conversiones implícitas a partir de los tipos de operando.
Ejemplo: Para la operación
b * s, dondebes unbyteyses unshort, la resolución de sobrecarga seleccionaoperator *(int, int)como mejor operador. Así, el efecto es quebysse convierten aint, y el tipo del resultado esint. Del mismo modo, para la operacióni * d, dondeies unintydes undouble,overloadla resolución seleccionaoperator *(double, double)como mejor operador. ejemplo final
Fin del texto informativo.
12.4.7.2 Promociones numéricas unarias
Esta subcláusula es informativa.
La promoción numérica unaria se produce para los operandos de los operadores predefinidos y unarios +, -y ~. La promoción numérica unaria consiste simplemente en convertir operandos de tipo sbyte, byte, short, ushort o char al tipo int. Además, para el operador unario, la promoción numérica unaria convierte operandos de tipo uint a tipo long.
Fin del texto informativo.
12.4.7.3 Promociones numéricas binarias
Esta subcláusula es informativa.
La promoción numérica binaria se produce para los operandos de los operadores predefinidos y binarios +, -, *, /, %, &, |, ^, ==, !=, >, <, >= y <=. La promoción numérica binaria convierte implícitamente ambos operandos a un tipo común que, en el caso de los operadores no relacionales, se convierte también en el tipo resultado de la operación. La promoción numérica binaria consiste en aplicar las siguientes reglas, en el orden en que aparecen aquí:
- Si cualquiera de los operandos es del tipo
decimal, el otro operando se convierte al tipodecimal, o se produce un error de vinculación si el otro operando es del tipofloatodouble. - De lo contrario, si cualquiera de los operandos es del tipo
double, el otro operando se convierte al tipodouble. - De lo contrario, si cualquiera de los operandos es del tipo
float, el otro operando se convierte al tipofloat. - De lo contrario, si cualquiera de los operandos es del tipo
ulong, el otro operando se convierte al tipoulong, o se produce un error de vinculación si el otro operando es del tipotype sbyte,short,intolong. - De lo contrario, si cualquiera de los operandos es del tipo
long, el otro operando se convierte al tipolong. - En caso contrario, si uno de los operandos es del tipo
uinty el otro es del tiposbyte,shortoint, ambos operandos se convierten al tipolong. - De lo contrario, si cualquiera de los operandos es del tipo
uint, el otro operando se convierte al tipouint. - En caso contrario, ambos operandos se convierten al tipo
int.
Nota: La primera regla prohíbe cualquier operación que mezcle el tipo
decimalcon los tiposdoubleyfloat. La regla se deduce del hecho de que no hay conversiones implícitas entre el tipodecimaly los tiposdoubleyfloat. nota final
Nota: Observe también que no es posible que un operando sea de tipo
ulongcuando el otro operando es de tipo integral con signo. La razón es que no existe ningún tipo integral que pueda representar el rango completo deulongasí como los tipos integrales con signo. nota final
En los dos casos anteriores, se puede utilizar una expresión cast para convertir explícitamente un operando a un tipo compatible con el otro operando.
Example: En el código de ejemplo siguiente
decimal AddPercent(decimal x, double percent) => x * (1.0 + percent / 100.0);se produce un error de vinculación porque un
decimalno puede multiplicarse por undouble. El error se resuelve convirtiendo explícitamente el segundo operando endecimal, 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 elevados
Un operador elevado permite que los operadores predefinidos y definidos por el usuario que operan en un tipo de valor que no acepta valores NULL también se usen con la forma que acepta valores NULL de ese tipo. Los operadores elevados se construyen a partir de operadores predefinidos y definidos por el usuario que cumplen ciertos requisitos, como se describe a continuación:
- 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. La forma elevada se construye añadiendo un único modificador?a los tipos del operando y del resultado. El operador elevado genera un valor denullsi el operando esnull. En caso contrario, el operador elevado desenvuelve el operando, aplica el operador subyacente y envuelve 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. La forma elevada se construye añadiendo un único modificador?a cada operando y tipo de resultado. El operador elevado genera unnullvalor si uno o ambos operandos sonnull(una excepción que es los&operadores y|delbool?tipo, como se describe en §12.15.5). De lo contrario, el operador elevado desenvuelve los operandos, aplica el operador subyacente y envuelve el resultado. - Para los operadores de igualdad
==y!=, existe una forma elevada de un operador si los tipos de los operandos son ambos tipos de valores que no admiten valores NULL y si el tipo del resultado esbool. La forma elevada se construye añadiendo un único modificador?a cada tipo de operando. El operador levantado considera dos valores igualesnull, y un valornulldesigual a cualquier valor que no seanull. Si ambos operandos son nonull, el operador elevado desenvuelve los operandos y aplica el operador subyacente para producir el resultadobool. - Para los operadores relacionales
<,>,<=y>=, existe una forma elevada de un operador si los tipos de operando son tipos de valor no anulables y si el tipo de resultado esbool. La forma elevada se construye añadiendo un único modificador?a cada tipo de operando. El operador elevado genera el valorfalsesi uno o ambos operandos sonnull. En caso contrario, el operador elevado desenvuelve los operandos y aplica el operador subyacente para obtener el resultadobool.
12.5 Búsqueda de miembros
12.5.1 General
La búsqueda de miembros es el proceso mediante el cual se determina el significado de un nombre en el contexto de un tipo. Una búsqueda de miembro 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 ocurre como la primary_expression de una invocation_expression (§12.8.10.2), se dice que el miembro es invocado.
Si un miembro es un método o evento, o si es una constante, un campo o una propiedad de un tipo delegado (§21) o del tipo dynamic (§8.2.4), se dice que el miembro es invocable.
La búsqueda de miembros no solo tiene en cuenta el nombre de un miembro, sino también el número de parámetros de tipo que tiene y si el miembro es accesible. A efectos de 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 indicado en sus respectivas declaraciones y todos los demás miembros tienen cero parámetros de tipo.
Una búsqueda de miembros de un nombre N con argumentos de tipo K en un tipo T se procesa como sigue:
- En primer lugar, se determina un conjunto de miembros accesibles denominados
N:- Si
Tes un parámetro de tipo, entonces el conjunto es la unión de los conjuntos de miembros accesibles nombradosNen cada uno de los tipos especificados como restricción primaria o restricción secundaria (sección 15.2.5) paraT, junto con el conjunto de miembros accesibles nombradosNenobject. - En caso contrario, el conjunto está formado por todos los miembros accesibles (sección 7.5) nombrados
NenT, incluidos los miembros heredados y los miembros accesibles nombradosNenobject. SiTes un tipo construido, el conjunto de miembros se obtiene sustituyendo los argumentos de tipo como se describe en la sección 15.3.3. Los miembros que incluyen un modificadoroverridese excluyen del conjunto.
- Si
- A continuación, si
Kes cero, se eliminan todos los tipos anidados cuyas declaraciones incluyan parámetros de tipo. SiKno es cero, se eliminan todos los miembros con un número diferente de parámetros de tipo. CuandoKes cero, los métodos que tienen parámetros de tipo no se eliminan, ya que el proceso de inferencia de tipos (sección 12.6.3) podría ser capaz de inferir los argumentos de tipo. - A continuación, si se invoca el miembro, se eliminan del conjunto todos los miembros no invocables.
- A continuación, los miembros ocultos por otros miembros se eliminan del conjunto. Para cada miembro
S.Mdel conjunto, dondeSes el tipo en el que se declara el miembroM, se aplican las siguientes reglas:- Si
Mes un miembro constante, campo, propiedad, evento o enumeración, entonces todos los miembros declarados en un tipo base deSson eliminados del conjunto. - Si
Mes una declaración de tipo, todos los no tipos declarados en un tipo base deSse eliminan del conjunto, y todas las declaraciones de tipo con el mismo número de parámetros de tipo que los declarados comoMen un tipo base deSse eliminan del conjunto. - Si
Mes un método, todos los miembros no-método declarados en un tipo base deSse eliminan del conjunto.
- Si
- A continuación, se eliminan del conjunto los miembros de la interfaz que están ocultos por miembros de la clase. Este paso solo tiene efecto si
Tes un parámetro de tipo yTtiene una clase base efectiva distinta deobjecty un conjunto de interfaces efectivas no vacío (sección 15.2.5). Para cada miembro del conjuntoS.M, dondeSes el tipo en el que se declara el miembroM, se aplican las siguientes reglas siSes una declaración de clase distinta deobject:- Si
Mes 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 eliminan del conjunto. - Si
Mes un método, se eliminan del conjunto todos los miembros no-método declarados en una declaración de interfaz, y se eliminan del conjunto todos los métodos con la misma firmaMque la declarada en una declaración de interfaz.
- Si
- Por último, una vez eliminados los 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.
- En caso contrario, si el conjunto solo contiene métodos, el resultado de la búsqueda será este grupo de métodos.
- En caso contrario, la búsqueda es ambigua y se produce un error de tiempo de vinculación.
Para las búsquedas de miembros en tipos que no sean parámetros de tipo e interfaces, y las búsquedas de miembros en interfaces que son estrictamente de herencia única (cada interfaz en 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 a 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 §19.4.11.
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 el uso adicional del grupo de métodos debido a su ambigüedad, como se describe en §12.6.4.1 y §12.6.6.2. nota final
12.5.2 Tipos base
Para propósitos de búsqueda de miembros, se considera que un tipo T tiene los siguientes tipos base:
- Si
Tesobjectodynamic, entoncesTno tiene tipo base. - Si
Tes un enum_type, los tipos base deTson los tipos de claseSystem.Enum,System.ValueTypeyobject. - Si
Tes un struct_type, los tipos base deTson los tipos de claseSystem.ValueTypeyobject.Nota: un nullable_value_type es un struct_type (§8.3.1). nota final
- Si
Tes un class_type, los tipos base deTson las clases base deT, incluido el tipo de claseobject. - Si
Tes un interface_type, los tipos base deTson las interfaces base deTy el tipo de claseobject. - Si
Tes un array_type, los tipos base deTson los tipos de claseSystem.Arrayyobject. - Si
Tes un delegate_type, los tipos base deTson los tipos de claseSystem.Delegateyobject.
12.6 Miembros de función
12.6.1 General
Los miembros de función son aquellos que contienen instrucciones ejecutables. Los miembros de función son siempre miembros de tipos y no pueden ser miembros de namespaces. 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 pueden invocarse explícitamente), las instrucciones contenidas en los miembros de función se ejecutan mediante invocaciones a miembros de función. La sintaxis real para escribir una invocación a un miembro de función depende de la categoría particular del miembro de función.
La lista de argumentos (sección 12.6.2) de una invocación a un miembro funcional proporciona valores reales o referencias a variables para los parámetros del miembro funcional.
Las invocaciones de métodos genéricos pueden emplear la inferencia de tipo para determinar el conjunto de argumentos de tipo que deben pasarse al método. Este proceso se describe en la sección 12.6.3.
Las invocaciones de métodos, indizadores, operadores y constructores de instancia emplean la resolución de sobrecarga para determinar cuál miembro de funciones de un conjunto candidato se debe invocar. Este proceso se describe en la sección 12.6.4.
Una vez que se ha identificado un miembro de función concreto 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: La siguiente tabla resume el procesamiento que tiene lugar en las construcciones que implican las seis categorías de miembros de función que pueden invocarse explícitamente. En la tabla,
e,x,yyvaluey indican expresiones clasificadas como variables o valores,Tindica una expresión clasificada como tipo,Fes el nombre simple de un método yPes 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 Fde la clase o estructura contenedora. El método se invoca con la lista de argumentos(x, y). Si el método no esstatic, la expresión de instancia esthis.T.F(x, y)La resolución de sobrecarga se aplica para seleccionar el mejor método Fde la clase o estructuraT. Si el método no esstatic. El método se invoca con la lista de argumentos(x, y).e.F(x, y)La resolución de sobrecarga se aplica para seleccionar el mejor método Fde la clase, estructura o interfaz dada por el tipo dee. Se produce un error de vinculación si el método esstatic. El método se invoca con la expresión de instanciaey la lista de argumentos(x, y).Property Access PSe invoca el descriptor de acceso get de la propiedad Pen la clase o struct contenedora. Se produce un error de compilación siPes de escritura únicamente. SiPno esstatic, la expresión de instancia esthis.P = valueSe invoca al descriptor de acceso set de la propiedad Pen la clase o struct contenedora con la lista de argumentos(value). Se produce un error de compilación siPes de solo lectura. SiPno esstatic, la expresión de instancia esthis.T.PSe invoca al descriptor de acceso get de la propiedad Pen la clase o struct contenedoraT. Se produce un error de compilación siPno esstatico siPes de solo escritura.T.P = valueEl accesor 'set' de la propiedad Pen la clase o estructuraTse invoca con la lista de argumentos(value). Se produce un error de compilación siPno esstatico siPes de solo lectura.e.PEl descriptor de acceso get de la propiedad Pen la clase, struct o interfaz dada por el tipo deEse invoca con la expresión de instanciae. Se produce un error de vinculación siPesstatico siPes de solo escritura.e.P = valueEl accesor de conjunto de la propiedad Pen la clase, struct o interfaz perteneciente al tipoEse invoca con la expresión de instanciaey lista de argumentos(value). Se produce un error de vinculación siPesstatico siPes de solo lectura.Acceso a eventos E += valueSe invoca al descriptor de acceso add del evento Een la clase o struct contenedora. SiEno esstatic, la expresión de instancia esthis.E -= valueSe invoca al descriptor de acceso remove del evento Een la clase o struct contenedora. SiEno esstatic, la expresión de instancia esthis.T.E += valueSe invoca al descriptor de acceso add del evento Een la clase o structT. Se produce un error de vinculación siEno esstatic.T.E -= valueSe invoca al descriptor de acceso remove del evento Een la clase o structT. Se produce un error de vinculación siEno esstatic.e.E += valueEl accesor 'add' del evento Een la clase, struct o interfaz proporcionada por el tipo deEse invoca mediante la expresión de instanciae. Se produce un error de vinculación siEesstatic.e.E -= valueEl descriptor de acceso remove del evento Een la clase, struct o interfaz dada por el tipo deEse invoca con la expresión de instanciae. Se produce un error de vinculación siEesstatic.Acceso a indizador e[x, y]La resolución de sobrecarga se aplica para seleccionar el mejor indexador en la clase, estructura o interfaz dada por el tipo de e. El descriptor de acceso get del indexador se invoca con la expresión de instanciaey la lista de argumentos(x, y). Se produce un error de vinculación si es o si es de solo escritura.e[x, y] = valueLa resolución de sobrecarga se aplica para seleccionar el mejor indexador en la clase, estructura o interfaz dada por el tipo de e. El descriptor de acceso set del indexador se invoca con la expresión de instanciaey la lista de argumentos(x, y, value). Se produce un error de vinculación si es o si es de solo lectura.Invocación del operador -xLa resolución de sobrecarga se aplica para seleccionar el mejor operador unario en la clase o estructura dada por el tipo de x. El operador seleccionado se invoca con la lista de argumentos(x).x + yLa resolución de sobrecarga se aplica para seleccionar el mejor operador binario en las clases o estructuras dadas por los tipos de xyy. El operador seleccionado se invoca con la lista de argumentos(x, y).Invocación del constructor de instancia new T(x, y)La resolución de sobrecarga se aplica para seleccionar el mejor constructor de instancia en la clase o estructura T. El constructor de instancia se invoca con la lista de argumentos(x, y).nota final
12.6.2 Listas de argumentos
12.6.2.1 General
Cada miembro de función e invocación de 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 a un miembro funcional depende de la categoría del miembro funcional:
- Por ejemplo, para constructores, métodos, indizadores y delegados, los argumentos se especifican como argument_list, como se describe a continuación. En el caso de los indexadores, cuando se invoca al descriptor de acceso set, la lista de argumentos incluye además la expresión especificada como 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 accesor 'set'. nota final
- En el caso de las propiedades, la lista de argumentos está vacía cuando se invoca al descriptor de acceso get, y consiste en la expresión especificada como operando derecho del operador de asignación cuando se invoca al descriptor de acceso set.
- Para los eventos, la lista de argumentos está formada por la expresión especificada como operando derecho del operador
+=o-=. - Para los operadores definidos por el usuario, la lista de argumentos consiste en el operando único del operador unario o los dos operandos del operador binario.
Los argumentos de propiedades (sección 15.7) y eventos (sección 15.8) se pasan siempre como parámetros de valor (sección 15.6.2.2). Los argumentos de los operadores definidos por el usuario (sección 15.10) se pasan siempre como parámetros de valor (sección 15.6.2.2) o parámetros de entrada (sección 9.2.8). Los argumentos de los indexadores (sección 15.9) siempre se pasan como parámetros de valor (sección 15.6.2.2), parámetros de entrada (sección 9.2.8) o matrices de parámetros (sección 15.6.2.4). Estas categorías de miembros de función no admiten parámetros de salida ni de referencia.
Los argumentos de un constructor de instancia, método, indexador o invocación de delegado se especifican como 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
;
Una argument_list consta de uno o más argumentos, separados por comas. Cada argumento consta de un argument_name opcional seguido de un argument_value. Un argumento con un argument_name se denomina argumento con nombre, mientras que un argumento sin argument_name es un argumento posicional.
El argument_value puede adoptar una de las siguientes formas:
- Una 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 se pasa de esa forma, como se determina en§12.6.4.2 y se describe en §12.6.2.3.
- La palabra clave
inseguida de una variable_reference (sección 9.5), que indica que el argumento se pasa como parámetro de entrada (sección 15.6.2.3.2). Una variable debe asignarse definitivamente (sección 9.4) antes de poder pasarse como parámetro de entrada. - La palabra clave
refseguida de una variable_reference (sección 9.5), que indica que el argumento se pasa como parámetro de referencia (sección 15.6.2.3.3). Una variable deberá estar asignada definitivamente (sección 9.4) antes de que pueda pasarse como parámetro de referencia. - La palabra clave
outseguida de una variable_reference (sección 9.5), que indica que el argumento se pasa como parámetro de salida (sección 15.6.2.3.4). Una variable se considera definitivamente asignada (sección 9.4) tras una invocación a un miembro de función en la que la variable se pasa como parámetro de salida.
La forma determina el modo de paso de parámetros del argumento: valor, entrada, referencia o salida, respectivamente. Sin embargo, como se ha mencionado anteriormente, un argumento con modo de paso de valor, puede transformarse en uno con 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 existir un parámetro correspondiente en el miembro de función o delegado invocado.
La lista de parámetros utilizada a continuación se determina del siguiente modo:
- Para los métodos virtuales y los indexadores definidos en clases, la lista de parámetros se extrae de la primera declaración o anulación del miembro de la función que se encuentra al comenzar con el tipo estático del receptor y buscar a través de sus clases base.
- Para los métodos parciales se utiliza la lista de parámetros de la declaración del método parcial que los define.
- Para todos los demás miembros de función y delegados existe una única lista de parámetros, que es la que se utiliza.
La posición de un argumento o parámetro se define como el número de argumentos o parámetros que le preceden en la lista de argumentos o la lista de parámetros.
Los parámetros correspondientes a los argumentos miembros de una función se establecen del siguiente modo:
- Argumentos en la argument_list de constructores de instancia, métodos, indexadores y delegados:
- Un argumento posicional en el que un parámetro aparece en la misma posición en la lista de parámetros corresponde a ese parámetro, a menos que el parámetro sea una matriz de parámetros y el miembro de la función se invoque en su forma expandida.
- Un argumento posicional de un miembro de función con una matriz de parámetros invocada en su forma expandida, que aparece 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, cuando se invoca al descriptor de acceso set, la expresión especificada como operando derecho del operador de asignación corresponde al parámetro implícito
valuede la declaración del descriptor de acceso set.
- Para las propiedades, cuando se invoca al descriptor de acceso get no hay argumentos. Al invocar el accesor 'set', la expresión especificada como operando derecho del operador de asignación corresponde al parámetro implícito de valor de la declaración del accesor 'set'.
- Para los operadores unarios definidos por el usuario (incluidas las conversiones), el operando único corresponde al parámetro único de la declaración del 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 del operador.
- Un argumento sin nombre no corresponde a ningún parámetro cuando sigue a un argumento con nombre desordenado o a un argumento con nombre que corresponde a un array de parámetros.
Nota: esto impide que
void M(bool a = true, bool b = true, bool c = true);sea invocado porM(c: false, valueB);. El primer argumento se utiliza fuera de posición (el argumento se utiliza en primera posición, pero el parámetro nombradocestá en tercera posición), por lo que los argumentos siguientes deben nombrarse. En otras palabras, solo se permiten argumentos con nombre no final 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 a un miembro de una función (sección 12.6.6), las expresiones o referencias a variables de una lista de argumentos se evalúan en orden, de izquierda a derecha, de la siguiente manera:
Para un argumento de valor, si el modo de paso del parámetro es valor
se evalúa la expresión del argumento y se realiza una conversión implícita (sección 10.2) al tipo de parámetro correspondiente. El valor resultante se convierte en el valor inicial del parámetro de valor en la invocación del miembro de la función.
en caso contrario, el modo de paso del parámetro es input. Si el argumento es una referencia variable y existe una conversión de identidad (sección 10.2.2) entre el tipo del argumento y el tipo del parámetro, el almacén resultante se convierte en el almacén representado por el parámetro en la invocación del miembro de la función. En caso contrario, se crea un almacén con el mismo tipo que el del parámetro correspondiente. Se evalúa la expresión del argumento y se realiza una conversión implícita (sección 10.2) al tipo de parámetro correspondiente. El valor resultante se almacena en esa ubicación de almacenamiento. Ese almacén está representado por el parámetro de entrada en la invocación del miembro de la 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 argumentEn la llamada al método
M1(i),ise pasa como argumento de entrada, porque se clasifica como variable y tiene el mismo tipointque el parámetro de entrada. En la llamada al métodoM1(i + 5), se crea una variable sin nombreint, se inicializa con el valor del argumento y se pasa como argumento de entrada. Consulte sección 12.6.4.2 y sección 12.6.4.4.ejemplo final
Para un argumento de entrada, salida o referencia, se evalúa la referencia de la variable y el almacén resultante se convierte en el almacén representado por el parámetro en la invocación del miembro de la función. Para un argumento de entrada o de referencia, la variable se asignará definitivamente en el punto de la llamada al método. Si la referencia variable se da como argumento de salida, o es un elemento de matriz de reference_type, se realiza una comprobación en tiempo de ejecución para asegurar que el tipo de elemento de la matriz es idéntico al tipo del parámetro. Si esta comprobación falla, se lanza un
System.ArrayTypeMismatchException.
Nota: esta comprobación en tiempo de ejecución es necesaria debido a la covarianza de matrices (sección 17.6). nota final
Example: En el código de ejemplo 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
Fprovoca que se lance unSystem.ArrayTypeMismatchException, porque el tipo real de elemento debesstringy noobject.ejemplo final
Los métodos, indexadores y constructores de instancia pueden declarar que su parámetro situado más a la derecha es una matriz de parámetros (sección 15.6.2.4). Estos miembros de función se invocan en su forma normal o en su forma expandida, dependiendo de cuál sea aplicable (sección 12.6.4.2):
- Cuando un miembro de función con una matriz de parámetros se invoca en su forma normal, el argumento dado para la matriz de parámetros deberá ser una única expresión que sea convertible implícitamente (sección 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 un miembro de función con una matriz de parámetros se invoca en su forma expandida, la invocación deberá 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 (sección 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 los argumentos dados y utiliza la instancia de matriz recién creada como argumento real.
Las expresiones de una lista de argumentos se evalúan siempre en orden textual.
Ejemplo: Así, 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 = 3ejemplo 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.4) se insertara alrededor de los argumentos expandidos. Se pasa un array vacío cuando no hay argumentos para el parámetro array; no se especifica si la referencia pasada es a un array vacío recién asignado o existente.
Ejemplo: Dada la declaración
void F(int x, int y, params object[] args);las siguientes invocaciones de la forma expandida del método
F(10, 20, 30, 40); F(10, 20, 1, "hello", 3.0);corresponden exactamente a
F(10, 20, new object[] { 30, 40 }); F(10, 20, new object[] { 1, "hello", 3.0 });ejemplo final
Cuando se omiten los argumentos de un miembro de función con parámetros opcionales correspondientes, se pasan implícitamente los argumentos por defecto de la declaración del miembro de función. (Esto puede implicar la creación de un almacén, como se ha descrito anteriormente).
Nota: Dado que estos son siempre constantes, su evaluación no afectará a la evaluación del resto de argumentos. nota final
12.6.3 Inferencia de tipo
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 deducir argumentos de tipo para la llamada. La presencia de la inferencia de tipo permite utilizar 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 tipo, los argumentos de tipo
intystringse determinan a partir de los argumentos del método.ejemplo final
La inferencia de tipo se produce como parte del procesamiento en tiempo de enlace de la invocación a un método (sección 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 concreto en una invocación de método, y no se especifican argumentos de tipo como parte de la invocación de método, se aplica la inferencia de tipo a cada método genérico del grupo de métodos. Si la inferencia de tipo tiene éxito, los argumentos de tipo inferidos se utilizan para determinar los tipos de argumentos para la posterior resolución de sobrecarga. Si la resolución de sobrecarga elige un método genérico como el que se va a invocar, entonces los argumentos de tipo inferidos se utilizan como los argumentos de tipo para la invocación. Si la inferencia de tipo para un método en particular falla, ese método no participa en la resolución de sobrecarga. El fallo de la inferencia de tipos, en sí mismo, no causa un error de vinculación. Sin embargo, a menudo provoca un error de tiempo de vinculación cuando la resolución de sobrecargas no logra encontrar ningún método aplicable.
Si cada argumento suministrado no corresponde exactamente a un parámetro del método (sección 12.6.2.2), o hay un parámetro no opcional sin argumento correspondiente, la inferencia falla inmediatamente. En caso contrario, supongamos que el método genérico tiene la siguiente firma:
Tₑ M<X₁...Xᵥ>(T₁ p₁ ... Tₓ pₓ)
Con una llamada a un método de la forma la tarea de la inferencia M(E₁ ...Eₓ) de tipo es encontrar argumentos de tipo único S₁...Sᵥ para cada uno de los parámetros de tipo X₁...Xᵥ para que la llamada M<S₁...Sᵥ>(E₁...Eₓ) sea válida.
El proceso de inferencia de tipo se describe a continuación como un algoritmo. Un compilador conforme puede implementarse utilizando un enfoque alternativo, siempre que llegue al mismo resultado en todos los casos.
Durante el proceso de inferencia, cada parámetro Xᵢ de tipo se fija a un tipo particular Sᵢ o no se fija con un conjunto asociado de límites. Cada uno de los límites es de un tipo T. Inicialmente cada variable de tipo Xᵢ no está fijada con un conjunto vacío de límites.
La inferencia de tipos tiene lugar en fases. Cada fase intentará inferir argumentos de tipo para más variables de tipo basándose en los resultados de la fase anterior. La primera fase realiza algunas inferencias iniciales de límites, mientras que la segunda fase asigna variables de tipo a tipos específicos e infiere límites adicionales. La segunda fase puede tener 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.15) y la búsqueda del mejor tipo común de un conjunto de expresiones (§12.6.3.16). nota final
12.6.3.2 La primera fase
Para cada uno de los argumentos Eᵢdel método , se realiza una inferencia de tipo de entrada (§12.6.3.7) del Eᵢ tipo Tⱼde parámetro correspondiente.
12.6.3.3 Segunda fase
La segunda fase procede como sigue:
- Todas las variables de tipo
Xᵢque no dependen de (§12.6.3.6)Xₑson fijas (§12.6.3.13). - Si no existen tales variables de tipo, todas las variables de tipo no fijas
Xᵢson fijas para las que se cumplen todas las siguientes condiciones:- Hay al menos una variable de tipo
Xₑde la cual depende deXᵢ -
Xᵢtiene un conjunto no vacío de límites
- Hay al menos una variable de tipo
- Si no existen tales variables de tipo y sigue habiendo variables de tipo no fijas, la inferencia de tipo falla.
- De lo contrario, si no existen más variables de tipo no fijadas, la inferencia de tipos tiene éxito.
- De lo contrario, para todos los argumentos
EᵢTⱼcon el tipo de parámetro correspondiente en el que los tipos de salida (§12.6.3.5) contienen variablesXₑde tipo sin fijar, pero los tipos de entrada (§12.6.3.4) no lo hacen, se realizaEᵢuna inferencia de tipo de salida (Tⱼ). 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 de árbol de expresión, todos los tipos de parámetros de T son tipos de entrada deEcon tipoT.
12.6.3.5 Tipos de salida
Si E es un grupo de métodos o una función anónima y T es de tipo delegado o de tipo árbol de expresión, entonces el tipo de devolución de T es un tipo de salidaEcon tipoT.
12.6.3.6 Dependencia
Una variable de tipo no fijoXᵢdepende directamente de una variable de tipo no fijoXₑ si para algún argumento Eᵥ con tipo TᵥXₑ ocurre en un tipo de entrada de Eᵥ con tipo Tᵥ y Xᵢ ocurre en un tipo de salida de Eᵥ con tipo Tᵥ.
Xₑ
depende deXᵢ si Xₑdepende directamente deXᵢ o si Xᵢdepende directamente deXᵥ y Xᵥdepende deXₑ. Así, "depende de" es el cierre transitivo pero no reflexivo de "depende directamente de".
12.6.3.7 Inferencias de tipo de entrada
Una inferencia de tipo de entrada se realiza desde una expresión Ea un tipo T de la siguiente manera:
- Si
Ees una expresión de tupla (§12.8.6) con aridadNy elementosEᵢ, yTes un tipo de tupla con aridadNcon los tiposTₑde elemento correspondientes oTes un tipo de valor que acepta valores NULL yT0?es un tipoT0de tupla con aridadNque tiene un tipoTₑde elemento correspondiente , a continuación, para cadaEᵢ, se realiza una inferencia de tipo de entrada deEᵢaTₑ. - Si
Ees una función anónima, se realiza una inferencia de tipo de parámetro explícita (§12.6.3.9) deEa .T - De lo
Econtrario, siUtiene un tipo y el parámetro correspondiente es un parámetro de valor (§15.6.2.2), se realizaUuna inferencia de límite inferior (T). - De lo contrario, si
Etiene un tipoUy el parámetro correspondiente es un parámetro de referencia (§15.6.2.3.3) o un parámetro de salida (§15.6.2.3.4), se realiza una inferencia exacta (§12.6.3.10) desdeUaT. - De lo contrario, si
Etiene un tipoUy el parámetro correspondiente es un parámetro de entrada (§15.6.2.3.2) yEes un argumento de entrada, se realiza una inferencia exacta (§12.6.3.10) desdeUaT. - De lo
Econtrario, siUtiene un tipo y el parámetro correspondiente es un parámetro de entrada (§15.6.2.3.2), se realizaUuna inferencia de límite inferior (T). - En caso contrario, no se realiza ninguna inferencia para este argumento.
12.6.3.8 Inferencias de tipo de salida
Una inferencia de tipo de salida se realiza desde una expresión Ea un tipo T de la siguiente manera:
- Si
Ees una expresión de tupla con aridadNy elementosEᵢ, yTes un tipo de tupla con aridadNcon los tiposTₑde elemento correspondientes oTes un tipoT0?de valor que acepta valores NULL yT0es un tipo de tupla con aridadNque tiene un tipoTₑde elemento correspondiente , para cadaEᵢuna de las inferencias de tipo de salida se realiza deEᵢaTₑ. - Si
Ees una función anónima con tipoUde valor devuelto inferido (§12.6.3.14) yTes un tipo de delegado o tipo de árbol de expresión con tipoTₓde valor devuelto , se realiza una inferencia de límite inferior (§12.6.3.11) desdeUaTₓ. - De lo contrario, si
es un grupo de métodos y es un tipo delegado o tipo de árbol de expresión con tipos de parámetros y tipo de valor devuelto , y la resolución de sobrecarga de con los tipos produce un único método con tipo de valor devuelto , entonces se realiza una inferencia de límite inferior de a . - En caso contrario, si
Ees una expresión de tipoU, se realiza una inferencia de límite inferiordeUaT. - En caso contrario, no se realizan inferencias.
12.6.3.9 Inferencias de tipo de parámetro explícito
Una inferencia explícita de tipo de parámetro se hace de una expresión Ea un tipo T de la siguiente manera:
- Si
Ees una función anónima explícitamente tipada con tiposU₁...Uᵥde parámetros yTes un tipo delegado o un tipo de árbol de expresión con tiposV₁...Vᵥde parámetros, para cadaUᵢuna de las inferencias exactas (§12.6.3.10) se realiza desdeUᵢa la correspondienteVᵢ.
12.6.3.10 Inferencias exactas
La inferencia exactade un tipo Ua otro tipo V se realiza del siguiente modo:
- Si
Ves uno de losXᵢno fijos, entoncesUse añade al conjunto de límites exactos paraXᵢ. - En caso contrario, los conjuntos
V₁...VₑyU₁...Uₑse determinan comprobando si se da alguno de los siguientes casos:-
Ves un tipo de matrizV₁[...]yUes un tipo de matrizU₁[...]del mismo rango -
Ves el tipoV₁?yUes el tipoU₁ -
Ves un tipo construidoC<V₁...Vₑ>yUes un tipo construidoC<U₁...Uₑ>
Si se da alguno de estos casos, se hace una inferencia exacta de cada uno deUᵢal correspondienteVᵢ.
-
- En caso contrario, no se realizan inferencias.
12.6.3.11 Inferencias enlazadas a inferior
Una inferencia de límite inferior de un tipo Ua otro tipo V se hace como sigue:
- Si
Ves uno de losXᵢno fijados, entonces se agregaUal conjunto de límites inferiores paraXᵢ. - De lo contrario, si
Ves el tipoV₁?yUes el tipoU₁?entonces se hace una inferencia de límite inferior deU₁aV₁. - En caso contrario, los conjuntos
U₁...UₑyV₁...Vₑse determinan comprobando si se da alguno de los siguientes casos:-
Ves un tipo de matrizV₁[...]yUes un tipo de matrizU₁[...]del mismo rango -
Ves uno deIEnumerable<V₁>,ICollection<V₁>,IReadOnlyList<V₁>>,IReadOnlyCollection<V₁>oIList<V₁>yUes un tipo de matriz unidimensionalU₁[] -
Ves un tipo construidoclass,struct,interfaceodelegateC<V₁...Vₑ>y hay un tipo únicoC<U₁...Uₑ>de modo queU(o, siUes un tipoparameter, su clase base efectiva o cualquier miembro de su conjunto de interfaz eficaz) es idéntica a,inheritsde (directa o indirectamente) o implementa (directa o indirectamente)C<U₁...Uₑ>. - (La restricción de "unicidad" significa que en el caso interfaz
C<T>{} class U: C<X>, C<Y>{}, entonces no se hace ninguna inferencia al inferir deUaC<T>porqueU₁podría serXoY.)
Si se aplica cualquiera de estos casos, se realiza una inferencia de cadaUᵢal correspondienteVᵢcomo se indica a continuación: - Si no se sabe que
Uᵢes un tipo de referencia, se hace una inferencia exacta de . - De lo contrario, si
Ues un tipo de matriz, se realiza una inferencia de límite inferior . - En caso contrario, si
VesC<V₁...Vₑ>entonces la inferencia depende del parámetro de tipoi-thdeC:- 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 .
-
- En caso contrario, no se realizan inferencias.
12.6.3.12 Inferencias enlazadas superior
Una inferencia de límite superior de un tipo Ua un tipo V se hace como sigue:
- Si
Ves uno de losXᵢno fijados,Use agrega al conjunto de límites superiores paraXᵢ. - En caso contrario, los conjuntos
V₁...VₑyU₁...Uₑse determinan comprobando si se da alguno de los siguientes casos:-
Ues un tipo de matrizU₁[...]yVes un tipo de matrizV₁[...]del mismo rango -
Ues uno deIEnumerable<Uₑ>,ICollection<Uₑ>,IReadOnlyList<Uₑ>,IReadOnlyCollection<Uₑ>oIList<Uₑ>yVes un tipo de matriz unidimensionalVₑ[] -
Ues el tipoU1?yVes el tipoV1? -
Ues de tipo clase, struct, interfaz o delegado construidoC<U₁...Uₑ>yVes un tipoclass, struct, interfaceodelegateque esidenticala,inheritsdesde (directa o indirectamente) o implementa (directa o indirectamente) un tipo únicoC<V₁...Vₑ> - (La restricción de "unicidad" significa que dada una interfaz
C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}, no se realiza ninguna inferencia cuando se infiere deC<U₁>aV<Q>. No se realizan inferencias deU₁a niX<Q>oY<Q>.)
Si se aplica cualquiera de estos casos, se realiza una inferencia de cadaUᵢal correspondienteVᵢcomo se indica a continuación: - Si no se sabe que
Uᵢes un tipo de referencia, se hace una inferencia exacta de . - De lo contrario, si
Ves un tipo de matriz, se realiza una inferencia de límite superior . - En caso contrario, si
UesC<U₁...Uₑ>entonces la inferencia depende del parámetro de tipoi-thdeC:- 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 .
-
- En caso contrario, no se realizan inferencias.
12.6.3.13 Corrección
Una variable de tipo no fijadaXᵢ con un conjunto de límites se fija de la siguiente manera:
- El conjunto de tipos candidatos
Uₑcomienza como el conjunto de todos los tipos en el conjunto de límites establecidos paraXᵢ. - Cada límite para
Xᵢse examina uno por uno: para cada límite exacto U deXᵢ, todos los tiposUₑque no son idénticos aUse eliminan del conjunto candidato. En cada límite inferiorUdeXᵢ, se eliminan del conjunto de candidatos todos los tiposUₑque no sean el resultado de una conversión implícita a partir deU. En cada límite superior U deXᵢ, se eliminan del conjunto de candidatos todos los tiposUₑque no sean una conversión implícita aU. - Si entre los tipos candidatos restantes
Uₑhay un único tipoVal que existe una conversión implícita desde todos los demás tipos candidatos, entoncesXᵢse fija aV. - En caso contrario, la inferencia de tipo falla.
12.6.3.14 Tipo de valor devuelto inferido
El tipo de devolución inferido de una función anónima F se utiliza durante la inferencia de tipos y la resolución de sobrecargas. El tipo de retorno inferido solo puede determinarse para una función anónima en la que se conocen todos los tipos de parámetros, ya sea porque se dan explícitamente, porque se proporcionan a través de una conversión de función anónima o porque se infieren durante la inferencia de tipos en una invocación a un método genérico adyacente.
El tipo de retorno efectivo inferido se determina como sigue:
- Si el cuerpo de
Fes una expresión que tiene un tipo, entonces el tipo de retorno efectivo inferido de es el tipoFde esa expresión. - Si el cuerpo de
Fes un bloque y el conjunto de expresiones de las instrucciones delreturnbloque tiene un tipoTcomún (§12.6.3.16), el tipo de valor devuelto efectivo inferido deFesT. - De lo contrario, no se puede deducir un tipo de retorno efectivo para
F.
El tipo de retorno inferido se determina como sigue:
- Si
Fes asincrónico y el cuerpo deFes una expresión clasificada como nulo (§12.2), o un bloque donde ninguna declaración dereturntiene expresiones, el tipo inferido de valor devuelto es«TaskType»(§15.14.1). - Si
Fes asincrónico y tiene un tipoTde valor devuelto efectivo inferido , el tipo de valor devuelto inferido es«TaskType»<T>»(§15.14.1). - Si
Fno es asincrónico y tiene un tipo de retorno efectivo inferidoT, el tipo de retorno inferido esT. - De lo contrario, no se puede deducir un tipo de retorno para
F.
Ejemplo: Como ejemplo de inferencia de tipo que involucra funciones anónimas, considere el método de extensión
Selectdeclarado en la claseSystem.Linq.Enumerable: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 espacio de nombres
System.Linqse importó con una directivausing namespace, y dada una claseCustomercon una propiedad de tipoNamestring, el métodoSelectpuede utilizarse para seleccionar los nombres de una lista de clientes:List<Customer> customers = GetCustomerList(); IEnumerable<string> names = customers.Select(c => c.Name);La invocación al método de extensión (sección 12.8.10.3) de
Selectse procesa reescribiendo la invocación a una invocación a un método estático:IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);Dado que los argumentos de tipo no se especificaron explícitamente, se utiliza la inferencia de tipo para inferir los argumentos de tipo. En primer lugar, el argumento de los clientes está relacionado con el parámetro de origen, infiriendo que
TSourceesCustomer. A continuación, mediante el proceso de inferencia de tipos de función anónima descrito anteriormente, acse le asigna el tipoCustomer, y la expresiónc.Namese relaciona con el tipo de valor de retorno del parámetro selector, infiriendo queTResultesstring. Así, la invocación es equivalente aSequence.Select<Customer,string>(customers, (Customer c) => c.Name)y el resultado es de tipo
IEnumerable<string>.El siguiente ejemplo demuestra cómo la inferencia de tipo de función anónima permite que la información de tipo "fluya" entre los argumentos en una invocación de método genérico. Dado el siguiente método e invocación:
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 procede de la siguiente manera: En primer lugar, el argumento "1:15:30" se relaciona con el parámetro de valor, infiriéndose que
Xesstring. A continuación, al parámetro de la primera función anónima,s, se le asigna el tipo inferidostring, y la expresiónTimeSpan.Parse(s)está relacionada con el tipo de retorno def1, infiriendo queYesSystem.TimeSpan. Por último, al parámetro de la segunda función anónima,t, se le asigna el tipo inferidoSystem.TimeSpan, y la expresiónt.TotalHoursestá relacionada con el tipo de retorno def2, inferidoZcomodouble. Así, el resultado de la invocación es del tipodouble.ejemplo final
12.6.3.15 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 tipo también se aplicará cuando un grupo de métodos M que contenga un método genérico se convierta a un tipo delegado determinado D (sección 10.8). Dado que un método
Tₑ M<X₁...Xᵥ>(T₁ x₁ ... Tₑ xₑ)
y al asignarse el grupo de métodos M al tipo de delegado D, la tarea de inferencia de tipo es buscar argumentos de tipo S₁...Sᵥ para que la expresión:
M<S₁...Sᵥ>
se vuelve compatible (§21.2) con D.
A diferencia del algoritmo de inferencia de tipos para las llamadas a métodos genéricos, en este caso, solo existen tipos de argumentos , sin que haya expresiones de argumentos . En particular, no hay funciones anónimas y por lo tanto no hay necesidad de múltiples fases de inferencia.
En su lugar, todos los Xᵢ se consideran sin fijar y se realiza una inferencia de límite inferiorde cada tipo de argumento Uₑ de Dal tipo de parámetro correspondiente Tₑ de M. Si para cualquiera de las Xᵢ no se encontraron límites, la inferencia de tipo falla. De lo contrario, todos los Xᵢ se fijan a las Sᵢ correspondientes, que son el resultado de la inferencia de tipos.
12.6.3.16 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 particular, los tipos de elementos de matrices implícitamente introducidas y los tipos de retorno 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 del siguiente modo:
- Se introduce una nueva variable de tipo no fijado
X. - Para cada expresión
Ei, se realiza una inferencia de tipo de salida (§12.6.3.8) desde ella aX. -
Xes fijo (§12.6.3.13), si es posible, y el tipo resultante es el mejor tipo común. - En caso contrario, la inferencia falla.
Nota: Intuitivamente esta inferencia es equivalente a llamar a un método
void M<X>(X x₁ ... X xᵥ)con losEᵢcomo argumentos e inferirX. 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 dados una lista de argumentos y un conjunto de miembros de función candidatos. La resolución de sobrecarga selecciona el miembro de función a invocar en los siguientes contextos distintos dentro de C#:
- Invocación de un método nombrado en una invocation_expression (§12.8.10).
- Invocación de un constructor de instancia con nombre en una object_creation_expression (§12.8.17.2).
- Invocación de un descriptor de acceso de indexador mediante 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 (sección 12.4.4 y sección 12.4.5).
Cada uno de estos contextos define el conjunto de miembros candidatos de la función y la lista de argumentos de forma única. Por ejemplo, el conjunto de candidatos para la invocación de un método no incluye los métodos marcados como override (sección 12.5), y los métodos de una clase base no son candidatos si cualquier método de una clase derivada es aplicable (sección 12.8.10.2).
Una vez identificados los miembros de función candidatos 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 aquellos miembros de función que son aplicables con respecto a la lista de argumentos dada (§12.6.4.2). Si este conjunto reducido está vacío, se produce un error de compilación.
- A continuación, se localiza el mejor miembro de función del conjunto de miembros de función candidatos aplicables. Si el conjunto solo contiene un miembro, este es el mejor. En caso contrario, el mejor miembro de la función es el miembro de la función que es mejor que todos los demás miembros de la función con respecto a la lista de argumentos dada, siempre que cada miembro de la función se compare con todos los demás miembros de la función utilizando las reglas de sección 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 al miembro de función es ambigua y se produce un error de vinculación.
Las siguientes subcláusulas definen los significados exactos de los términos miembro de función aplicable y miembro de función mejor .
12.6.4.2 Miembro funcional aplicable
Se dice que un miembro de función es un miembro de función aplicable con respecto a una lista de argumentos A cuando se cumplen todas las condiciones siguientes:
- Cada argumento en
Acorresponde a un parámetro en la declaración de miembro de función como se describe en la sección 12.6.2.2, como máximo un argumento corresponde a cada parámetro, y cualquier parámetro al que no corresponde ningún argumento es un parámetro opcional. - Para cada argumento en
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 (sección 10.2) de la expresión del argumento al tipo del parámetro correspondiente, o bien
- para un parámetro de referencia o de salida, existe una conversión de identidad entre el tipo de la expresión del argumento (si existe) y el tipo del parámetro correspondiente, o
- para un parámetro de entrada cuando el argumento correspondiente tiene el modificador
in, hay una conversión de identidad entre el tipo de la expresión del argumento (si existe) y el tipo del parámetro correspondiente, o - para un parámetro de entrada cuando el argumento correspondiente omite el modificador
in, existe una conversión implícita (sección 10.2) entre la expresión del argumento y el tipo del parámetro correspondiente.
Para un miembro de función que incluye una matriz de parámetros, si el miembro de función es aplicable según 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 puede ser aplicable en su forma expandida:
- La forma expandida se construye sustituyendo la matriz de parámetros en la declaración del miembro de la función por cero o más parámetros de valor del tipo de elemento de la matriz de parámetros de forma que el número de argumentos de la lista de argumentos
Acoincida con el número total de parámetros. SiAtiene menos argumentos que el número de parámetros fijos en la declaración del miembro funcional, la forma expandida del miembro funcional no puede construirse y, por tanto, no es aplicable. - En caso contrario, la forma expandida es aplicable si para cada argumento en
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 al tipo del parámetro correspondiente; o
- para un parámetro por referencia, el tipo de la expresión del argumento es idéntico al tipo del parámetro correspondiente.
- El modo de paso de parámetros del argumento es por valor, y el modo de paso de parámetros del parámetro correspondiente es de entrada, y existe una conversión implícita (§10.2) desde la expresión de argumento al tipo del parámetro correspondiente.
- El modo de paso de parámetros del argumento es idéntico al modo de paso de parámetros del parámetro correspondiente y:
Cuando la conversión implícita del tipo del argumento al tipo del parámetro de un parámetro de entrada es una conversión implícita dinámica (sección 10.2.10), los resultados son indefinidos.
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 resulta de un simple_name o de un member_access a través de un tipo.
- Un método de instancia solo es aplicable si el grupo de métodos resulta de un simple_name, un member_access a través de una variable o 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 se permite el acceso
this§12.8.14.
- Si el grupo de métodos resulta de un simple_name, un método de instancia solo es aplicable si se permite el acceso
- Cuando el grupo de métodos resulta de un member_access que puede ser a través de una instancia o de un tipo, tal como se describe en la sección 12.8.7.2, son aplicables tanto los métodos de instancia como los estáticos.
- No es aplicable un método genérico cuyos argumentos de tipo (especificados explícitamente o inferidos) no satisfagan todos sus restricciones.
- En el contexto de una conversión de grupo de métodos, deberá existir una conversión de identidad (sección 10.2.2) o una conversión de referencia implícita (sección 10.2.8) del tipo de retorno del método al tipo de retorno del delegado. En caso contrario, el método candidato no es aplicable.
12.6.4.3 Mejor miembro de función
Para determinar el mejor miembro de la función, se construye una lista simplificada de argumentos A que contiene solo las expresiones de argumento en sí mismas en el orden en que aparecen en la lista de argumentos original, dejando fuera los argumentos out o ref.
Las listas de parámetros para cada uno de los miembros de la función candidata se construyen de la siguiente manera:
- La forma expandida se utiliza si el miembro de función solo era aplicable en la forma expandida.
- Los parámetros opcionales sin argumentos correspondientes se eliminan de la lista de parámetros
- Los parámetros de referencia y de salida se eliminan de la lista de parámetros.
- Los parámetros se reordenan para que aparezcan en la misma posición que el argumento correspondiente en la lista de argumentos.
Dada una lista de argumentos A con un conjunto de expresiones de argumentos {E₁, E₂, ..., Eᵥ} y dos miembros de función aplicables Mᵥ y Mₓ con tipos de parámetros {P₁, P₂, ..., Pᵥ} y {Q₁, Q₂, ..., Qᵥ}, Mᵥ se define que es un miembro de función mejor que Mₓ si
- para cada argumento, la conversión implícita de
EᵥaQᵥno es mejor que la conversión implícita deEᵥaPᵥ, y - para al menos un argumento, la conversión de
EᵥaPᵥes mejor que la conversión deEᵥaQᵥ.
En caso de que las secuencias de tipo de parámetro {P₁, P₂, ..., Pᵥ} y {Q₁, Q₂, ..., Qᵥ} sean equivalentes (es decir, cada Pᵢ tiene una conversión de identidad a la Qᵢcorrespondiente), se aplican las siguientes reglas de desempate, para determinar el mejor miembro de función.
- Si
Mᵢes un método no genérico yMₑes un método genérico, entoncesMᵢes mejor queMₑ. - En caso contrario, si
Mᵢes aplicable en su forma normal yMₑtiene una matriz params y solo es aplicable en su forma expandida, entoncesMᵢes mejor queMₑ. - En caso contrario, si ambos métodos tienen matrices de parámetros y solo son aplicables en sus formas expandidas, y si la matriz de parámetros de
Mᵢtiene menos elementos que la matriz de parámetros deMₑ, entoncesMᵢes mejor queMₑ. - En caso contrario, si los tipos de los parámetros de
Mᵥson más específicos que los deMₓ, entoncesMᵥes mejor queMₓ. Deje que{R1, R2, ..., Rn}y{S1, S2, ..., Sn}representen los tipos de parámetros no instanciados y no expandidos deMᵥyMₓ. los tipos de parámetro deMᵥson más específicos queMₓsi, para cada parámetro,Rxno es menos específico queSx, y, para al menos un parámetro,Rxes más específico queSx:- 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 correspondiente argumento de tipo 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.
- En caso contrario, si un miembro es un operador no elevado y el otro es un operador elevado, el no elevado es mejor.
- Si ninguno de los miembros de la función resulta ser mejor y todos los parámetros de
Mᵥtienen un argumento correspondiente mientras que los argumentos por defecto deben sustituirse por al menos un parámetro opcional enMₓ, entoncesMᵥes mejor queMₓ. - Si al menos un parámetro
Mᵥutiliza la mejor opción de paso de parámetros (sección 12.6.4.4) que el parámetro correspondiente enMₓy ninguno de los parámetros enMₓutiliza la mejor opción de paso de parámetros queMᵥ,Mᵥes mejor queMₓ. - En caso contrario, ningún miembro de la función es mejor.
12.6.4.4 Mejor modo de paso de parámetros
Se permite que los parámetros correspondientes en dos métodos sobrecargados difieran solo por el modo de paso de parámetros siempre que uno de los dos parámetros tenga modo de paso de valor, como sigue:
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) hacen 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 de modo de paso de parámetros.
Nota: No existe tal necesidad de elección para los argumentos de los modos de paso de entrada, salida o referencia, ya que esos argumentos solo coinciden exactamente con los mismos modos de paso de parámetros. nota final
12.6.4.5 Mejor conversión desde expresión
Dada una conversión implícita C₁ que convierte de una expresión E a un tipo T₁, y una conversión implícita C₂ que convierte de una expresión E a un tipo T₂, C₁ es una conversión mejor que C₂ si se cumple una de las siguientes condiciones:
-
Ecoincide exactamente conT₁yEno coincide exactamente conT₂(sección 12.6.4.6) -
Ecoincide exactamente con ambos o ninguno deT₁yT₂, yT₁es un destino de conversión mejor queT₂(§12.6.4.7) -
Ees un grupo de métodos (§12.2),T₁es compatible (§21.4) con el único método del grupo de métodos para la conversiónC₁yT₂no es compatible con el único método del grupo de métodos para la conversión.C₂
12.6.4.6 Expresión exactamente coincidente
Dada una expresión E y un tipo T, Ecoincide exactamente conT si se cumple una de las siguientes condiciones:
-
Etiene un tipoSy existe una conversión de identidad deSaT -
Ees una función anónima,Tes un tipo delegadoDo un tipo de árbol de expresiónExpression<D>y se cumple una de las siguientes condiciones:- Existe un tipo
Xde valor devuelto inferido paraEen el contexto de la lista de parámetros deD(§12.6.3.13) y existe una conversión de identidad desdeXhasta el tipo de valor devuelto deD -
Ees una lambdaasyncsin valor de devolución, yDtiene un tipo de devolución que no es genérico«TaskType». - O
Ees no sincrónico yDtiene un tipo de valor devueltoYoEes asincrónico yDtiene un tipo de valor devuelto«TaskType»<Y>(§15.14.1) y se cumple una de las siguientes condiciones:- El cuerpo de
Ees una expresión que coincide exactamente conY - El cuerpo de
Ees un bloque donde cada sentencia de retorno devuelve una expresión que coincide exactamente conY
- El cuerpo de
- Existe un tipo
12.6.4.7 Mejor objetivo de conversión
Dados dos tipos T₁ y T₂, T₁ es un mejor destino de conversión que T₂ si se cumple una de las siguientes condiciones:
- Existe una conversión implícita de
T₁aT₂y no existe ninguna conversión implícita deT₂aT₁ -
T₁is«TaskType»<S₁>(§15.14.1),T₂es«TaskType»<S₂>, yS₁es un destino de conversión mejor queS₂ -
T₁es«TaskType»<S₁>(§15.14.1),T₂es«TaskType»<S₂>, yT₁es más especializado queT₂ -
T₁esS₁oS₁?dondeS₁es un tipo entero con signo yT₂esS₂oS₂?dondeS₂es un tipo entero sin signo. En concreto:-
S₁essbyteyS₂esbyte,ushort,uintoulong -
S₁esshortyS₂esushort,uintoulong -
S₁esintyS₂esuintoulong -
S₁eslongyS₂esulong
-
12.6.4.8 Sobrecarga en clases genéricas
Nota: Aunque las firmas declaradas deben ser únicas (sección 8.6), es posible que la sustitución de argumentos de tipo dé lugar a 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; de lo contrario, notificará un error. nota final
Ejemplo: Los siguientes ejemplos muestran sobrecargas que son válidas e invá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 la sobrecarga de una operación ligada dinámicamente tiene lugar en tiempo de ejecución, a veces es posible conocer en tiempo de compilación la lista de miembros de función entre 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 (sección 12.8.10.2) sobre un tipo, o sobre 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 objeto (sección 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.4), 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 de cada miembro del conjunto conocido de miembros de función, para ver si se puede saber con certeza que nunca será invocado en tiempo de ejecución. Para cada miembro de función F se construye una lista modificada de parámetros y argumentos:
- En primer lugar, si
Fes un método genérico y se han proporcionado argumentos de tipo, estos se sustituyen por los parámetros de tipo en la lista de parámetros. Sin embargo, si no se han proporcionado argumentos de tipo, no se produce tal 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 omite, junto con sus argumentos correspondientes.
Para que F pase la comprobación, se deberán cumplir todas las siguientes condiciones:
- La lista de parámetros modificada para
Fes aplicable a la lista de argumentos modificada en los términos de sección 12.6.4.2. - Todos los tipos construidos en la lista de parámetros modificada satisfacen sus restricciones (sección 8.4.5).
- Si los parámetros de tipo de
Fse sustituyeron en el paso anterior, se satisfacen sus restricciones. - Si
Fse trata de un método estático, el grupo de métodos no debe ser el resultado de un member_access cuyo receptor se sepa en tiempo de compilación que es una variable o un valor. - Si
Fes un método de instancia, el grupo de métodos no puede haberse originado de un member_access cuyo receptor se conoce en tiempo de compilación como un tipo.
Si ningún candidato supera esta prueba, se produce un error de compilación.
12.6.6 Invocación de miembro de función
12.6.6.1 General
Esta subcláusula describe el proceso que tiene lugar en tiempo de ejecución para invocar un determinado miembro de función. 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. Se trata de los métodos estáticos, los descriptores de acceso a propiedades estáticas y los operadores definidos por el usuario. Los miembros de función estáticos siempre son no virtuales.
- Miembros de la función de instancia. Estos son métodos de instancia, constructores de instancia, accesores de propiedades de instancia y accesores de 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 la función como
this(§12.8.14). Para un constructor de instancia, se toma la expresión de instancia como el objeto recientemente asignado.
El procesamiento en tiempo de ejecución de la invocación de un miembro de función consiste en los siguientes pasos, donde M es el miembro de función y, si M es un miembro de instancia, E es la expresión de instancia:
- Si
Mes un miembro de función estática:- La lista de argumentos se evalúa como se describe en la sección 12.6.2.
- Se invoca a
M.
- De lo contrario, si el tipo de es un tipo de
Evalor yVse declara o invalida enM:V-
Ese evalúa. Si esta evaluación causa una excepción, entonces no se ejecutan más pasos. Para un constructor de instancia, esta evaluación consiste en asignar almacenamiento (típicamente de una pila de ejecución) para el nuevo objeto. En este casoEse clasifica como variable. - Si
Eno se clasifica como una variable, o siVno es un tipo de estructura de solo lectura (§16.2.2) yMno es un miembro de función de solo lectura (§16.4.12) yEes uno de:- un parámetro de entrada (sección 15.6.2.3.2) o
- un campo
readonly(§15.5.3), o - se crea una
readonlyvariable de referencia o devuelve (§9.7), se crea una variable local temporal delEtipo y el valor deEse asigna a esa variable.Ese reclasifica como una referencia a esa variable local temporal. La variable temporal es accesible comothisdentro deM, pero no de ninguna otra forma. Así, solo cuando se puede escribirEes posible para el invocador observar los cambios que haceMathis.
- La lista de argumentos se evalúa como se describe en la sección 12.6.2.
- Se invoca a
M. La variable referenciada porEse convierte en la variable referenciada porthis.
-
- De lo contrario:
-
Ese evalúa. Si esta evaluación causa una excepción, entonces no se ejecutan más pasos. - La lista de argumentos se evalúa como se describe en la sección 12.6.2.
- Si el tipo de
Ees un value_type, se realiza una conversión boxing (§10.2.9) para convertirEen un class_type yEse considera que es de ese class_type en los siguientes pasos. Si el value_type es un enum_type, el class_type esSystem.Enum;; de lo contrario, esSystem.ValueType. - Se comprueba si el valor de
Ees válido. Si el valor deEes NULL, se lanza unSystem.NullReferenceExceptiony 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
Ees una interfaz, el miembro de función que se va a invocar es la implementación deMproporcionada por el tipo de tiempo de ejecución de la instancia a la que hace referenciaE. Este miembro de función se determina aplicando las reglas de asignación de interfaz (§19.6.5) para determinar la implementación deMproporcionada por el tipo en tiempo de ejecución de la instancia a la que haceEreferencia . - En caso contrario, si
Mes un miembro de función virtual, el miembro de función a invocar es la implementación deMproporcionada por el tipo en tiempo de ejecución de la instancia referenciada porE. Este miembro de función se determina aplicando las reglas para determinar la implementación más derivada (sección 15.6.4) deMcon respecto al tipo de tiempo de ejecución de la instancia referenciada porE. - En caso contrario,
Mes un miembro de función no virtual, y el miembro de función a invocar es el mismoM.
- Si el tipo de tiempo de enlace de
- Se invoca la implementación de un miembro de función determinada en el paso anterior. El objeto al que hace referencia
Ese convierte en el objeto al que hace referencia este.
-
Nota:§12.2 clasifica el acceso a la propiedad como invocando el miembro de función correspondiente, ya sea el
getdescriptor de acceso o el descriptor desetacceso. Se sigue el proceso anterior para invocar ese descriptor de acceso. nota final
El resultado de la invocación de un constructor de instancia (sección 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) de su cuerpo.
12.6.6.2 Invocaciones en instancias encapsuladas
Un miembro de función implementado en un value_type se puede invocar a través de una instancia encapsulada 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.ValueTypeoSystem.Enum. nota 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 una función se invoca a través de un delegado.
En estas situaciones, la instancia encapsulada se considera que contiene una variable del value_type y esta variable se convierte en la variable referenciada por esta dentro de la invocación al miembro de función.
Nota: en particular, esto significa que cuando se invoca un miembro de función sobre una instancia en caja, es posible que el miembro de función modifique el valor contenido en la instancia en caja. nota final
12.7 Deconstrucción
La deconstrucción es un proceso mediante el cual una expresión se convierte en una tupla de expresiones individuales. La deconstrucción se utiliza cuando el objetivo de una asignación simple es una expresión de tupla, con el fin de obtener valores para asignar a cada uno de los elementos de esa tupla.
Una expresión E se deconstruye en una expresión de tupla con elementos n de la siguiente manera:
- Si
Ees una expresión de tupla con elementosn, el resultado de la deconstrucción es la expresiónE. - En caso contrario, si
Etiene un tipo de tupla(T1, ..., Tn)con elementosn, entonces se evalúaEen una variable temporal__v, 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 a 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. - En caso contrario,
Eno se puede deconstruir.
Aquí, __v y __v1, ..., __vn se refieren a variables temporales que de otro modo serían invisibles e inaccesibles.
Nota: No se puede deconstruir una expresión de tipo
dynamic. nota final
12.8. Expresiones primarias
12.8.1 General
Las expresiones primarias incluyen las formas más simples de expresiones.
primary_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
| array_creation_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: Esta regla gramatical no está lista para ANTLR, ya que forma parte de un conjunto de reglas mutuamente recursivas a la izquierda (
primary_expression,member_access,invocation_expression,element_accesspost_increment_expression,post_decrement_expression, ynull_forgiving_expressionpointer_member_accesspointer_element_access) que ANTLR no controla. Las técnicas estándar se pueden usar para transformar la gramática para eliminar la recursividad mutua a la izquierda. Este estándar no lo hace, ya que no todas las estrategias de análisis lo requieren (por ejemplo, un analizador LALR no lo haría) y hacerlo ofuscaría la estructura y la descripción. nota final
pointer_member_access (§24.6.3) y pointer_element_access (§24.6.4) solo están disponibles en código no seguro (§24).
12.8.2 Literales
Una primary_expression que consta de un literal (§6.4.5) se clasifica como un valor.
12.8.3 Expresiones de cadena interpoladas
Una interpolated_string_expression consiste en $, $@o @$, seguida inmediatamente por texto dentro de los caracteres ". Dentro del texto entrecomillado hay cero o más interpolaciones , delimitadas por los caracteres { y }, cada una de las cuales incluye una expresión junto con especificaciones de formato opcionales.
Las expresiones de cadena interpoladas tienen dos formas; regular (interpolated_regular_string_expression) y verbatim (interpolated_verbatim_string_expression); que son léxicamente similares a, pero difieren 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 son sensibles al contexto, como se indica a continuación:
| 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 una 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_expressions contenidos en dichas interpolaciones. |
| Interpolated_Verbatim_String_MidVerbatim_Interpolation_FormatInterpolated_Verbatim_String_End | El reconocimiento de estas tres reglas sigue el de las reglas correspondientes de arriba, donde cada regla de gramática normal mencionada se reemplaza por la regla textual correspondiente. |
Nota: Las reglas anteriores son sensibles al contexto, ya que sus definiciones se superponen con las de otros tokens del lenguaje. nota final
Nota: la gramática anterior no está preparada para ANTLR debido a las reglas léxicas sensibles al contexto. Al igual que otros generadores de lexer, ANTLR soporta reglas léxicas sensibles al contexto, por ejemplo usando sus modos léxicos, pero esto es un detalle de implementación y por lo tanto no forma parte de esta especificación. nota final
Una interpolated_string_expression se clasifica como un valor. Si se convierte inmediatamente a System.IFormattable o System.FormattableString con una conversión implícita de cadena interpolada (sección 10.2.5), la expresión de cadena interpolada tiene ese tipo. En caso contrario, tiene el tipo string.
Nota: las diferencias entre los tipos posibles de interpolated_string_expression se pueden determinar a partir de la documentación de
System.String(§C.2) ySystem.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 string 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 formateada para generar el string final que se va a interpolar en la interpolated_string_expression.
Nota: Cómo se detalla la determinación del formato predeterminado de un tipo se explica en la documentación de
System.String(§C.2) ySystem.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 deSystem.IFormattable(§C.4) y en otros tipos de la biblioteca estándar (§C). nota final
En un interpolation_minimum_width, la constant_expression deberá tener una conversión implícita a int. Deje que el ancho del campo sea el valor absoluto de esta expresión constante y que la alineación sea el signo (positivo o negativo) del valor de esta expresión constante:
- Si el valor de la anchura de campo es menor o igual que la longitud de la cadena formateada, esta no se modifica.
- En caso contrario, la cadena formateada se rellena con espacios en blanco para que su longitud sea igual a la anchura del campo:
- Si la alineación es positiva, la cadena con formato está alineada a la derecha con el relleno,
- De lo contrario, se alinea a la izquierda añadiendo el relleno.
El significado global de una interpolated_string_expression, incluidos el formateo y el relleno de interpolaciones anteriores, 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 (sección C.3) que devuelve un valor de tipo System.FormattableString; en caso contrario, el tipo será string y el método es string.Format (sección 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 siguiente manera, donde N es el número de interpolaciones en la interpolated_string_expression. La cadena literal de formato consta de lo siguiente, en orden:
- Los caracteres de Interpolated_Regular_String_Start o Interpolated_Verbatim_String_Start
- Los caracteres de Interpolated_Regular_String_Mid o Interpolated_Verbatim_String_Mid, si los hay
- A continuación, si
N ≥ 1para cada númeroIde0aN-1:- Especificación de marcador de posición:
- Un carácter de llave izquierda (
{) - La representación decimal de
I - A continuación, si la correspondiente regular_interpolation o verbatim_interpolation tiene un interpolation_minimum_width, una coma (
,) va seguida de la representación decimal del valor de constant_expression - Los caracteres del Regular_Interpolation_Format o Verbatim_Interpolation_Format, si existen, de la correspondiente regular_interpolation o verbatim_interpolation
- Un carácter de llave derecha (
})
- Un carácter de llave izquierda (
- Los caracteres del Interpolated_Regular_String_Mid o Interpolated_Verbatim_String_Mid inmediatamente después de la interpolación correspondiente, si la hay
- Especificación de marcador de posición:
- Por último, los caracteres de Interpolated_Regular_String_End o Interpolated_Verbatim_String_End.
Los argumentos siguientes son las expresiones de las interpolaciones, en orden, si las hay.
Cuando una interpolated_string_expression contiene varias interpolaciones, las expresiones de dichas interpolaciones se evalúan en orden textual de izquierda a derecha.
Ejemplo:
Este ejemplo utiliza las siguientes características de especificación de formato:
- la especificación de formato
Xque da formato a enteros como hexadecimal en mayúsculas, - el formato por defecto de un valor
stringes el valor mismo, - valores de alineación positivos que se 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,
- Se han definido constantes para el interpolation_minimum_width, y
- que
{{y}}se formatean como{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 consiste en un identificador, opcionalmente seguido de una lista de argumentos de tipo:
simple_name
: identifier type_argument_list?
;
Un simple_name es de la forma I o de la forma I<A₁, ..., Aₑ>, donde I es un identificador único y I<A₁, ..., Aₑ> es un type_argument_listopcional. Si no se especifica ningún type_argument_list, considera que e es cero. El simple_name se evalúa y clasifica del siguiente modo:
- Si
ees cero y el simple_name aparece dentro de un espacio de declaración de variable local (sección 7.3) que contiene directamente una variable, parámetro o constante local con nombreI, entonces el simple_name se refiere a esa variable, parámetro o constante local y se clasifica como variable o valor. - Si
ees 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 nombreI, entonces el simple_name se refiere a ese parámetro de tipo. - En caso contrario, para cada tipo de instancia
T(§15.3.2), empezando por el tipo de instancia de la declaración de tipo inmediatamente contigua y continuando con el tipo de instancia de cada declaración de clase o estructura contigua (si existe):- Si
ees cero y la declaración deTincluye un parámetro de tipo con nombreI, entonces el simple_name se refiere a ese parámetro de tipo. - De lo contrario, si una búsqueda de miembros (§12.5) de
IenTcon argumentos de tipoeda como resultado una coincidencia:- Si
Tes el tipo de instancia de la clase o tipo struct que lo encierra inmediatamente y la búsqueda identifica uno o más métodos, el resultado es un grupo de métodos con una expresión de instancia asociada dethis. Si se ha especificado una lista de argumentos de tipo, se utiliza para llamar a un método genérico (sección 12.8.10.2). - De lo contrario, si
Tes el tipo de instancia de la clase o el tipo de estructura inmediatamente envolvente, si la búsqueda identifica un miembro de instancia y si 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) de la formathis.I. Esto solo puede ocurrir cuandoees cero. - De lo contrario, el resultado es el mismo que un acceso de miembro (§12.8.7) de la forma
T.IoT.I<A₁, ..., Aₑ>.
- Si
- Si
- De lo contrario, para cada espacio de nombres
N, comenzando por el espacio de nombres en el que aparece el simple_name, continuando con cada espacio de nombres adyacente (si lo hay) y terminando con el espacio de nombres global, se evalúan los siguientes pasos hasta que se localiza una entidad:- Si
ees cero yIes el nombre de un espacio de nombres enN, entonces:- Si la ubicación en la que se produce el simple_name está incluida dentro de una declaración de espacio de nombres para
Ny la declaración de espacio de nombres contiene una extern_alias_directive o una using_alias_directive que asocia el nombreIcon un espacio de nombres o un tipo, entonces el simple_name es ambiguo y se produce un error en tiempo de compilación. - En caso contrario, el simple_name hace referencia al espacio de nombres nombrado
IenN.
- Si la ubicación en la que se produce el simple_name está incluida dentro de una declaración de espacio de nombres para
- De lo contrario, si
Ncontiene un tipo accesible cuyo nombre esIy tieneeparámetros de tipo, entonces:- Si
ees cero y la ubicación donde aparece simple_name está encerrada por una declaración de espacio de nombres paraNy la declaración de espacio de nombres contiene una extern_alias_directive o using_alias_directive que asocia el nombreIcon un espacio de nombres o tipo, entonces simple_name es ambiguo y se produce un error de compilación. - En caso contrario, el namespace_or_type_name hace referencia al tipo construido con los argumentos de tipo dados.
- Si
- De lo contrario, si la ubicación donde ocurre el simple_name está incluida en una declaración de espacio de nombres para
N:- Si
ees cero y la declaración del espacio de nombres contiene una directiva extern_alias_directive o using_alias_directive que asocia el nombreIcon un espacio de nombres o tipo importado, entonces el simple_name se refiere a ese espacio de nombres o tipo. - De lo contrario, si los espacios de nombres importados por las using_namespace_directives de la declaración de espacio de nombres contienen exactamente un tipo que tiene parámetros de nombre
Iy tipoe, 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 las using_namespace_directives de la declaración de espacio de nombres contienen más de un tipo con los parámetros de nombre
Iy tipoe, el simple_name es ambiguo y se produce un error en tiempo de compilación.
- Si
Nota: Todo este paso es exactamente paralelo al paso correspondiente en el procesamiento de un namespace_or_type_name (sección 7.8). nota final
- Si
- De lo contrario, si
ees cero yIes el identificador_, el simple_name es un descarte sencillo, que es una forma de expresión de declaración (§12.19). - De lo contrario, el simple_name es indefinido y se produce un error de compilación.
12.8.5 Expresiones entre paréntesis
Una parenthesized_expression consta de una expresión entre paréntesis.
parenthesized_expression
: '(' expression ')'
;
Una parenthesized_expression se evalúa evaluando la expresión dentro de los paréntesis. Si la expresión entre paréntesis denota un espacio de nombres o un tipo, se produce un error 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
Una tuple_expression representa una tupla y consta de dos o más expressions separadas por comas y, opcionalmente, incluidas entre paréntesis. Una deconstruction_expression es una sintaxis abreviada para una tupla que contiene expresiones de declaración con tipos implícitos.
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
;
Una tuple_expression se clasifica como una tupla.
Una deconstruction_expressionvar (e1, ..., en) es una abreviatura de la tuple_expression(var e1, ..., var en) y sigue el mismo comportamiento. Esto se aplica de forma recursiva a cualquier deconstruction_tuples que esté anidada en la deconstruction_expression. Cada identificador anidado dentro de un deconstruction_expression introduce así una expresión de declaración (§12.19). Como resultado, una deconstruction_expression solo puede ocurrir en el lado izquierdo de una asignación simple.
Ejemplo: El código siguiente declara tres variables: a, b y c. Cada uno de los cuales es un entero y se le asigna su valor de la tupla ubicada a la derecha de la asignación.
var (a, b, c) = (1, 2, 3); // a is 1, b is 2, and c is 3. var sum = a + b + c; // sum is 6.Cualquiera de los elementos individuales de una tarea puede ser una expresión de deconstrucción. Por ejemplo, la siguiente expresión de deconstrucción asigna seis variables, de
ahastaf.var (a, b, (c, d, (e, f))) = (1, 2, (3, 4, (5, 6)));En este ejemplo, observe que la estructura de las tuplas anidadas debe coincidir en ambos lados de la asignación.
Si las variables del lado izquierdo se escriben implícitamente, la expresión correspondiente debe tener un tipo:
(int a, string? b) = (42, null); //OK var (c, d) = (42, null); // Invalid as type of d cannot be inferred (int e, var f) = (42, null); // Invalid as type of f cannot be inferredejemplo final
Una expresión de tupla tiene un tipo solo si cada una de sus expresiones de elemento Ei tiene un tipo Ti. El tipo será un tipo de tupla de la misma aridad que la expresión de tupla, donde cada elemento se define por lo siguiente:
- Si el elemento de la tupla en la posición correspondiente se llama
Ni, entonces el elemento de tipo de tupla seráTi Ni. - De lo contrario, si
Eitiene el formatoNioE.NioE?.Ni, el elemento de tipo de tupla seráTi Ni, a menos que se cumpla alguna de las siguientes condiciones:- Otro elemento de la expresión de tupla tiene el nombre
Ni, o - Otro elemento de la tupla sin nombre tiene una expresión de elemento de tupla de la forma
NioE.NioE?.Nio -
Nies del formatoItemX, dondeXes una secuencia de dígitos decimales que no se inicia con0y que podría representar la posición de un elemento de tupla, mientras queXno representa la posición del elemento.
- Otro elemento de la expresión de tupla tiene el nombre
- De lo contrario, el elemento de tipo de tupla deberá ser
Ti.
Una expresión de tupla se evalúa mediante el análisis de 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 haciendo que sea el destino de una asignación deconstrucción (§12.23.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 typeEn este ejemplo, las cuatro expresiones de tuplas son válidas. Los dos primeras,
t1yt2, no usan el tipo de la expresión de tupla, sino que, en su lugar, aplican una conversión de tupla implícita. En el caso det2, la conversión de tupla implícita se basa en las conversiones implícitas de2alongy denullastring. La tercera expresión de tupla tiene un tipo(int i, string), y por lo tanto puede ser reclasificada como un valor de ese tipo. La declaración det4, por otro lado, es un error: la expresión de tupla no tiene tipo porque su segundo elemento carece de 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
12.8.7 Acceso a miembros
12.8.7.1 General
Un member_access consiste en una primary_expression, un predefined_type o un qualified_alias_member, seguido de un token ".", seguido de un identificador y opcionalmente seguido de una 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 producción de qualified_alias_member se define en §14.8.
Un member_access es de la forma E.I o de la forma E.I<A₁, ..., Aₑ>, donde E es una primary_expression, predefined_type o qualified_alias_member,I es un identificador único y <A₁, ..., Aₑ> es una type_argument_list opcional. Si no se especifica ningún type_argument_list, considera que e es cero.
Un member_access con una primary_expression del tipo dynamic se vincula dinámicamente (§12.3.3). En este caso, el acceso de miembro se clasifica 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 de la primary_expression. Si esta clasificación en tiempo de ejecución lleva a un grupo de métodos, entonces el acceso del miembro deberá ser la primary_expression de una invocation_expression.
El member_access se evalúa y clasifica de la siguiente manera:
- Si
ees cero yEes un espacio de nombres yEcontiene un espacio de nombres anidado con nombreI, entonces el resultado es ese espacio de nombres. - En caso contrario, si
Ees un espacio de nombres yEcontiene un tipo accesible con nombreIy parámetros de tipoK, entonces el resultado es ese tipo construido con los argumentos de tipo dados. - Si
Ese clasifica como un tipoE, si no es un parámetro de tipo, y si una búsqueda de miembros (sección 12.5) deIenEcon parámetros de tipoKproduce una coincidencia, entoncesE.Ise evalúa y clasifica como sigue:Nota: Cuando el resultado de dicha búsqueda de miembros es un grupo de métodos y
Kes cero, el grupo de métodos puede contener métodos con parámetros de tipo. Esto permite que tales métodos sean considerados para la inferencia de argumentos de tipo. nota final- Si
Iidentifica un tipo, entonces el resultado es ese tipo construido con cualquier argumento de tipo dado. - Si
Iidentifica uno o más métodos, el resultado es un grupo de métodos sin expresión de instancia asociada. - Si
Iidentifica una propiedad estática, el resultado es un acceso a una propiedad sin expresión de instancia asociada. - Si
Iidentifica 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 estático
IenE. - En caso contrario, el resultado es una variable, es decir, el campo estático
IenE.
- 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 estático
- Si
Iidentifica un evento estático:- Si la referencia ocurre dentro de la clase o estructura en la que se declara el evento, y el evento se declaró sin event_accessor_declarations (sección 15.8.1), entonces
E.Ise procesa exactamente como siIfuera un campo estático. - En caso contrario, el resultado es un acceso a un evento sin expresión de instancia asociada.
- Si la referencia ocurre dentro de la clase o estructura en la que se declara el evento, y el evento se declaró sin event_accessor_declarations (sección 15.8.1), entonces
- Si
Iidentifica una constante, el resultado es un valor, es decir, el valor de dicha constante. - Si
Iidentifica un miembro de enumeración, el resultado es un valor, es decir, el valor de ese miembro de enumeración. - En caso contrario,
E.Ies una referencia de miembro no válida y se produce un error de compilación.
- Si
- Si
Ees un acceso de propiedad, acceso de indexador, variable o valor, cuyo tipo esT, y una búsqueda de miembros (§12.5) deIenTcon argumentos de tipoKproduce una coincidencia,E.Ise evalúa y clasifica de la siguiente manera:- En primer lugar, si
Ees una propiedad o un acceso a un indexador, entonces se obtiene el valor de la propiedad o del acceso al indexador (sección 12.2.2) y E se reclasifica como un valor. - Si
Iidentifica uno o más métodos, el resultado es un grupo de métodos con una expresión de instancia asociada deE. - Si
Iidentifica una propiedad de instancia, el resultado es un acceso a una propiedad con una expresión de instancia asociada deEy un tipo asociado que es el tipo de la propiedad. SiTes un tipo de clase, el tipo asociado se elige de la primera declaración o invalidación de la propiedad encontrada al comenzar conTy buscar en sus clases base. - Si
Tes un class_type yIidentifica un campo de instancia de ese class_type:- Si el valor de
Eesnull, se produce unSystem.NullReferenceException. - En caso 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 está declarado el campo, el resultado es un valor, es decir, el valor del campo
Ien el objeto referenciado porE. - En caso contrario, el resultado es una variable, es decir, el campo
Idel objeto referenciado porE.
- Si el valor de
- Si
Tes un struct_type e identificaIun campo de instancia de ese struct_type:- Si
Ees 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, entonces el resultado es un valor, es decir, el valor del campoIen la instancia del struct dada porE. - En caso contrario, el resultado es una variable, es decir, el campo
Ide la instancia struct dado porE.
- Si
- Si
Iidentifica un evento de instancia:- Si la referencia ocurre dentro de la clase o estructura en la que se declara el evento, y el evento fue declarado sin event_accessor_declarations (§15.8.1), y la referencia no ocurre como el lado izquierdo del operador
a +=o-=, entoncesE.Ise procesa exactamente como siIfuera un campo de instancia. - De lo contrario, el resultado es un acceso a eventos con una expresión de instancia asociada de
E.
- Si la referencia ocurre dentro de la clase o estructura en la que se declara el evento, y el evento fue declarado sin event_accessor_declarations (§15.8.1), y la referencia no ocurre como el lado izquierdo del operador
- En primer lugar, si
- En caso contrario, se intenta procesar
E.Icomo una invocación a un método de extensión (sección 12.8.10.3). Si esto falla,E.Ies una referencia de miembro no válida y se produce un error de 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), a continuación, se permiten ambos significados posibles de E. La búsqueda de miembros de E.I nunca es ambigua, ya que I será necesariamente un 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 E donde de otro modo se habría producido un error en tiempo de 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 expositivos, dentro de la clase
A, esas apariciones del identificadorColorque hacen referencia al tipoColorestán delimitadas por«...», y las que hacen referencia al campoColorno están delimitadas.ejemplo final
12.8.8 Acceso a miembro condicional nulo
Un null_conditional_member_access es una versión condicional de member_access (§12.8.7) y es un error en tiempo de vinculación si el tipo de resultado es void. Para una expresión condicional nula en la que el tipo de resultado puede ser void consulte (sección 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 ir precedido 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 null_conditional_member_accessE tiene el formato P?.A. El significado de E se determina como sigue:
Si el tipo de
Pes un tipo de valor con nulidad:Sea
Tel tipo deP.Value.A.Si
Tes un parámetro de tipo que no se sabe si es un tipo de referencia o un tipo de valor no anulable, se produce un error de compilación.Si
Tes un tipo de valor no anulable, entonces el tipo deEesT?, y el significado deEes el mismo que el significado de:((object)P == null) ? (T?)null : P.Value.AExcepto que
Psolo se evalúa una vez.De lo contrario, el tipo de
EesT, y el significado deEes el mismo que el significado de:((object)P == null) ? (T)null : P.Value.AExcepto que
Psolo se evalúa una vez.
De lo contrario:
Sea
Tel tipo de la expresiónP.A.Si
Tes un parámetro de tipo que no se sabe si es un tipo de referencia o un tipo de valor no anulable, se produce un error de compilación.Si
Tes un tipo de valor no anulable, entonces el tipo deEesT?, y el significado deEes el mismo que el significado de:((object)P == null) ? (T?)null : P.AExcepto que
Psolo se evalúa una vez.De lo contrario, el tipo de
EesT, y el significado deEes el mismo que el significado de:((object)P == null) ? (T)null : P.AExcepto que
Psolo se evalúa una vez.
Nota: en una expresión de la forma:
P?.A₀?.A₁después, si
Pse evalúa comonullniA₀niA₁se evalúan. Lo mismo sucede si una expresión es una secuencia de operaciones null_conditional_member_access o null_conditional_element_access§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.3).
12.8.9 Expresiones que admiten valores NULL
12.8.9.1 Generalidades
El valor, tipo, clasificación de una expresión que admite valores NULL (§12.2) y el contexto seguro (§16.4.15) 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 prefijo y de postfijo que admiten valores NULL (§12.9.4), aunque se representan mediante el mismo token léxico (!), son distintos. Solo se puede sobrecargar este último (§15.10), se fija la definición del operador que admite valores NULL.
nota final
Es un error en tiempo de compilación aplicar el operador null-forgiving más de una vez a la misma expresión, independientemente de los paréntesis que se interpongan.
Ejemplo: todo 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 subcláusula y las siguientes subcláusulas hermanas son condicionalmente normativas.
Un compilador que realice un análisis estático del estado nulo (sección 8.9.5) debe ajustarse a la siguiente especificación.
El operador que admite valores NULL es una pseudooperación en tiempo de compilación que se usa para notificar al análisis de estado nulo estático del compilador. Tiene dos usos: anular la determinación de un compilador de que una expresión tal vez NULL; y anular la emisión por parte de un compilador de una advertencia relacionada con la anulabilidad.
Aplicar el operador null-forgiving a una expresión para la cual el análisis estático de estado nulo de un compilador no produce ninguna advertencia no es un error.
12.8.9.2 Anulación de una determinación "tal vez NULL"
En algunas circunstancias, el análisis estático de estado NULL de un compilador puede determinar que una expresión tiene el estado nulo 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 de estado null estático del compilador que el estado null está en y no en null, lo cual impide la advertencia de diagnóstico y puede informar a cualquier análisis en curso.
Ejemplo: Considere 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
IsValiddevuelvetrue,ppuede ser dereferenciada con seguridad para acceder a su propiedadName, y la advertencia de "dereferenciación de un valor posiblemente NULL" puede ser suprimida usando!.ejemplo final
Ejemplo: El operador null-forgiving debe utilizarse con precaución, considere:
#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 anula cualquier advertencia sobre
x. Sin embargo, sixesnullen tiempo de ejecución, se producirá una excepción ya quenullno se puede convertir aint.ejemplo final
12.8.9.3 Invalidación de otras advertencias de análisis nulo
Además de invalidar las determinaciones de puede ser nulo como se indicó anteriormente, puede haber otras circunstancias en las que se desee invalidar la determinación del análisis de estado de nulidad estático de un compilador, para las cuales una expresión podría requerir una o más advertencias. La aplicación del operador null-forgiving a una expresión de este tipo solicita que el compilador no emita ninguna advertencia para la expresión. En respuesta, un compilador puede optar por no emitir advertencias y también puede modificar su análisis posterior.
Ejemplo: Considere 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 los parámetros del método
Assign,lv&rv, consisten enstring?, conlvsiendo este un parámetro de salida y que realiza una asignación sencilla.El método
Mpasa la variables, de tipostring, como parámetro de salida paraAssign; el compilador utilizado emite una advertencia ya quesno es una variable que acepta valores nulos. Dado que el segundo argumento deAssignno puede ser null, se usa el operador null-forgiving para anular la advertencia.ejemplo final
Fin del texto normativo condicional.
12.8.10 Expresiones de invocación
12.8.10.1 General
Una invocation_expression se usa para invocar un método.
invocation_expression
: primary_expression '(' argument_list? ')'
;
La primary_expression puede ser una null_forgiving_expression solo si tiene un delegate_type.
Un invocation_expression se enlaza dinámicamente (§12.3.3) si se cumple al menos una de las siguientes condiciones:
- La primary_expression tiene el tipo
dynamicen tiempo de compilación. - Al menos un argumento de la argument_list opcional tiene el tipo en el tiempo de compilación
dynamic.
En este caso, el invocation_expression se clasifica como un valor de tipo dynamic. Las reglas siguientes para determinar el significado de la invocation_expression se aplican en tiempo de ejecución, usando el tipo en tiempo de ejecución en lugar del tipo en tiempo de compilación de la primary_expression y los argumentos que tienen el tipo en tiempo de compilación dynamic. Si la expresión primaria no tiene el tipo en tiempo de compilación dynamic, entonces 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.
La primary_expression de una invocation_expression será un grupo de métodos o un valor de un delegate_type. Si la primary_expression es un grupo de métodos, la invocation_expression es una invocación de método (sección 12.8.10.2) Si la primary_expression es un valor de un delegate_type, la invocation_expression es una invocación de delegado (sección 12.8.10.4). Si la expresión primaria no es un grupo de métodos ni un valor de un tipo delegado , se produce un error en tiempo de vinculación.
El argument_list opcional (sección 12.6.2) proporciona valores o referencias a variables para los parámetros del método.
El resultado de la evaluación de una invocation_expression se clasifica como sigue:
- Si el invocation_expression invoca un método que no devuelve valor (§15.6.1) o un delegado que no devuelve valor, el resultado es nulo. 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.21). De lo contrario, se produce un error en tiempo de enlace.
- En caso contrario, si la invocation_expression invoca un método returns-by-ref (§15.6.1) o un delegado returns-by-ref, el resultado es una variable con un tipo asociado del tipo de retorno del método o delegado. Si la invocación es de un método de instancia y el receptor es de un tipo de clase
T, el tipo asociado se elige de la primera declaración o invalidación del método encontrado al comenzar conTy buscar entre sus clases base. - De lo contrario, la invocation_expression invoca un método de retorno por valor (§15.6.1) o un delegado de retorno por valor, y el resultado es un valor, con un tipo asociado al tipo de retorno del método o delegado. Si la invocación es de un método de instancia y el receptor es de un tipo de clase
T, el tipo asociado se elige de la primera declaración o invalidación del método encontrado al comenzar conTy buscar entre sus clases base.
12.8.10.2 Invocaciones de métodos
Para una invocación de método, el primary_expression del invocation_expression deberá ser un grupo de métodos. El grupo de métodos identifica el método a invocar o el conjunto de métodos sobrecargados entre los que elegir un método específico a invocar. En este último caso, la determinación del método específico a invocar se basa en el contexto proporcionado por los tipos de los argumentos de argument_list.
El procesamiento en tiempo de enlace de una invocación de método de la forma M(A), donde M es un grupo de métodos (posiblemente incluyendo una lista de argumentos de tipo ), y A es una lista de argumentos opcional, consiste en los siguientes pasos:
- Se construye el conjunto de métodos candidatos para la invocación del método. Para cada método
Fasociado al grupo de métodosM:- Si
Fno es genéricoF, es un candidato cuando:-
Mno tiene lista de argumentos de tipo, y -
Fes aplicable con respecto aA(sección 12.6.4.2).
-
- Si
Fes genérico yMno tiene lista de argumentos de tipoF, es candidato cuando:- La inferencia de tipos (§12.6.3) se realiza correctamente, infiriendo una lista de argumentos de tipo para la llamada.
- Una vez que los argumentos de tipo inferidos se sustituyen por los parámetros de tipo de método correspondientes, todos los tipos construidos en la lista de parámetros de
Fsatisfacen sus restricciones (sección 8.4.5), y la lista de parámetros deFes aplicable con respecto aA(sección 12.6.4.2).
- Si
Fes genérico yMincluye una lista de argumentos de tipo,Fes un candidato cuando:-
Ftiene el mismo número de parámetros de tipo de método que los suministrados en la lista de argumentos de tipo, y - Una vez sustituidos los argumentos de tipo por los correspondientes parámetros de tipo de método, todos los tipos construidos en la lista de parámetros de
Fsatisfacen sus restricciones (sección 8.4.5), y la lista de parámetros deFes aplicable con respecto aA(sección 12.6.4.2).
-
- Si
- El conjunto de métodos candidatos se reduce para contener solo métodos de los tipos más derivados: Para cada método
C.Fdel conjunto, dondeCes el tipo en el que se declara el métodoF, se eliminan del conjunto todos los métodos declarados en un tipo base deC. Además, siCes un tipo de clase distinto deobject, todos los métodos declarados en un tipo de interfaz se eliminan del conjunto.Nota: Esta última regla solo tiene efecto cuando el grupo de métodos es el resultado de una búsqueda de miembros en un parámetro de tipo que tiene una clase base efectiva distinta de
objecty un conjunto de interfaces efectivo no vacío. nota final - Si el conjunto de métodos candidatos resultante 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 una invocación a un método de extensión (sección 12.8.10.3). Si esto falla, no existen métodos aplicables y se produce un error de tiempo de enlace.
- El mejor método del conjunto de métodos candidatos se identifica utilizando las reglas de resolución de sobrecargas de sección 12.6.4. Si no se puede identificar el mejor método, la invocación del método es ambigua y se produce un error de vinculación. 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) por los correspondientes parámetros de tipo de método.
Una vez que se ha seleccionado y validado un método en tiempo de enlace mediante los pasos anteriores, la invocación real en tiempo de ejecución se procesa según las reglas de invocación de miembros de función descritas en la sección 12.6.6.
Nota: El efecto intuitivo de las reglas de resolución descritas anteriormente es el siguiente: Para localizar el método concreto invocado por una invocación de método, comience con el tipo indicado por la invocación de método y proceda hacia arriba en la cadena de herencia hasta que se encuentre al menos una declaración de método aplicable, accesible y no sobrecargado. A continuación, realice la inferencia de tipos y la resolución de sobrecargas en el conjunto de métodos aplicables, accesibles y no anulables declarados en ese tipo e invoque al método seleccionado. Si no se encuentra ningún método, intenta procesar la invocación como una invocación a un método de extensión. nota final
12.8.10.3 Invocaciones a métodos de extensión
En una invocación de método (§12.6.6.2) de uno de los formatos
«expr» . «identifier» ( )
«expr» . «identifier» ( «args» )
«expr» . «identifier» < «typeargs» > ( )
«expr» . «identifier» < «typeargs» > ( «args» )
si el procesamiento normal de la invocación no encuentra métodos aplicables, 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 de tiempo de compilación dynamic, los métodos de extensión no se aplicarán.
El objetivo es encontrar el mejor type_nameC, para que pueda tener lugar la correspondiente invocación a método estático:
C . «identifier» ( «expr» )
C . «identifier» ( «expr» , «args» )
C . «identifier» < «typeargs» > ( «expr» )
C . «identifier» < «typeargs» > ( «expr» , «args» )
Un método de extensión Cᵢ.Mₑ es elegible si:
-
Cᵢes una clase no genérica y no anidada - El nombre de
Mₑes identificador -
Mₑes accesible y aplicable cuando se aplique a los argumentos como un método estático como se muestra arriba - Existe una conversión implícita de identidad, referencia o boxing desde expr al tipo del primer parámetro de
Mₑ.
La búsqueda para C procede como sigue:
- 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 que la contiene, se realizan intentos sucesivos para buscar un conjunto candidato de métodos de extensión.
- Si el espacio de nombres o unidad de compilación dado contiene directamente declaraciones de tipos no genéricos
Cᵢcon métodos de extensión elegiblesMₑ, entonces 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 unidad de compilación dados contienen directamente declaraciones de tipos no genéricos
Cᵢcon métodos de extensión elegiblesMₑ, entonces el conjunto de esos métodos de extensión es el conjunto candidato.
- Si el espacio de nombres o unidad de compilación dado contiene directamente declaraciones de tipos no genéricos
- Si no se encuentra ningún conjunto candidato en ninguna declaración de espacio de nombres o unidad de compilación adjunta, se produce un error de compilación.
- En caso contrario, se aplica la resolución de sobrecarga al conjunto candidato como se describe en la sección 12.6.4. Si no se encuentra el mejor método, se produce un error de compilación.
-
Ces el tipo dentro del cual se declara el mejor método como método de extensión.
Si se utiliza C como objetivo, la llamada al método se procesa como una invocación a un método estático (sección 12.6.6).
Nota: a diferencia de una invocación a un método de instancia, no se lanza ninguna excepción cuando expr se evalúa a una referencia nula. En su lugar, este valor de
nullse pasa al método de extensión, tal como se haría a través de una invocación de método estático normal. Depende de la implementación del método de extensión decidir cómo responder a una llamada de este tipo. 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 mediante una directiva de espacio de nombres.
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, el método de
Btiene prioridad sobre el primer método de extensión eCtiene 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 prioridad sobreC.G, yE.Ftiene prioridad sobre yD.FC.F.ejemplo final
12.8.10.4 invocaciones de delegados
Para una invocación de delegado, la primary_expression de la invocation_expression debe ser un valor de un delegate_type. Además, considerando que el delegate_type sea miembro de función que tenga la misma lista de parámetros que el delegate_type, el delegate_type será aplicable (§12.6.4.2) en relación con la argument_list de la invocation_expression.
El procesamiento en tiempo de ejecución de una invocación de delegado con el formato D(A), donde D es una primary_expression de un delegate_type y A es una argument_list opcional, consta de los pasos siguientes:
-
Dse evalúa. Si esta evaluación causa una excepción, no se ejecutan más pasos. - Se evalúa la lista de argumentos
A. Si esta evaluación causa una excepción, no se ejecutan más pasos. - Se comprueba si el valor de
Des válido. Si el valor deDesnull, se lanza unSystem.NullReferenceExceptiony no se ejecutarán más pasos. - De lo contrario,
Des una referencia a una instancia de delegado. Las invocaciones a miembros de funciones (§12.6.6) se realizan en cada una de las entidades invocables de la lista de invocaciones del delegado. Para las entidades invocables que constan de una instancia y un método de instancia, la instancia para la invocación es la instancia contenida en la entidad invocable.
Consulte §21.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, desde el punto de vista sintáctico, un null_conditional_member_access (§12.8.8) o un 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.21.1) o method_body (§15.6.1).
A diferencia del null_conditional_member_access o null_conditional_element_access, que son sintácticamente equivalentes, una null_conditional_invocation_expression puede no tener valor alguno.
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 se puede incluir solo si el null_conditional_member_access o null_conditional_element_access tiene un delegate_type.
Una expresión null_conditional_invocation_expressionE 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 [. Permita PA firmar la concatenación de P y A.
Cuando E se produce como una statement_expression el significado de E es el mismo que el significado de la instrucción:
if ((object)P != null) PA
salvo que P solo se evalúa una vez.
Cuando E ocurre como un anonymous_function_body o method_body el significado de E depende de su clasificación:
Si
Ese clasifica como nada, su significado es el mismo que el del bloque:{ if ((object)P != null) PA; }salvo que
Psolo se evalúa una vez.En caso contrario, el significado de
Ees el mismo que el del bloque:{ return E; }y, a su vez, el significado de este bloque depende de si
Ees sintácticamente equivalente a un null_conditional_member_access (§12.8.8) o null_conditional_element_access (§12.8.13).
12.8.12 Acceso al elemento
12.8.12.1 General
Un element_access consta de un primary_expression, seguido de un token "[", seguido de un argument_list, seguido de un token "]". La argument_list consta de uno o varios argumentos, separados por comas.
element_access
: primary_expression '[' argument_list ']'
;
Al reconocer un primary_expression si las alternativas element_access y pointer_element_access (§24.6.4) son aplicables, se elegirá esta última si el primary_expression incrustado es de tipo de puntero (§24.3).
El primary_expression de un element_access no será un array_creation_expression a menos que incluya un array_initializer o un stackalloc_expression a menos que incluya un stackalloc_initializer.
Nota: Esta restricción existe para no permitir código potencialmente confuso, como:
var a = new int[3][1];que, de lo contrario, se interpretaría como:
var a = (new int[3])[1];Se aplica una restricción similar a null_conditional_element_access (§12.8.13). nota final
Un element_access se enlaza dinámicamente (§12.3.3) si se cumple al menos una de las siguientes condiciones:
- La primary_expression tiene el tipo
dynamicen tiempo de compilación. - Al menos una expresión del argument_list tiene el tipo
dynamicen tiempo de compilación .
En este caso, el tipo en tiempo de compilación del element_access depende del tipo de tiempo de compilación de su primary_expression: si tiene un tipo de matriz, el tipo en tiempo de compilación es el tipo de elemento de ese tipo de matriz; De lo contrario, el tipo en tiempo de compilación es dynamic y el element_access se clasifica 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 de tiempo de ejecución en lugar del tipo de tiempo de compilación de las expresiones primary_expression y argument_list que tienen el tipo de tiempo de compilación dynamic. Si el primary_expression no tiene el tipo de tiempo de compilación dynamic, entonces el acceso al elemento se somete a una comprobación limitada en tiempo de compilación, como se describe en §12.6.5.
Ejemplo:
var index = (dynamic)1; // index has compile-time type dynamic int[] a = {0, 1, 2}; var a_elem = a[index]; // dynamically bound, a_elem has compile-time type int string s = "012"; var s_elem = s[index]; // dynamcially bound, s_elem has compile-time type dynamicejemplo final
Si el primary_expression de un element_access es:
- un valor de un tipo de matriz, el element_access es un acceso de matriz (§12.8.12.2);
- un valor de
stringtipo, el element_access es un acceso de cadena (§12.8.12.3); - De lo contrario, el primary_expression será una variable o un valor de una clase, estructura o tipo de interfaz que tenga uno o varios miembros del indizador, en cuyo caso el element_access es un acceso de indexador (§12.8.12.4).
12.8.12.2 Acceso a matrices
Para que una matriz tenga acceso a la argument_list no contendrá argumentos con nombre ni argumentos de referencia (§15.6.2.3).
El número de expresiones de la argument_list será el mismo que el rango del array_type, y cada expresión será:
- de tipo
int,uint,longoulong; o - solo para el acceso de matriz de clasificación única, de tipo
IndexoRange; o - se puede convertir implícitamente en uno o varios de los tipos anteriores.
El procesamiento en tiempo de ejecución de un acceso de matriz del formulario P[A], donde P es un primary_expression de un array_type y A es un argument_list de expresiones de índice, consta de los pasos siguientes:
-
Pse evalúa. Si esta evaluación causa una excepción, no se ejecutan más pasos. - Para cada expresión de índice del argument_list en orden, de izquierda a derecha:
- La expresión de índice se evalúa, deje que el tipo del valor resultante sea T;
- A continuación, este valor se convierte en el primero de los tipos:
int, ,uintlong,ulongo solo para el acceso de matriz de clasificación única,IndexoRange; para el que existe una conversión implícita (§10.2) de T. - Si la evaluación de una expresión de índice o la conversión implícita subsiguiente provocan una excepción, no se evalúan más expresiones de índice y no se ejecutan más pasos.
- Se comprueba si el valor de
Pes válido. Si el valor dePesnull, se lanza unSystem.NullReferenceExceptiony no se ejecutarán más pasos. - Si los pasos anteriores han generado un único valor de índice de tipo
Range, siga estos pasos:- Deje que L sea la longitud de la matriz a la que hace
Preferencia . -
Ase comprueba que sea válido con respecto a L (§18.3). Si no es así, se produce unaSystem.ArgumentOutOfRangeExceptionexcepción y no se ejecutan pasos adicionales. - El desplazamiento inicial, S y el número de elementos, N, para
Acon respecto a L se determinan como se describe paraGetOffsetAndLength(§18.3). - El resultado del acceso a la matriz es una matriz que contiene una copia superficial de los N elementos de a partir del
Píndice S. Si N es cero, la matriz tiene cero elementos.
- Deje que L sea la longitud de la matriz a la que hace
Nota: Tanto S como N pueden ser cero ($24.3). La indexación de una matriz vacía normalmente no es válida; sin embargo, la indexación con un intervalo vacío a partir de cero es válida y devuelve una matriz vacía. La definición también permite que S sea L, el índice de extremo anterior (§18.1), en cuyo caso N será cero y se devolverá una matriz vacía. nota final
Nota: No se puede asignar un intervalo de elementos de una matriz al uso de un acceso a una matriz. Esto difiere de los accesos del indexador (§12.8.12.4), que pueden, pero no necesitar, admitir la asignación a un intervalo de índices especificados por un
Rangevalor. nota final
- De lo contrario:
- El resultado de evaluar el acceso a la matriz es una referencia de variable (§9.5) del tipo de elemento de la matriz.
- El valor de cada expresión de la argument_list se comprueba con los límites reales de cada dimensión de la instancia de matriz referenciada por
P. Si uno o más valores están fuera de rango, se lanza unSystem.IndexOutOfRangeExceptiony no se ejecutan más pasos. - La referencia variable del elemento de matriz proporcionado por las expresiones de índice se calcula y se convierte en el resultado del acceso a la matriz.
12.8.12.3 Acceso a cadenas
Para obtener acceso a una cadena, el argument_list del element_access contendrá un único argumento de valor sin nombre (§15.6.2.2), que será:
- de tipo
int,IndexoRange; o - convertible implícitamente a uno o varios de los tipos anteriores.
El procesamiento en tiempo de ejecución de un acceso de cadena del formulario P[A], donde P es un primary_expression de string tipo y A es una expresión única, consta de los pasos siguientes:
-
Pse evalúa. Si esta evaluación causa una excepción, no se ejecutan más pasos. - La expresión de índice se evalúa, deje que el tipo del valor resultante sea T;
- A continuación, este valor se convierte en el primero de los tipos:
int,IndexoRange; para los que existe una conversión implícita (§10.2) de T . - Si la evaluación de una expresión de índice o la conversión implícita subsiguiente provocan una excepción, no se evalúan más expresiones de índice y no se ejecutan más pasos.
- Se comprueba si el valor de
Pes válido. Si el valor dePesnull, se lanza unSystem.NullReferenceExceptiony no se ejecutarán más pasos. - Si los pasos anteriores han generado un valor de índice de tipo
Range, haga lo siguiente:- El resultado de evaluar el acceso a la cadena es un valor de
stringtipo. - Deje que L sea la longitud de la cadena a la que hace
Preferencia . -
Ase comprueba que sea válido con respecto a L (§18.3), si no se produce unaSystem.ArgumentOutOfRangeExceptionexcepción y no se ejecuta ningún paso adicional. - El desplazamiento inicial, S y el número de elementos, N, para
Acon respecto a L se determinan como se describe paraGetOffsetAndLength(§18.3). - El resultado del acceso a la cadena es una cadena formada copiando los N caracteres de a partir de
PS, si N es cero, la cadena está vacía.
- El resultado de evaluar el acceso a la cadena es un valor de
Nota: Tanto S como N pueden ser cero (§18.3). La indexación de una cadena vacía normalmente no es válida; sin embargo, la indexación con un intervalo vacío a partir de cero es válida y devuelve una cadena vacía. La definición también permite que S sea L, el índice de extremo anterior (§18.1), en cuyo caso N será cero y una cadena vacía devuelta. nota final
- De lo contrario:
- El resultado de evaluar el acceso a la cadena es un valor de
chartipo. - El valor de la expresión de índice convertido se comprueba con los límites reales de la instancia de cadena a la que hace
Preferencia . Si el valor está fuera del intervalo, se produce unaSystem.IndexOutOfRangeExceptionexcepción y no se ejecutan pasos adicionales. - El valor de carácter en el desplazamiento de la expresión de índice convertida con la cadena
Pse convierte en el resultado del acceso a la cadena.
- El resultado de evaluar el acceso a la cadena es un valor de
Acceso al indexador 12.8.12.4
Para un acceso de indexador, el primary_expression de la 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 a la argument_list de la element_access. El argument_list no out contendrá argumentos ni ref .
El procesamiento en tiempo de vinculación de un acceso de indexador de la forma P[A], donde P es un primary_expression de una clase, estructura o tipo de interfaz T, y A es un argument_list, consta de los pasos siguientes:
- El conjunto de indexadores proporcionados por
Tse ha construido. El conjunto consiste en todos los indexadores declarados enTo un tipo base deTque no son declaraciones de anulación y son accesibles en el contexto actual (sección 7.5). - El conjunto se reduce a los indexadores que son aplicables y que no están ocultos por otros indexadores. Las siguientes reglas se aplican a cada indexador
S.Idel conjuntoS, donde es el tipo en el que se declara el indexadorI:- Si
Ino es aplicable con respecto aA(sección 12.6.4.2), se eliminaIdel conjunto. - Si
Ies aplicable con respecto aA(sección 12.6.4.2), todos los indexadores declarados en un tipo base deSse eliminan del conjunto. - Si
Ies aplicable con respecto aA(sección 12.6.4.2) ySes un tipo de clase distinto deobject, todos los indexadores declarados en una interfaz se eliminan del conjunto.
- Si
- Si el conjunto resultante de indexadores candidatos está vacío, entonces no existen indexadores aplicables y se produce un error de vinculación.
- El mejor indexador del conjunto de indexadores candidatos se identifica utilizando las reglas de resolución de sobrecargas de sección 12.6.4. Si no se puede identificar el mejor indexador, el acceso al indexador es ambiguo y se produce un error de vinculación.
- Se comprueban los descriptores de acceso del mejor indexador:
- Si el acceso del indexador es el destino de una asignación, el indexador tendrá un descriptor de acceso set o ref get; de lo contrario, se produce un error en tiempo de enlace;
- De lo contrario, el indexador tendrá un descriptor de acceso get o ref get; de lo contrario, se produce un error en tiempo de enlace.
El procesamiento en tiempo de ejecución del acceso al indexador consta de los pasos siguientes:
- Se evalúa el primary_expression
Pde destino. - Las expresiones de índice del argument_list
Ase evalúan en orden, de izquierda a derecha. - Con el mejor indexador determinado en tiempo de enlace:
12.8.13 Acceso a elementos condicionales NULL
Un null_conditional_element_access consta de un primary_expression seguido de los dos tokens "?" y "[", seguido 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_expression '?' '[' argument_list ']'
(null_forgiving_operator? dependent_access)*
;
El argument_list de un null_conditional_element_access no deberá contener argumentos out ni ref.
La primary_expression de un null_conditional_element_access no será una array_creation_expression a menos que incluya un array_initializer o una stackalloc_expression a menos que incluya un stackalloc_initializer.
Nota: Esta restricción existe para no permitir código potencialmente confuso. Se aplica una restricción similar a element_access (§12.8.12) donde se puede encontrar un ejemplo de lo que se excluye. nota final
Un null_conditional_element_access es una versión condicional de element_access (sección 12.8.12) y es un error en tiempo de vinculación si el tipo de resultado es void. Para una expresión condicional nula en la que el tipo de resultado puede ser void consulte (sección 12.8.11).
Una expresión null_conditional_element_accessE tiene el formato P?[A]B; donde B son los dependent_accesses, si existen. El significado de E se determina como sigue:
Si el tipo de
Pes un tipo de valor con nulidad:Sea
Tel tipo de la expresiónP.Value[A]B.Si
Tes un parámetro de tipo que no se sabe si es un tipo de referencia o un tipo de valor no anulable, se produce un error de compilación.Si
Tes un tipo de valor no anulable, entonces el tipo deEesT?, y el significado deEes el mismo que el significado de:((object)P == null) ? (T?)null : P.Value[A]BExcepto que
Psolo se evalúa una vez.De lo contrario, el tipo de
EesT, y el significado deEes el mismo que el significado de:((object)P == null) ? null : P.Value[A]BExcepto que
Psolo se evalúa una vez.
De lo contrario:
Sea
Tel tipo de la expresiónP[A]B.Si
Tes un parámetro de tipo que no se sabe si es un tipo de referencia o un tipo de valor no anulable, se produce un error de compilación.Si
Tes un tipo de valor no anulable, entonces el tipo deEesT?, y el significado deEes el mismo que el significado de:((object)P == null) ? (T?)null : P[A]BExcepto que
Psolo se evalúa una vez.De lo contrario, el tipo de
EesT, y el significado deEes el mismo que el significado de:((object)P == null) ? null : P[A]BExcepto que
Psolo se evalúa una vez.
Nota: en una expresión de la forma:
P?[A₀]?[A₁]si
Pse evalúa comonullniA₀niA₁se evalúan. Lo mismo sucede si una expresión es una secuencia de operaciones null_conditional_element_access o null_conditional_member_access§12.8.8.nota final
12.8.14 Acceso a This
Un this_access consta de la palabra clave this.
this_access
: 'this'
;
Solo se permite un this_access dentro del 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 siguientes significados:
- Cuando se usa
thisen una primary_expression dentro de un constructor de instancia de una case, se clasifica como un valor El tipo del valor es el tipo de instancia (sección 15.3.2) de la clase dentro de la cual se produce el uso, y el valor es una referencia al objeto que se está construyendo. - Cuando se usa
thisen una expresión primaria dentro de un método de instancia o descriptor de acceso de instancia de una clase, se considera como un valor. El tipo del valor es el tipo de instancia (sección 15.3.2) de la clase dentro de la cual se produce el uso, y el valor es una referencia al objeto para el cual se invocó el método o descriptores de acceso. - Cuando se usa
thisen un primary_expression dentro de un constructor de instancia de una estructura, se clasifica como una variable. El tipo de la variable es el tipo de instancia (sección 15.3.2) de la estructura dentro de la cual se produce el uso, y la variable representa la estructura que se está construyendo.- Si la declaración del constructor no tiene inicializador de constructor, la variable
thisse comporta exactamente igual que un parámetro de salida del tipo struct. En particular, esto significa que la variable se asignará definitivamente en cada ruta de ejecución del constructor de instancia. - En caso contrario, la variable
thisse comporta exactamente igual que un parámetrorefdel tipo struct. En particular, esto significa que la variable se considera asignada inicialmente.
- Si la declaración del constructor no tiene inicializador de constructor, la variable
- Cuando se usa
thisen una primary_expression dentro de un método de instancia o descriptor de acceso de un struct, se clasifica como una variable. El tipo de la variable es el tipo de instancia (sección 15.3.2) del struct dentro del cual se produce la utilización.- Si el método o descriptor de acceso no es un iterador (§15.15) o una función asincrónica (§15.14), la
thisvariable representa la estructura para la que se invocó el método o descriptor de acceso.- Si la estructura es un
readonly struct, la variablethisse comporta exactamente igual que un parámetro de entrada de tipo estructura - En caso contrario, la variable
thisse comporta exactamente igual que un parámetrorefdel tipo struct
- Si la estructura es un
- Si el método o descriptores de acceso es un iterador o una función asíncrona, la variable
thisrepresenta una copia de la estructura para la que se invocó el método o descriptores de acceso, y se comporta exactamente igual que un parámetro de valor de tipo estructura.
- Si el método o descriptor de acceso no es un iterador (§15.15) o una función asincrónica (§15.14), la
El uso de this en una primary_expression en un contexto distinto de los enumerados anteriormente es un error en tiempo de compilación. En particular, no es posible referirse 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 a la base 12.8.15
Un base_access consta de la palabra clave base seguida de un token "." y un identificador y una type_argument_list opcional o una argument_list entre corchetes:
base_access
: 'base' '.' identifier type_argument_list?
| 'base' '[' argument_list ']'
;
Un base_access se utiliza para acceder a miembros de la clase base que están ocultos por miembros de nombre similar en la clase o estructura actual. Un base_access solo está permitido 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, I indicará un miembro de la clase base de esa clase o estructura. Del mismo modo, cuando base[E] ocurre en una clase, debe existir un indexador aplicable en la clase base.
En tiempo de enlace, las expresiones base_access con formato base.I y base[E] se evalúan exactamente como si estuvieran escritas como ((B)this).I y ((B)this)[E], donde B es la clase base de la clase o struct en la que se encuentra la construcción. Así, base.I y base[E] corresponden a this.I y this[E], excepto this que se considera 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 modifica la determinación de qué miembro de función invocar en tiempo de ejecución (secció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 referenciado por un base_access es abstracto, se produce un error de vinculación.
Nota: A diferencia de
this,baseno es una expresión en sí misma. Es una palabra clave que solo se utiliza en el contexto de un base_access o un constructor_initializer (sección 15.11.2). nota final
12.8.16 Operadores de incremento y decremento postfijos
post_increment_expression
: primary_expression '++'
;
post_decrement_expression
: primary_expression '--'
;
El operando de una operación de incremento o decremento postfija será una expresión clasificada como una variable, un acceso a una propiedad o un acceso a un indizador. El resultado de la operación tiene un valor del mismo tipo que el operando.
Si la primary_expression tiene el tipo dynamic en tiempo de compilación, entonces el operador está ligado dinámicamente (sección 12.3.3), la post_increment_expression o la post_decrement_expression tienen el tipo dynamic en tiempo de compilación y se aplican las siguientes reglas en tiempo de ejecución utilizando el tipo en tiempo de ejecución de la 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 deberán tener tanto un descriptor de acceso get como un descriptor de acceso set. Si no es así, se produce un error en tiempo de enlace.
La resolución de sobrecarga de operador unario (sección 12.4.4) se aplica para seleccionar una implementación de operador específica. Existen operadores de ++ y -- predefinidos para los siguientes tipos: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimaly cualquier tipo de enumeración. Los operadores predefinidos ++ devuelven el valor producido al sumar 1 al operando, y los operadores predefinidos -- devuelven el valor producido al restar 1 del operando. En un contexto verificado, si el resultado de esta suma o resta está fuera del rango del tipo de resultado y el tipo de resultado es un tipo entero o un tipo de enumeración, se lanza una System.OverflowException.
Debe haber una conversión implícita del tipo de retorno del operador unario seleccionado al tipo de la primary_expression; de lo contrario, se produce un error de compilación.
El procesamiento en tiempo de ejecución de una operación de incremento o decremento de postfijo de la forma x++ o x-- consta de los siguientes pasos:
- Si
xse clasifica como variable:-
xse evalúa para obtener la variable. - Se guarda el valor de
x. - El valor guardado de
xse convierte al tipo de operando del operador seleccionado y se invoca al operador con este valor como argumento. - El valor devuelto por el operador se convierte al tipo de
xy se almacena en la ubicación dada por la evaluación anterior dex. - El valor guardado de
xse convierte en el resultado de la operación.
-
- Si
xse clasifica como acceso a una propiedad o indexador:- La expresión de instancia (si
xno esstatic) y la lista de argumentos (sixes un acceso indexador) asociada a se evalúan conx, y los resultados se utilizan en las invocaciones subsiguientes del descriptor de acceso get y set. - Se invoca el descriptor de acceso get de
xy se guarda el valor devuelto. - El valor guardado de
xse convierte al tipo de operando del operador seleccionado y se invoca al operador con este valor como argumento. - El valor devuelto por el operador se convierte en el tipo de
xy el accesor set dexse invoca con este valor como valor argumental. - El valor guardado de
xse convierte en el resultado de la operación.
- La expresión de instancia (si
Los ++ operadores y -- también admiten la notación de prefijo (§12.9.7). El resultado de x++ o x-- es el valor de xantes de la operación, mientras que el resultado de ++x o --x es el valor de xdespués de la operación. En cualquier caso, x tiene el mismo valor después de la operación.
Se puede invocar la implementación de un operador ++ o un operador -- mediante notación de prefijo o posfijo. No es posible tener implementaciones de operadores separadas para las dos notaciones.
12.8.17 El operador new
12.8.17.1 General
El operador new se utiliza para crear nuevas instancias de tipos.
Existen tres formas de expresiones new:
- Las expresiones de creación de objetos se usan para crear nuevas instancias de tipos de clase y tipos de valor.
- Las expresiones de creación de matrices se utilizan para crear nuevas instancias de tipos de matrices.
- Las expresiones de creación de delegados se usan para obtener instancias de tipos delegados.
El operador new implica la creación de una instancia de un tipo, pero no implica necesariamente la asignación de memoria. En particular, 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 se utiliza new para crear instancias de tipos de valor.
Nota: Las expresiones de creación de delegados no siempre crean nuevas instancias. Cuando la expresión se procesa del mismo modo que la conversión de un grupo de métodos (sección 10.8) o la conversión de una función anónima (sección 10.7), es posible que se reutilice una instancia de delegado existente. nota final
12.8.17.2 Expresiones de creación de objetos
12.8.17.2.1 General
Una expresión object_creation_expression se utiliza para crear una nueva instancia de class_type or a 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 una expresión de creación de objeto será una clase tipo , un tipo de valor o un parámetro de tipo . El type no puede ser un tuple_type ni un class_type abstracto o estático.
La argument_list opcional (§12.6.2) solo se permite si el type 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 los paréntesis que la encierran, siempre que incluya un inicializador de objeto o un inicializador de colección. Omitir la lista de argumentos del constructor y encerrar los 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.2.2) o inicializador de colección (§12.8.17.2.3).
Si alguno de los argumentos de la lista opcional argument_list tiene el tipo dynamic en tiempo de compilación, la expresión object_creation_expression se vincula dinámicamente (sección 12.3.3) y las siguientes reglas se aplican en tiempo de ejecución utilizando el tipo en tiempo de ejecución de aquellos argumentos de la lista argument_list que tienen el tipo en tiempo de compilación dynamic. No obstante, la creación del objeto se somete a una comprobación limitada en tiempo de compilación, como se describe en la sección 12.6.5.
El procesamiento en tiempo de enlace de una object_creation_expression del formato new T(A), donde T es un class_type o un value_type, y A es una argument_list opcional, consta de los pasos siguientes:
- Si
Tes un value_type yAno está presente:- La object_creation_expression es una invocación al constructor por defecto. El resultado de la expresión object_creation_expression es un valor de tipo
T, es decir, el valor por defecto paraTdefinido en la sección 8.3.3.
- La object_creation_expression es una invocación al constructor por defecto. El resultado de la expresión object_creation_expression es un valor de tipo
- En caso contrario, si
Tes un type_parameter yAno está presente:- Si no se ha especificado ninguna restricción de tipo de valor o restricción de constructor (sección 15.2.5) para
T, se produce un error de vinculación. - El resultado de la expresión object_creation_expression es un valor del tipo en tiempo de ejecución al que se ha vinculado el parámetro de tipo, es decir, el resultado de invocar al constructor por defecto de ese tipo. El tipo en tiempo de ejecución puede ser un tipo de referencia o un tipo de valor.
- Si no se ha especificado ninguna restricción de tipo de valor o restricción de constructor (sección 15.2.5) para
- En caso contrario, si
Tes un class_type o un struct_type:- Si
Tes un class_type abstracto o estático, se produce un error en tiempo de compilación. - El constructor de instancia a invocar se determina utilizando las reglas de resolución de sobrecargas de sección 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 aA(§12.6.4.2). Si el conjunto de constructores de instancia candidatos está vacío, o si no se puede identificar un único constructor de instancia óptimo, se produce un error de vinculación. - El resultado de la expresión object_creation_expression es un valor de tipo
T, es decir, el valor producido por la invocación del constructor de instancia determinado en el paso anterior. - En caso contrario, la expresión object_creation_expression no es válida y se produce un error de vinculación.
- Si
Incluso si el object_creation_expression está enlazado dinámicamente, el tipo en momento de compilación sigue siendo T.
El procesamiento en tiempo de ejecución de una object_creation_expression de la forma new T(A), donde T es class_type o un struct_type y A es una argument_list opcional, consiste en los siguientes pasos:
- Si
Tes un class_type:- Se asigna una nueva instancia de clase
T. Si no hay suficiente memoria disponible para asignar la nueva instancia, se lanza unSystem.OutOfMemoryExceptiony no se ejecutan más pasos. - Todos los campos de la nueva instancia se inicializan con sus valores por defecto (sección 9.3).
- El constructor de instancia se invoca de acuerdo con las reglas de invocación de miembros de función (sección 12.6.6). Se pasa automáticamente una referencia a la instancia recién asignada al constructor de instancia y se puede acceder a la instancia desde dentro de ese constructor de la siguiente manera.
- Se asigna una nueva instancia de clase
- Si
Tes un struct_type:- Se crea una instancia del tipo
Tasignando una variable local temporal. Dado que se requiere que un constructor de instancia de un struct_type asigne definitivamente un valor a cada campo de la instancia que se está creando, no es necesaria la inicialización de la variable temporal. - El constructor de instancia se invoca de acuerdo con las reglas de invocación de miembros de función (sección 12.6.6). Se pasa automáticamente una referencia a la instancia recién asignada al constructor de instancia y se puede acceder a la instancia desde dentro de ese constructor de la siguiente manera.
- Se crea una instancia del tipo
12.8.17.2.2 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, delimitada por los tokens { y }, y separados por comas. Cada member_initializer designará un objetivo para la inicialización. Un identificador nombrará un campo o propiedad accesible del objeto que se está inicializando, mientras que una argument_list encerrada entre corchetes especificará argumentos para un indexador accesible en el objeto que se está inicializando. Es un error que un inicializador de objeto incluya más de una inicialización de miembro para el mismo campo o propiedad.
Nota: Mientras que no se permite que un inicializador de objeto establezca el mismo campo o propiedad más de una vez, no existen tales restricciones para los indexadores. Un inicializador de objeto puede contener varios objetivos de inicializador que hagan referencia a indexadores, e incluso puede utilizar los mismos argumentos de indexador varias veces. nota final
Cada initializer_target va seguido de un signo de igual y, a continuación, una expresión, un inicializador de objeto o un inicializador de colección. No es posible que las expresiones dentro del inicializador de objetos hagan referencia al objeto recién creado que está inicializando.
En el argument_list de un initializer_target no hay compatibilidad implícita con argumentos de tipo Index (§18.4.2) o Range (§18.4.3).
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.23.2) al destino.
Inicializador de miembro que especifica un inicializador de objeto después del signo de 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 propiedad, las asignaciones del inicializador de objetos anidados se tratan como asignaciones a miembros del campo o propiedad. Los inicializadores de objetos anidados no pueden aplicarse a propiedades con un tipo de valor ni a campos de solo lectura con un tipo de valor.
Un inicializador de miembro que especifica un inicializador de colección después del signo de igual es la inicialización de una colección incrustada. En lugar de asignar una nueva colección al campo, propiedad o indexador objetivo, los elementos dados en el inicializador se añaden a la colección referenciada por el objetivo. El destino será de un tipo de colección que cumpla los requisitos especificados en §12.8.17.2.3.
Cuando un objetivo 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 por no ser utilizados (por ejemplo, debido a un inicializador anidado vacío), se evalúan por 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
Pointde la siguiente manera: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
__aes 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 instancia de
Rectangle:public class Rectangle { public Point P1 { get; set; } public Point P2 { get; set; } }Se puede crear e inicializar una instancia de
Rectanglede la siguiente manera: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,__p1y__p2son variables temporales que de otro modo serían invisibles e inaccesibles.Si el constructor de
Rectanglealoca las dos instancias incrustadas dePoint, se pueden usar para inicializar las instancias incrustadas dePointen lugar de instanciar nuevas instancias.public class Rectangle { public Point P1 { get; } = new Point(); public Point P2 { get; } = new Point(); }la siguiente construcción se puede utilizar para inicializar las instancias incrustadas
Pointen 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.2.3 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)*
;
Un inicializador de colección consta de una serie de inicializadores de elementos, delimitados por los tokens { y }, y separados por comas. Cada inicializador de elemento especifica un elemento que se agregará al objeto de colección que se está inicializando y consta de una lista de expresiones, delimitadas por los tokens { y }, y separadas 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.24.
Ejemplo: El siguiente es 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 aplique un inicializador de colección deberá ser de un tipo que implemente a System.Collections.IEnumerable o se producirá un error de compilación. Para cada elemento especificado en orden de izquierda a derecha, se aplica la búsqueda normal de miembros para encontrar un miembro llamado Add. Si el resultado de la búsqueda de miembros no es un grupo de métodos, se produce un error de compilación. En caso contrario, se aplica la resolución de sobrecarga con la lista de expresiones del inicializador del elemento como lista de argumentos, y el inicializador de la colección invoca el método resultante. Así, el objeto de colección contendrá un método de instancia o 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, así como 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,__c1y__c2son variables temporales que de otro modo serían invisibles e inaccesibles.ejemplo final
12.8.17.3 Expresiones de creación de objetos anónimos
Una anonymous_object_creation_expression se utiliza para crear un objeto de 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 objeto anónimo utilizado para crear una instancia del tipo. En concreto, un inicializador de objetos anónimo de la forma
new {
p₁=e₁,p Pudin=e Expulsado, …
Pv=Ev}
declara un tipo anónimo de la forma
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 "ex" correspondiente. La expresión usada en un member_declarator deberá tener 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.
Los nombres de un tipo anónimo y del parámetro de su método Equals son generados automáticamente por el compilador y no pueden ser referenciados en el texto del programa.
Dentro del mismo programa, dos inicializadores de objetos anónimos que especifiquen una secuencia de propiedades con 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
p1yp2son del mismo tipo anónimo.ejemplo final
Los métodos Equals y GetHashcode de los tipos anónimos anulan los métodos heredados de object, y se definen en términos de Equals y GetHashcode de las propiedades, de modo que dos instancias del mismo tipo anónimo son iguales si y solo si todas sus propiedades son iguales.
Un declarador de miembro puede abreviarse en un nombre simple (sección 12.8.4), un acceso a miembro (sección 12.8.7), un inicializador de proyección condicional nulo sección 12.8.8 o un acceso base (sección 12.8.15). Esto se denomina inicializador de proyección y es una forma abreviada de declarar y asignar una propiedad con el mismo nombre. En concreto, declaradores de miembros de los formatos
«identifier», «expr» . «identifier» y «expr» ? . «identifier»
son precisamente equivalentes a los siguientes, respectivamente:
«identifier» = «identifier», «identifier» = «expr» . «identifier» y «identifier» = «expr» ? . «identifier»
Así, en un inicializador de proyección el identificador selecciona tanto el valor como el campo o propiedad al 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.17.4 Expresiones de creación de matrices
Se utiliza una expresión de creación de matriz para crear una instancia nueva de un tipo de matriz .
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 array de la primera forma asigna una instancia de array del tipo que resulta de borrar cada una de las expresiones individuales de la lista de expresiones.
Ejemplo: La expresión de creación de matriz
new int[10,20]produce una instancia de matriz del tipoint[,], y la expresión de creación de matriz newint[10][,]produce una instancia de matriz del tipoint[][,]. ejemplo final
Cada expresión de la lista de expresiones será de tipo int, uint, long o ulong o implícitamente convertible a uno o más de estos tipos. El valor de cada expresión determina la longitud de la dimensión correspondiente en la nueva instancia de matriz asignada. Dado que la longitud de una dimensión de matriz debe ser no negativa, es un error de compilación tener una expresión constante con un valor negativo en la lista de expresiones.
Excepto en un contexto no seguro (§24.2), no se especifica el diseño de las matrices.
Si una expresión de creación de matrices de la primera forma incluye un inicializador de matrices, 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 matrices.
En una expresión de creación de matriz de la segunda o tercera forma, el rango del tipo de matriz especificado o el especificador de rango coincidirá con el del inicializador de matriz. Las longitudes de las dimensiones individuales se deducen del número de elementos en cada uno de los niveles de anidamiento correspondientes del inicializador de matrices. 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 matrices de la tercera forma se denomina expresión de creación de matrices implícitamente tipada. 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.16) del conjunto de expresiones en el inicializador de matriz. Para una matriz multidimensional, es decir, una en la que el rank_specifier contiene al menos una coma, este conjunto de elementos consta de todas las expresiones que se encuentran en los inicializadores de matriz anidados.
Los inicializadores de matrices se describen con más detalle en la sección 17.7.
El resultado de la evaluación de una expresión de creación de matriz se clasifica como un valor, es decir, una referencia a la nueva instancia de matriz asignada. El procesamiento en tiempo de ejecución de una expresión de creación de matriz consta de los siguientes pasos:
- Las expresiones de longitud de dimensión de la expression_list se evalúan en orden, de izquierda a derecha. Tras la evaluación de cada expresión, se realiza una conversión implícita (sección 10.2) a uno de los siguientes tipos:
int,uint,long,ulong. Se elige el primer tipo de la lista para el que existe una conversión implícita. Si la evaluación de una expresión o la conversión implícita posterior provocan una excepción, no se evalúan más expresiones ni se ejecutan más pasos. - Los valores calculados para las longitudes de las dimensiones se validan como sigue: Si uno o más de los valores son menores que cero, se lanza un
System.OverflowExceptiony no se ejecutan más pasos. - Se asigna una instancia de array con las longitudes de dimensión dadas. Si no hay suficiente memoria disponible para asignar la nueva instancia, se lanza un
System.OutOfMemoryExceptiony no se ejecutan más pasos. - Todos los elementos de la nueva instancia de matriz se inicializan con sus valores por defecto (sección 9.3).
- Si la expresión de creación de la 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 están escritas en el inicializador de la matriz; en otras palabras, 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 dada o la asignación subsiguiente al elemento de matriz correspondiente provoca una excepción, no se inicializan más elementos (y los elementos restantes tendrán sus valores por defecto).
Una expresión de creación de array permite instanciar un array con elementos de un tipo de array, pero los elementos de dicho array se inicializarán manualmente.
Ejemplo: la declaración
int[][] a = new int[100][];crea una matriz unidimensional con 100 elementos de tipo
int[]. El valor inicial de cada elemento esnull. No es posible que la misma expresión de creación de matrices cree instancias también de las submatrices y la instrucciónint[][] a = new int[100][5]; // Errorproduce un error de compilación. La instanciación de las submatrices 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 forma "rectangular", es decir, cuando las submatrices tienen todas la misma longitud, es más eficiente utilizar una matriz multidimensional. En el ejemplo anterior, la instanciación de la matriz de matrices crea 101 objetos: una matriz externa y 100 submatrices. Por el contrario,
int[,] a = new int[100, 5];crea un único objeto, una matriz bidimensional, y realiza la asignación en una única instrucción.
nota final
Ejemplo: Los siguientes son ejemplos de expresiones de creación de matrices con tipado 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" }; // ErrorLa última expresión produce un error en tiempo de compilación porque ni
intnistringse convierten implícitamente entre sí, y, por lo tanto, no existe un tipo común mejor. En este caso, se debe usar una expresión de creación de matriz explícitamente tipada, por ejemplo, especificando el tipo comoobject[]. Alternativamente, uno de los elementos puede ser lanzado a un tipo base común, que se convertiría entonces 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.3) 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.5 Expresiones de creación de delegados
Una delegate_creation_expression se usa 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 de tiempo de compilación dynamic 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 crear un delegado. Si el argumento es una función anónima, define directamente los parámetros y el cuerpo del método objetivo del delegado. Si el argumento es un valor, identifica una instancia de delegado de la que crear una copia.
Si la expresión tiene el tipo dynamic en tiempo de compilación, el delegate_creation_expression se enlaza dinámicamente (§12.8.17.5) y las siguientes reglas se aplican en tiempo de ejecución utilizando el tipo de tiempo de ejecución de la expresión. En caso contrario, las reglas se aplican en tiempo de compilación.
El procesamiento en tiempo de enlace de una delegate_creation_expression con el formato nuevo D(E), donde D es un delegate_type y E es una expresión, consta de los pasos siguientes:
Si
Ees un grupo de métodos, la expresión de creación de delegado se procesa del mismo modo que una conversión de grupo de métodos (sección 10.8) deEaD.Si
Ees una función anónima, la expresión de creación de delegado se procesa del mismo modo que una conversión de función anónima (sección 10.7) deEaD.Si
Ees un valor,Eserá compatible (§21.2) conDy el resultado es una referencia a un delegado recién creado con una lista de invocación de entrada única que invocaE.
El procesamiento en tiempo de ejecución de una delegate_creation_expression con el formato nuevo D(E), donde D es un delegate_type y E es una expresión, consta de los pasos siguientes:
- Si
Ees un grupo de métodos, la expresión de creación de delegado se evalúa como una conversión de grupo de métodos (sección 10.8) deEaD. - Si
Ees una función anónima, la creación del delegado se evalúa como una conversión de función anónima deEaD(sección 10.7). - Si
Ees un valor de un delegate_type:-
Ese evalúa. Si esta evaluación causa una excepción, no se ejecutan más pasos. - Si el valor de
Eesnull, se lanza unSystem.NullReferenceExceptiony no se ejecutarán más pasos. - Se asigna una nueva instancia del tipo delegado
D. Si no hay suficiente memoria disponible para asignar la nueva instancia, se lanza unSystem.OutOfMemoryExceptiony 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 establece cuando se instancia el delegado y luego permanece constante durante toda su vida útil. En otras palabras, no es posible cambiar las entidades invocables de destino de un delegado una vez que se haya creado.
Nota: recuerde, cuando se combinan dos delegados o se elimina uno de otro, se obtiene un nuevo delegado; ningún delegado existente 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 retorno del delegado determinan cuál de los métodos sobrecargados debe seleccionarse. 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 campo
A.fse inicializa con un delegado que hace referencia al segundo métodoSquareporque ese método coincide exactamente con la lista de parámetros y el tipo de retorno deDoubleFunc. Si el segundo métodoSquareno hubiera estado presente, se habría producido un error de compilación.ejemplo final
12.8.18 El operador typeof
El operador typeof se utiliza para obtener el objeto System.Type de un tipo.
typeof_expression
: 'typeof' '(' type ')'
| 'typeof' '(' unbound_type_name ')'
| 'typeof' '(' 'void' ')'
;
unbound_type_name
: identifier generic_dimension_specifier?
('.' identifier generic_dimension_specifier?)*
| unbound_qualified_alias_member
('.' identifier generic_dimension_specifier?)*
;
unbound_qualified_alias_member
: identifier '::' identifier generic_dimension_specifier?
;
generic_dimension_specifier
: '<' comma* '>'
;
comma
: ','
;
La primera forma de typeof_expression consiste en una palabra clave typeof seguida de un tipo entre paréntesis. El resultado de una expresión de esta forma es el objeto System.Type para el tipo indicado. Solo hay un objeto System.Type para cada tipo. Esto significa que para un tipo T, typeof(T) == typeof(T) siempre es verdadero. El tipo no puede ser dynamic.
La segunda forma de typeof_expression consiste en una palabra clave typeof seguida de un unbound_type_name entre paréntesis.
Nota: Las gramáticas de unbound_type_name y unbound_qualified_alias_member siguen las de type_name (§7.8) y qualified_alias_member (§14.8.1), salvo que generic_dimension_specifiers se sustituyen por type_argument_lists. nota final
Al reconocer el operando de una typeof_expression, si tanto unbound_type_name como type_name son aplicables, es decir, cuando no contiene ni un generic_dimension_specifier ni una type_argument_list, se elegirá type_name.
Nota: ANTLR hace la elección especificada automáticamente debido a la ordenación de las alternativas de typeof_expression. nota final
El significado de un unbound_type_name se determina como si:
- La secuencia de tokens se convierte en un type_name reemplazando cada generic_dimension_specifier por un type_argument_list que tiene el mismo número de comas y la palabra clave
objectque cada type_argument. - El type_name resultante se resuelve en un tipo construido (§7.8).
- A continuación, el unbound_type_name es el tipo genérico sin enlazar asociado al tipo construido resuelto (§8.4).
Nota: No es necesario que una implementación transforme la secuencia de tokens o genere el tipo construido intermediario, solo que el tipo genérico sin enlazar que se determina es "como si" se seguira este proceso. nota final
Es un error que el nombre de tipo sea un tipo de referencia anulable.
El resultado del typeof_expression es el objeto System.Type para el tipo genérico sin enlazar resultante.
La tercera forma de typeof_expression consta de una palabra clave typeof seguida de una palabra clave void entre paréntesis. El resultado de una expresión de esta forma es el objeto System.Type 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.Typees útil en las bibliotecas de clases que permiten la reflexión sobre los métodos en el lenguaje, donde esos métodos desean representar el tipo de retorno de cualquier método, incluidos los métodosvoid, con una instancia deSystem.Type. nota final
El operador typeof puede utilizarse en un parámetro de tipo. Es un error en tiempo de compilación si se sabe que el nombre del tipo es un tipo de referencia anulable. El resultado es el objeto System.Type para el tipo en tiempo de ejecución que estaba enlazado al parámetro de tipo. Si el tipo en tiempo de ejecución es un tipo de referencia anulable, el resultado es el tipo de referencia no anulable correspondiente. El operador typeof también puede utilizarse en un tipo construido o en un tipo genérico no ligado (sección 8.4.4). El objeto System.Type de un tipo genérico no enlazado no es el mismo que el objeto System.Type del tipo instancia (sección 15.3.2). El tipo instancia es siempre un tipo construido cerrado en tiempo de ejecución, por lo que su objeto System.Type depende de los argumentos de tipo en tiempo de ejecución que se utilicen. El tipo genérico no enlazado, por otra parte, no tiene argumentos de tipo, y produce el mismo objeto System.Type 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 el siguiente resultado:
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]Nótese que
intySystem.Int32son del mismo tipo. El resultado detypeof(X<>)no depende del argumento de tipo, pero el resultado detypeof(X<T>)sí.ejemplo final
12.8.19 El operador sizeof
El operador sizeof devuelve el número de bytes de 8 bits ocupados por una variable de un tipo dado. El tipo especificado como operando de sizeof debe ser un unmanaged_type (§8.8).
sizeof_expression
: 'sizeof' '(' unmanaged_type ')'
;
Para ciertos tipos predefinidos el operador sizeof devuelve un valor constante int como se muestra en la siguiente tabla:
| Expresión | 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 enum T, el resultado de la expresión sizeof(T) es un valor constante igual al tamaño de su tipo subyacente, como se indica más arriba. Para todos los demás tipos de operando, el sizeof operador se especifica en §24.6.9.
12.8.20 Operadores comprobados y no comprobados
Los operadores checked y unchecked sirven para controlar el contexto de comprobación de desbordamiento para conversiones y operaciones aritméticas de tipo integral.
checked_expression
: 'checked' '(' expression ')'
;
unchecked_expression
: 'unchecked' '(' expression ')'
;
El operador checked evalúa la expresión contenida en un contexto verificado, y el operador unchecked evalúa la expresión contenida en un contexto no verificado. Una checked_expression o una unchecked_expression corresponde exactamente a una parenthesized_expression (§12.8.5), excepto que la expresión contenida se evalúa en el contexto dado de comprobación de desbordamiento.
El contexto de comprobación de desbordamiento también se puede controlar a través de las instrucciones checked 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.7), cuando el operando es de un tipo entero o de enumeración. - El operador unario predefinido
-(sección 12.9.3), cuando el operando es de tipo integral. - Los operadores binarios predefinidos
+,-,*y/(§12.12), cuando ambos operandos son de tipos enteros o de enumeración. - Conversiones numéricas explícitas (§10.3.2) desde un tipo entero o de enumeración a otro tipo entero o de enumeración, o desde
floatodoublea un tipo entero o de enumeración.
Cuando una de las operaciones anteriores produce un resultado demasiado grande para representarlo en el tipo de destino, el contexto en el que se realiza la operación controla el comportamiento resultante:
- En un
checkedcontexto, si la operación es una expresión constante (§12.25), se produce un error en tiempo de compilación. De lo contrario, si la operación se realiza en tiempo de ejecución, se lanza una excepciónSystem.OverflowException. - En un contexto
unchecked, el resultado se trunca descartando los bits de orden superior que no caben en el tipo de destino.
En el caso de expresiones no constantes (§12.25) (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.
En el caso de las expresiones constantes (§12.25) (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 contexto unchecked, 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 los contextos checked o unchecked en los que ocurre la función anónima.
Example: En el código de ejemplo 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 notifica ningún error de compilación, ya que ninguna de las expresiones puede evaluarse en tiempo de compilación. En tiempo de ejecución, el método
Flanza unSystem.OverflowExceptiony el métodoGdevuelve –727379968 (los 32 bits inferiores del resultado fuera de rango). El comportamiento del métodoHdepende del contexto de comprobación de desbordamiento predeterminado para la compilación, peroFes el mismo queG.ejemplo final
Example: En el código de ejemplo 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 de constantes en
FyHhacen que se notifiquen errores en tiempo de compilación porque las expresiones se evalúan en un contexto dechecked. También se produce un desbordamiento al evaluar la expresión constante enG, pero como la evaluación tiene lugar en un contextounchecked, no se informa del desbordamiento.ejemplo final
Los operadores checked y unchecked solo afectan al contexto de comprobación de desbordamiento de 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.
Example: En el código de ejemplo siguiente
class Test { static int Multiply(int x, int y) => x * y; static int F() => checked(Multiply(1000000, 1000000)); }el uso de
checkeden F no afecta a la evaluación dex * yenMultiply, por lo quex * yse evalúa en el contexto de comprobación de desbordamiento por defecto.ejemplo final
El operador unchecked es conveniente cuando se escriben constantes de los tipos integrales con signo 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 del tipo
uint. Debido a que las constantes están fuera del rangoint, sin el operadorunchecked, la conversión aintproduciría errores de compilación.ejemplo final
Nota: Los operadores e instrucciones
checkedyuncheckedpermiten a los programadores controlar ciertos 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 produce una excepción por desbordamiento, incluso dentro de una construcción explícitamente no comprobada. De forma similar, la multiplicación de dos flotantes nunca produce una excepción por desbordamiento, incluso dentro de una construcción explícitamente comprobada. Además, otros operadores nunca se ven afectados por el modo de comprobación, ya sea por defecto o explícito. nota final
12.8.21 Expresiones de valor por defecto
Una expresión de valor por defecto se utiliza para obtener el valor por defecto (sección 9.3) de un tipo.
default_value_expression
: explicitly_typed_default
| default_literal
;
explicitly_typed_default
: 'default' '(' type ')'
;
default_literal
: 'default'
;
Un default_literal representa un valor predeterminado (sección 9.3). No tiene tipo, pero puede convertirse a cualquier tipo mediante la conversión de un literal por defecto (sección 10.2.16).
El resultado de un default_value_expression es el valor predeterminado (§9.3) del tipo explícito en un explicitly_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.25) si el tipo es uno de:
- un tipo de referencia
- un parámetro de tipo que se sabe que es un tipo de referencia (sección 8.2);
- uno de los siguientes tipos de valor:
sbyte,byte,short,ushort,int,uint,long,ulong,char,float,double,decimal,bool,o - cualquier tipo de enumeración.
12.8.22 Asignación de pila
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 retorna.
Las reglas de contexto seguro para una expresión de asignación de pila se describen en §16.4.15.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
;
El unmanaged_type (sección 8.8) indica el tipo de los elementos que se almacenarán en la nueva ubicació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 debe ser convertible implícitamente al tipo int.
Dado que el tamaño de una asignación de pila no puede ser negativo, especificar el número de elementos como una constant_expression que se evalúe como un valor negativo es un error de compilación.
En tiempo de ejecución, si el número de elementos a asignar es negativo, el comportamiento es indefinido. Si es cero, no se realiza ninguna asignación y el valor devuelto está definido por la implementación. Si no hay suficiente memoria disponible para asignar los elementos se lanza un System.StackOverflowException.
Cuando está presente un stackalloc_initializer:
- Si se omite unmanaged_type, se deduce que sigue las reglas del mejor tipo común (§12.6.3.16) 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 (sección 10.2). Los stackalloc_element_initializers inicializan elementos en la memoria asignada en orden creciente, comenzando con el elemento en el índice cero. En ausencia de un stackalloc_initializer, el contenido de la memoria recién asignada es indefinido.
Si un stackalloc_expression se produce directamente como expresión de inicialización de un local_variable_declaration (§13.6.2), donde el local_variable_type es un tipo de puntero (§24.3) o inferido (var), el resultado del stackalloc_expression es un puntero de tipo T* (§24.9). En este caso la expresión stackalloc_expression debe aparecer en código no seguro. En otro caso el resultado de una expresión stackalloc_expression es una instancia del tipo Span<T>, donde T es el tipo no administrado unmanaged_type:
-
Span<T>(sección C.3) es un tipo ref struct (sección 16.2.3), que presenta un bloque de memoria, aquí el bloque asignado por la expresión stackalloc_expression, como una colección indexable de elementos tipados (T). - La propiedad
Lengthdel objeto resultado devuelve el número de elementos que han sido asignados. - El indexador del resultado (§15.9) devuelve una variable_reference (§9.5) a un elemento del bloque asignado y su rango se verifica.
Los inicializadores de asignación de pila no están permitidos en bloques catch o finally (sección 13.11).
Nota: No hay forma de liberar explícitamente la memoria asignada usando
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 operador stackalloc, C# no proporciona ninguna construcción predefinida para administrar la memoria recopilada sin elementos no utilizados. Tales servicios son típicamente proporcionados por bibliotecas de clases de soporte o importados directamente del 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, el resultadostackalloces unSpan<int>, que es convertido por un operador implícito aReadOnlySpan<int>. De forma similar, paraspan9, el resultanteSpan<double>se convierte al tipo definidoWidget<double>por el usuario utilizando la conversión, como se muestra. ejemplo final
12.8.23 El operador nameof
Una nameof_expression se utiliza 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, una nameof_expression es siempre sintácticamente ambigua con una invocación del nombre simple nameof. Por razones de compatibilidad, si una búsqueda de nombre (sección 12.8.4) del nombre nameof tiene éxito, la expresión se trata como una invocation_expression —independientemente de si la invocación es válida—. De lo contrario, es una nameof_expression.
Las búsquedas de nombres y accesos a miembros 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 la sección 12.8.4 y sección 12.8.7 da lugar a un error porque se ha encontrado un miembro de instancia en un contexto estático, una expresión nameof_expression no produce tal error.
Se trata de un error en tiempo de compilación que una named_entity designe que un grupo de métodos tenga una type_argument_list. Es un error de tiempo de compilación que un named_entity_target tenga el tipo dynamic.
Una expresión nameof_expression es una expresión constante de tipo string, y 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 de la named_entity antes de la type_argument_list final opcional, transformada de la siguiente manera:
- El prefijo "
@", si se utiliza, se elimina. - Cada unicode_escape_sequence se transforma en su carácter Unicode correspondiente.
- Se quitan todos los formatting_characters.
Estas son las mismas transformaciones que se aplican en la sección 6.4.3 cuando se comprueba la igualdad entre identificadores.
Ejemplo: A continuación se muestran los resultados de varias expresiones de
nameof, suponiendo que hay un tipo genéricoList<T>declarado en el espacio de nombresSystem.Collections.Generic: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)a solo "Genérico" en lugar del espacio de nombres completo, y denameof(TestAlias)a "TestAlias" en lugar de "String". ejemplo final
12.8.24 Expresiones de método anónimas
Una anonymous_method_expression es una de las dos formas de definir una función anónima. Estos se describen más adelante en §12.21.
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 postfijo que admite valores NULL (§12.8.9),
!, debido a su naturaleza en 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
| '^' 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 (§24.6.2) y addressof_expression (§24.6.5) solo están disponibles en código no seguro (§24).
Si el operando de una unary_expression tiene un tipo de tiempo de compilación dynamic, se enlaza dinámicamente (§12.3.3). En este caso:
- el tipo en tiempo de compilación del unary_expression es:
-
Indexpara el^operador from-end del índice (§12.9.6) -
dynamicpara todos los demás operadores unarios; y
-
- La resolución que se describe a continuación tendrá lugar en tiempo de ejecución mediante el tipo de tiempo de ejecución del operando.
12.9.2 Operador de suma unario
Para una operación de la forma +x, se aplica la resolución de sobrecarga de operador unario (sección 12.4.4) para seleccionar una implementación de operador específica. El operando se convierte al tipo de parámetro del operador seleccionado, y el tipo del resultado es el tipo de retorno del operador. Los operadores de suma unarios 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 elevadas (§12.4.8) de los operadores de suma unarios predefinidos sin elevar definidos anteriormente también están predefinidas.
12.9.3 Operador de resta unario
Para una operación de la forma –x, se aplica la resolución de sobrecarga de operador unario (sección 12.4.4) para seleccionar una implementación de operador específica. El operando se convierte al tipo de parámetro del operador seleccionado, y el tipo del resultado es el tipo de retorno del operador. Los operadores de resta unarios predefinidos son:
Negación de enteros:
int operator –(int x); long operator –(long x);El resultado se calcula restando de cero
X. Si el valor deXes el valor representable más pequeño del tipo de operando (-2³¹ parainto -2⁶³ paralong), entonces la negación matemática deXno es representable dentro del tipo de operando. Si esto ocurre dentro de un contexto dechecked, se lanza unSystem.OverflowException; si ocurre dentro de un contexto deunchecked, el resultado es el valor del operando y no se notifica el desbordamiento.Si el operando del operador de negación es del tipo
uint, se convierte al tipolong, y el tipo del resultado eslong. Una excepción es la regla que permite escribir el valorint−2147483648(-2³¹) como un literal entero decimal (sección 6.4.5.3).Si el operando del operador de negación es del tipo
ulong, se produce un error de compilación. Una excepción es la regla que permite escribir el valorlong−9223372036854775808(-2⁶³) como un literal entero decimal (sección 6.4.5.3)Negación en coma flotante:
float operator –(float x); double operator –(double x);El resultado es el valor de
Xcon su signo invertido. SixesNaN, el resultado también esNaN.Negación decimal:
decimal operator –(decimal x);El resultado se calcula restando de cero
X. La negación decimal es equivalente a utilizar el operador menos unario de tipoSystem.Decimal.
Las formas elevadas (§12.4.8) de los operadores de resta unarios predefinidos sin elevar definidos anteriormente también están predefinidas.
12.9.4 Operador lógico de negación
Para una operación de la forma !x, se aplica la resolución de sobrecarga de operador unario (sección 12.4.4) para seleccionar una implementación de operador específica. El operando se convierte al tipo de parámetro del operador seleccionado, y el tipo del resultado es el tipo de retorno 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 elevadas (§12.4.8) del operador de negación lógica predefinido sin elevar definido anteriormente también están predefinidas.
Nota: los operadores de negación lógica de prefijo y de postfijo que admiten valores NULL (§12.8.9), aunque se representan mediante el mismo token léxico (!), son distintos.
nota final
12.9.5 Operador de complemento bit a bit
Para una operación de la forma ~x, se aplica la resolución de sobrecarga de operador unario (sección 12.4.4) para seleccionar una implementación de operador específica. El operando se convierte al tipo de parámetro del operador seleccionado, y el tipo del resultado es el tipo de retorno del operador. Los operadores de complemento a nivel de 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 de enumeración E proporciona implícitamente el siguiente operador de complemento a nivel de bits:
E operator ~(E x);
El resultado de evaluar ~x, donde X es una expresión de un tipo de enumeración E con un tipo subyacente U, es exactamente el mismo que evaluar (E)(~(U)x), salvo que la conversión a E se realiza siempre como si fuera en un contexto unchecked (sección 12.8.20).
Las formas elevadas (§12.4.8) de los operadores de complemento bit a bit predefinidos sin elevar definidos anteriormente también están predefinidas.
12.9.6 Index from-end (Operador desde el extremo)
El operador unario ^ se denomina operador index from-end (que se conoce coloquialmente como operador hat). Este operador no es sobrecargable (§12.4.3) y hay un único operador predefinido:
Index operator ^(int x);
El resultado de una operación del formulario ^x es un valor from-end Index (§18.2) equivalente al resultado de la expresión:
new Index(x, true)
Al igual que con los demás unary_expressions, el operando puede tener un tipo de tiempo de compilación de dynamic (§12.9.1) y estar enlazado dinámicamente (§12.3.3). El tipo en tiempo de compilación del resultado siempre Indexes .
También se predefini un formulario elevado (§12.4.8) del operador from-end del índice.
12.9.7 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 decremento de prefijo será una expresión clasificada como variable, acceso a una propiedad o acceso a un indexador. El resultado de la operación tiene un valor del mismo tipo que el operando.
Si el operando de una operación de incremento o disminución de prefijo es un acceso de propiedad o indexador, la propiedad o el indexador deberán tener tanto un descriptor de acceso get como un descriptor de acceso set. Si no es así, se produce un error en tiempo de enlace.
La resolución de sobrecarga de operador unario (sección 12.4.4) se aplica para seleccionar una implementación de operador específica. Existen operadores de ++ y -- predefinidos para los siguientes tipos: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimaly cualquier tipo de enumeración. Los operadores predefinidos ++ devuelven el valor producido al sumar 1 al operando, y los operadores predefinidos -- devuelven el valor producido al restar 1 del operando. En un contexto checked, si el resultado de esta suma o resta está fuera del rango del tipo de resultado y el tipo de resultado es un tipo entero o un tipo de enumeración, se lanza una System.OverflowException.
Debe haber una conversión implícita del tipo de retorno del operador unario seleccionado al tipo de la unary_expression; de lo contrario, se produce un error de compilación.
El procesamiento en tiempo de ejecución de una operación de incremento o decremento de prefijo de la forma ++x o --x consta de los siguientes pasos:
- Si
xse clasifica como variable:-
xse evalúa para obtener la variable. - El valor de
xse convierte al tipo de operando del operador seleccionado y se invoca al operador con este valor como argumento. - El valor devuelto por el operador se convierte al tipo de
x. El valor resultante se almacena en la ubicación dada por la evaluación dexy se convierte en el resultado de la operación.
-
- Si
xse clasifica como acceso a una propiedad o indexador:- La expresión de instancia (si
xno esstatic) y la lista de argumentos (sixes un acceso indexador) asociada a se evalúan conx, y los resultados se utilizan en las invocaciones subsiguientes del descriptor de acceso get y set. - Se invoca al descriptor de acceso get de
x. - El valor devuelto por el descriptor de acceso get se convierte al tipo de operando del operador seleccionado y el operador se invoca con este valor como argumento.
- El valor devuelto por el operador se convierte al tipo de
x. El accesor set dexse invoca con este valor como argumento. - Este valor también se convierte en el resultado de la operación.
- La expresión de instancia (si
Los operadores ++ y -- también admiten la notación postfija (sección 12.8.16). El resultado de x++ o x-- es el valor de x antes de la operación, mientras que el resultado de ++x o --x es el valor de x después de la operación. En cualquier caso, x tiene el mismo valor después de la operación.
Se puede invocar la implementación de un operador ++ o un operador -- mediante notación de prefijo o posfijo. No es posible tener implementaciones de operadores separadas para las dos notaciones.
Las formas elevadas (§12.4.8) de los operadores de incremento y disminución predefinidos sin elevar también están predefinidas.
12.9.8 Expresiones cast
Una cast_expression se utiliza para convertir explícitamente una expresión a un tipo dado.
cast_expression
: '(' type ')' unary_expression
;
Una cast_expression con el formato (T)E, donde T es un tipo y E es una unary_expression, realiza una conversión explícita (§10.3) del valor de E al tipo T. Si no existe una conversión explícita de E a T, se produce un error de vinculación. En caso contrario, el resultado es el valor producido por la conversión explícita. El resultado siempre se clasifica como un valor, incluso si E denota una variable.
La gramática para una cast_expression conduce a ciertas ambigüedades sintácticas.
Ejemplo: La expresión
(x)–ypodría interpretarse como una cast_expression (una conversión del tipo–ya tipox) o como una additive_expression combinada con una parenthesized_expression (que calcula el valorx – y). ejemplo final
Para resolver las ambigüedades de cast_expression, existe la siguiente regla: Una secuencia de uno o más tokens (sección 6.4) encerrada entre paréntesis se considera el comienzo de una cast_expression solo si al menos una de las siguientes condiciones es cierta:
- La secuencia de tokens es gramática correcta para un tipo, pero no para una expresión.
- La secuencia de tokens es una gramática correcta para un tipo, y el token que sigue 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), exceptoasyis.
El término "gramática correcta" anterior solo significa que la secuencia de tokens se ajustará a la producción gramatical particular. En concreto, no tiene en cuenta el significado real de ninguno de los identificadores constituyentes.
Ejemplo: Si
xyyson identificadores, entoncesx.yes una gramática correcta para un tipo, aunquex.yen realidad no denote un tipo. ejemplo final
Nota: de la regla de desambiguación se deduce que, si
xyyson identificadores,(x)y,(x)(y)y(x)(-y)son cast_expressions, pero(x)-yno, aunquexidentifique un tipo. Sin embargo, sixes una palabra clave que identifica un tipo predefinido (comoint), entonces todas las cuatro formas son cast_expressions (porque tal palabra clave no podría ser posiblemente una expresión por sí misma). nota final
12.9.9 Expresiones await
12.9.9.1 General
El operador await 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.14). Dentro de la función asincrónica de cierre más cercana, no se producirá una await_expression en estos lugares:
- Dentro de una función anónima anidada (no asíncrona)
- Dentro del bloque de una lock_statement
- En una conversión de función anónima a un tipo de árbol de expresión (sección 10.7.3)
- En un contexto inseguro
Nota: no se puede producir una await_expression en la mayoría de los lugares de una query_expression, ya que se transforman sintácticamente para usar expresiones lambda no asincrónicas. nota final
Dentro de una función asíncrona, no se usará await como available_identifier aunque se pueda usar el identificador textual @await. Por lo tanto, no hay ambigüedad sintáctica entre await_expressions y varias expresiones que implican identificadores. Fuera de las funciones asíncronas, await actúa como un identificador normal.
El operando de una await_expression se denomina tarea . Representa una operación asincrónica que podría o no estar completa en el momento en que se evalúa el await_expression . El propósito del operador await es suspender la ejecución de la función asíncrona adjunta hasta que la tarea esperada se complete, y luego obtener su resultado.
12.9.9.2 Expresiones awaitables
Es necesario que la tarea de una await_expressionadmita await. Una expresión t es esperable si se cumple una de las siguientes condiciones:
-
tes del tipodynamicen tiempo de compilación -
ttiene un método de instancia o extensión accesible llamadoGetAwaiterque no tiene parámetros ni parámetros de tipo, y un tipo de retornoApara el cual se cumplen todas las siguientes condiciones:-
Aimplementa la interfazSystem.Runtime.CompilerServices.INotifyCompletion(en adelante conocido comoINotifyCompletionpara simplificar). -
Atiene una propiedad de instancia accesible y legibleIsCompletedde tipobool -
Atiene un método de instancia accesibleGetResultsin parámetros y sin parámetros de tipo
-
El propósito del método GetAwaiter es obtener un elemento await para la tarea. El tipo A se denomina tipo awaiter para la expresión await.
El propósito de la propiedad IsCompleted es determinar si la tarea ya se ha completado. En caso afirmativo, no es necesario suspender la evaluación.
La finalidad del método INotifyCompletion.OnCompleted es dar de alta una "continuación" a la tarea; es decir, un delegado (de tipo System.Action) que será invocado una vez finalizada la tarea.
La finalidad del método GetResult es obtener el resultado de la tarea una vez finalizada. Este resultado puede ser una finalización correcta, posiblemente con un valor de resultado, o puede ser una excepción lanzada por el método GetResult.
12.9.9.3 Clasificación de expresiones await
La expresión await t se clasifica del mismo modo que la expresión (t).GetAwaiter().GetResult(). Así, si el tipo de devolución de GetResult es void, la await_expression se clasifica como nada. Si tiene un tipo de retorno novoidT, el await_expression se clasifica como un valor de tipo T.
12.9.9.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:
- Se obtiene un elemento await
aal evaluar la expresión(t).GetAwaiter(). - Se obtiene un
boolbevaluando la expresión(a).IsCompleted. - Si
besfalse, la evaluación depende de siaimplementa la interfazSystem.Runtime.CompilerServices.ICriticalNotifyCompletion(a partir de ahora conocida comoICriticalNotifyCompletionpor brevedad). Esta comprobación se realiza en tiempo de enlace; esto es, en tiempo de ejecución siatiene el tipo en tiempo de compilacióndynamic; y en tiempo de compilación de lo contrario. Searel delegado de reanudación (§15.14):- Si
ano implementaICriticalNotifyCompletion, se evalúa la expresión((a) as INotifyCompletion).OnCompleted(r). - Si
aimplementaICriticalNotifyCompletion, se evalúa la expresión((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r). - La evaluación se suspende y el control se devuelve al invocador actual de la función asíncrona.
- Si
- Ya sea inmediatamente después (si
bfuetrue) o tras la invocación posterior del delegado de reanudación (sibfuefalse), se evalúa la expresión(a).GetResult(). Si devuelve un valor, ese valor es el resultado de la await_expression. De lo contrario, el resultado será nulo.
La implementación que un elemento await haga de los métodos de interfaz INotifyCompletion.OnCompleted y ICriticalNotifyCompletion.UnsafeOnCompleted debe lograr que el delegado r se invoque como máximo una vez. En caso contrario, el comportamiento de la función asíncrona es indefinido.
Operador range 12.10
El .. operador se denomina operador range .
range_expression
: unary_expression
| unary_expression? '..' unary_expression?
;
El operador de intervalo predefinido es:
Range operator ..(Index x, Index y);
El operador range no es sobrecargable (§12.4.3).
Todas las expresiones de intervalo se tratan como tener el formato x..y, donde:
-
xes el operando izquierdo si está presente; de lo contrario, la expresión0; y -
yes el operando derecho si está presente; de lo contrario, la expresión^0.
El resultado de la operación es un Range valor (§18.3) equivalente al resultado de la expresión:
new Range(x, y)
Si uno o ambos operandos de una expresión de intervalo tienen el tipo dynamicen tiempo de compilación , la expresión se enlaza dinámicamente (§12.3.3). El tipo en tiempo de compilación del resultado siempre Rangees .
También se predefini una forma levantada (§12.4.8) del operador de intervalo.
El operador de intervalo no es asociativo (§12.4.2).
12.11 Expresión switch
Un switch_expression proporciona switchsemántica similar a en un contexto de expresión.
switch_expression
: range_expression
| switch_expression 'switch' '{' switch_expression_arms? '}'
;
switch_expression_arms
: switch_expression_arm (',' switch_expression_arm)* ','?
;
switch_expression_arm
: pattern case_guard? '=>' switch_expression_arm_expression
;
switch_expression_arm_expression
: expression
;
Hay una conversión de expresión switch (§10.2.18) de una expresión switch a un tipo T si hay una conversión implícita de cada switch_expression_arm_expression de cada una de las switch_expression_armde la expresión switch a T.
Si una expresión switch no está sujeta a una conversión de expresión switch,
- El tipo de la switch_expression es el mejor tipo común §12.6.3.16) de los switch_expression_arm_expressions de la switch_expression_arms, si existe este tipo, y cada switch_expression_arm_expression se puede convertir implícitamente a ese tipo.
- Se trata de un error si no existe este tipo.
Se trata de un error si algún patrón de switch_expression_arm no puede afectar al resultado porque algún patrón anterior y la protección siempre coincidirán.
Se dice que una expresión switch es exhaustiva si cada valor de su entrada se controla mediante al menos un brazo de la expresión switch. El compilador generará una advertencia si una expresión switch no es exhaustiva.
En tiempo de ejecución, el resultado del switch_expression es el valor de la expresión del primer switch_expression_arm para el que la expresión del lado izquierdo de la switch_expression coincide con el patrón del switch_expression_arm y para la que el case_guard del switch_expression_arm, si está presente, se evalúa como true. Si no hay ningún switch_expression_arm de este tipo, el switch_expression inicia una instancia de la excepción System.InvalidOperationException (o una clase derivada de eso).
Ejemplo: A continuación se convierten los valores de una enumeración que representa las instrucciones visuales de un mapa en línea a las direcciones cardinales correspondientes:
static Orientation ToOrientation(Direction direction) => direction switch { Direction.Up => Orientation.North, Direction.Right => Orientation.East, Direction.Down => Orientation.South, Direction.Left => Orientation.West, _ => throw new ArgumentOutOfRangeException(direction.ToString()), }; public enum Direction { Up, Down, Right, Left } public enum Orientation { North, South, East, West }ejemplo final
12.12 Operadores aritméticos
12.12.1 General
Los operadores *, /, %, + y - y se denominan operadores aritméticos.
multiplicative_expression
: switch_expression
| multiplicative_expression '*' switch_expression
| multiplicative_expression '/' switch_expression
| multiplicative_expression '%' switch_expression
;
additive_expression
: multiplicative_expression
| additive_expression '+' multiplicative_expression
| additive_expression '-' multiplicative_expression
;
Si un operando de un operador aritmético tiene el tipo en tiempo de compilación dynamic, entonces la expresión está ligada dinámicamente (sección 12.3.3). En este caso, el tipo en tiempo de compilación de la expresión es dynamic, y la resolución descrita a continuación tendrá lugar en tiempo de ejecución utilizando el tipo en tiempo de ejecución de aquellos operandos que tengan el tipo en tiempo de compilación dynamic.
Operador de multiplicación 12.12.2
Para una operación de la forma x * y, se aplica la resolución de sobrecarga de operador binaria (sección 12.4.5) para seleccionar una implementación de operador específica. Los operandos se convierten a los tipos de parámetros del operador seleccionado, y el tipo del resultado es el tipo de retorno del operador.
A continuación se enumeran los operadores de multiplicación predefinidos. Todos los operadores calculan 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 contexto de
checked, si el producto está fuera del intervalo del tipo de resultado, se lanza unaSystem.OverflowException. En un contexto deunchecked, los desbordamientos no se notifican y se descartan los bits importantes de orden superior fuera del rango del tipo de resultado.Multiplicación en coma flotante:
float operator *(float x, float y); double operator *(double x, double y);El producto se calcula según las reglas de la aritmética IEC 60559. La siguiente tabla enumera los resultados de todas las combinaciones posibles de valores finitos distintos de cero, ceros, infinitos y NaNs. En la tabla,
xeyson valores finitos positivos.zes el resultado dex * y, redondeado al valor representable más cercano. Si la magnitud del resultado es demasiado grande para el tipo de destino,zes infinito. Debido al redondeo,zpuede ser cero aunquexniysea cero.+y-y+0-0+∞-∞NaN+x+z-z+0-0+∞-∞NaN-x-z+z-0+0-∞+∞NaN+0+0-0+0-0NaNNaNNaN-0-0+0-0+0NaNNaNNaN+∞+∞-∞NaNNaN+∞-∞NaN-∞-∞+∞NaNNaN-∞+∞NaNNaNNaNNaNNaNNaNNaNNaNNaN(Excepto cuando se indique lo contrario, en las tablas de punto flotante de §12.12.2–§12.12.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 representarla en el formato decimal, se lanza un
System.OverflowException. 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 es equivalente a utilizar el operador de multiplicación de tipoSystem.Decimal.
Las formas elevadas (§12.4.8) de los operadores de multiplicación predefinidos no elevados definidos anteriormente.
Operador de división 12.12.3
Para una operación de la forma x / y, se aplica la resolución de sobrecarga de operador binaria (sección 12.4.5) para seleccionar una implementación de operador específica. Los operandos se convierten a los tipos de parámetros del operador seleccionado, y el tipo del resultado es el tipo de retorno del operador.
A continuación se enumeran los operadores de división predefinidos. Todos los operadores calculan el cociente de x y y.
División entera:
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 lanza una
System.DivideByZeroException.La división redondea el resultado hacia cero. Así, el valor absoluto del resultado es el mayor número entero posible 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 valor mínimo representable de
intolongy el operando derecho es–1, se produce un desbordamiento. En un contexto dechecked, esto provoca la generación de unaSystem.ArithmeticException(o una subclase de esta). En un contexto deunchecked, la implementación define si se lanza unaSystem.ArithmeticException(o una subclase de esta) o si el desbordamiento no se notifica, lo que hace que el valor sea 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 la aritmética IEC 60559. La siguiente tabla enumera los resultados de todas las combinaciones posibles de valores finitos distintos de cero, ceros, infinitos y NaNs. En la tabla,
xeyson valores finitos positivos.zes el resultado dex / y, redondeado al valor representable más cercano.+y-y+0-0+∞-∞NaN+x+z-z+∞-∞+0-0NaN-x-z+z-∞+∞-0+0NaN+0+0-0NaNNaN+0-0NaN-0-0+0NaNNaN-0+0NaN+∞+∞-∞+∞-∞NaNNaNNaN-∞-∞+∞-∞+∞NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNDivisión decimal:
decimal operator /(decimal x, decimal y);Si el valor del operando derecho es cero, se lanza una
System.DivideByZeroException. Si la magnitud del valor resultante es demasiado grande para representarla en el formato decimal, se lanza unSystem.OverflowException. 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 preservará un resultado igual al resultado exacto. La escala preferida es la escala dexmenos la escala dey.La división decimal es equivalente a utilizar el operador de división de tipo
System.Decimal.
Las formas elevadas (§12.4.8) de los operadores de división predefinidos sin elevar definidos anteriormente también están predefinidas.
Operador de resto 12.12.4
Para una operación de la forma x % y, se aplica la resolución de sobrecarga de operador binaria (sección 12.4.5) para seleccionar una implementación de operador específica. Los operandos se convierten a los tipos de parámetros del operador seleccionado, y el tipo del resultado es el tipo de retorno del operador.
A continuación se enumeran los operadores de resto predefinidos. 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 % yes el valor producido porx – (x / y) * y. Siyes cero, se lanza unSystem.DivideByZeroException.Si el operando izquierdo es el valor más pequeño de
intolongy el operando derecho es–1, se produce unaSystem.OverflowExceptionsolo six / yproduciría una excepción.Resto en coma flotante:
float operator %(float x, float y); double operator %(double x, double y);La siguiente tabla enumera los resultados de todas las combinaciones posibles de valores finitos distintos de cero, ceros, infinitos y NaNs. En la tabla,
xeyson valores finitos positivos.zes el resultado dex % yy se calcula comox – n * y, donde n es el mayor número entero posible menor o igual quex / y. Este método de cálculo del resto es análogo al utilizado para los operandos enteros, pero difiere de la definición de la norma IEC 60559 (en la quenes el entero más próximo ax / y).+y-y+0-0+∞-∞NaN+x+z+zNaNNaN+x+xNaN-x-z-zNaNNaN-x-xNaN+0+0+0NaNNaN+0+0NaN-0-0-0NaNNaN-0-0NaN+∞NaNNaNNaNNaNNaNNaNNaN-∞NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNResto decimal:
decimal operator %(decimal x, decimal y);Si el valor del operando derecho es cero, se lanza una
System.DivideByZeroException. Queda a criterio de la implementación cuándo se lanza unaSystem.ArithmeticException(o una subclase de esta). Una implementación conforme no lanzará una excepción parax % yen ningún caso en el quex / yno lance una excepción. La escala del resultado, antes de cualquier redondeo, es la mayor de las escalas de los dos operandos, y el signo del resultado, si es distinto de cero, es el mismo que el dex.El resto decimal es equivalente a usar el operador de resto del tipo
System.Decimal.Nota: estas reglas garantizan que para todos los tipos, el resultado nunca tenga el signo opuesto del operando izquierdo. nota final
Las formas elevadas (§12.4.8) de los operadores de resto predefinidos sin elevar definidos anteriormente también están predefinidas.
Operador de suma 12.12.5
Para una operación de la forma x + y, se aplica la resolución de sobrecarga de operador binaria (sección 12.4.5) para seleccionar una implementación de operador específica. Los operandos se convierten a los tipos de parámetros del operador seleccionado, y el tipo del resultado es el tipo de retorno 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 números 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 contexto de
checked, si la suma se encuentra fuera del rango del tipo de resultado, se lanza unSystem.OverflowException. En un contexto deunchecked, los desbordamientos no se notifican y se descartan los bits importantes de orden superior fuera del rango del tipo de resultado.Suma en coma flotante:
float operator +(float x, float y); double operator +(double x, double y);La suma se calcula según las reglas de la aritmética IEC 60559. La siguiente tabla enumera los resultados de todas las combinaciones posibles de valores finitos distintos de cero, ceros, infinitos y NaNs. En la tabla,
xeyson valores finitos distintos de cero yzes el resultado dex + y. Sixeytienen la misma magnitud pero signos opuestos,zes cero positivo. Six + yes demasiado grande para representarlo en el tipo de destino,zes un infinito con el mismo signo quex + y.y+0-0+∞-∞NaNxzxx+∞-∞NaN+0y+0+0+∞–∞NaN-0y+0-0+∞-∞NaN+∞+∞+∞+∞+∞NaNNaN-∞-∞-∞-∞NaN-∞NaNNaNNaNNaNNaNNaNNaNNaNSuma decimal:
decimal operator +(decimal x, decimal y);Si la magnitud del valor resultante es demasiado grande para representarla en el formato decimal, se lanza un
System.OverflowException. La escala del resultado, antes de cualquier redondeo, es la mayor entre las escalas de los dos operandos.La suma decimal es equivalente a utilizar el operador de suma de tipo
System.Decimal.Suma de enumeración. Cada tipo de enumeración proporciona implícitamente los siguientes operadores predefinidos, donde
Ees el tipo de enumeración, yUes el tipo subyacente deE: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 la concatenación de cadenas esnull, se sustituye por una cadena vacía. En caso contrario, cualquier no operandostringse convierte a su representación de cadena invocando el método virtualToStringheredado del tipoobject. SiToStringdevuelvenull, se sustituye por 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 mostrada en los comentarios es el resultado típico en un sistema inglés estadounidense. El resultado exacto puede depender de la configuración regional del entorno de ejecución. El operador de concatenación de cadenas se comporta de la misma manera en todos los casos, pero los métodos
ToStringllamados implícitamente durante la ejecución pueden verse afectados por la configuración regional.ejemplo final
El resultado del operador de concatenación de cadenas es un
stringque consta de los caracteres del operando izquierdo seguidos de los caracteres del operando derecho. El operador de concatenación de cadenas nunca devuelve un valornull. Se puede producir un errorSystem.OutOfMemoryExceptionsi 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
Des 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 este también seanull). En caso contrario, si el segundo operando esnull, el resultado de la operación es el valor del primer operando. En caso contrario, el resultado de la operación es una nueva instancia de delegado cuya lista de invocación está formada por los elementos de la lista de invocación del primer operando, seguidos de los elementos de la lista de invocación del segundo operando. Es decir, la lista de invocaciones del delegado resultante es la concatenación de las listas de invocaciones de los dos operandos.Nota: Para obtener ejemplos de combinación de delegados, consulte §12.12.6 y §21.6. Dado
System.Delegateque no es un tipo delegado, el operador + no está definido para él. nota final
Las formas elevadas (§12.4.8) de los operadores de suma predefinidos sin elevar definidos anteriormente también están predefinidas.
Operador de resta 12.12.6
Para una operación de la forma x – y, se aplica la resolución de sobrecarga de operador binaria (sección 12.4.5) para seleccionar una implementación de operador específica. Los operandos se convierten a los tipos de parámetros del operador seleccionado, y el tipo del resultado es el tipo de retorno del operador.
A continuación se enumeran los operadores de resta predefinidos. Todos los operadores restan y a x.
Resta de números enteros:
int operator –(int x, int y); uint operator –(uint x, uint y); long operator –(long x, long y); ulong operator –(ulong x, ulong yEn un contexto de
checked, si la diferencia está fuera del intervalo del tipo de resultado, se lanza unaSystem.OverflowException. En un contexto deunchecked, los desbordamientos no se notifican y se descartan los bits importantes de orden superior fuera del rango 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 la aritmética IEC 60559. La siguiente tabla enumera los resultados de todas las combinaciones posibles de valores finitos distintos de cero, ceros, infinitos y NaNs. En la tabla,
xeyson valores finitos distintos de cero yzes el resultado dex – y. Sixeyson iguales,zes cero positivo. Six – yes demasiado grande para representarlo en el tipo de destino,zes un infinito con el mismo signo quex – y.y+0-0+∞-∞NaNxzxx-∞+∞NaN+0-y+0+0-∞+∞NaN-0-y-0+0-∞+∞NaN+∞+∞+∞+∞NaN+∞NaN-∞-∞-∞-∞-∞NaNNaNNaNNaNNaNNaNNaNNaNNaN(En la tabla anterior, las entradas
-ydenotan la negación dey, no que el valor sea negativo).Resta decimal:
decimal operator –(decimal x, decimal y);Si la magnitud del valor resultante es demasiado grande para representarla en el formato decimal, se lanza un
System.OverflowException. La escala del resultado, antes de cualquier redondeo, es la mayor entre las escalas de los dos operandos.La resta decimal es equivalente a utilizar 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
Ees el tipo de enumeraciónU, y es el tipo subyacente deE: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 dexyy, 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, obteniendo un valor de la enumeración.Eliminación de delegados. Cada tipo de delegado proporciona implícitamente el siguiente operador predefinido, donde
Des 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 esnull. - En caso 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 (§21.2).
- Si las listas comparan igual, según lo determinado por el operador de igualdad de delegado (§12.14.9), el resultado de la operación es
null. - En caso contrario, el resultado de la operación es una nueva lista de invocación formada por la lista del primer operando de la que se han eliminado las entradas del segundo operando, siempre que la lista del segundo operando sea una sublista de la del primero. (Para determinar la igualdad de la sublista, las entradas correspondientes se comparan como con el operador de igualdad del delegado.) Si la lista del segundo operando coincide con varias sublistas de entradas contiguas de la lista del primer operando, se quita la última sublista coincidente de entradas contiguas.
- De lo contrario, el resultado de la operación es el valor del operando izquierdo.
- Si las listas comparan igual, según lo determinado por el operador de igualdad de delegado (§12.14.9), el resultado de la operación es
Ninguna de las listas de los operandos (si las hay) se modifica 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
- Si el primer operando es
Las formas elevadas (§12.4.8) de los operadores de resta predefinidos sin elevar definidos anteriormente también están predefinidas.
12.13 Operadores de desplazamiento
Los operadores << y >> se utilizan 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 una shift_expression tiene el tipo en tiempo de compilación dynamic, 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 dynamic, y la resolución descrita a continuación tendrá lugar en tiempo de ejecución utilizando el tipo en tiempo de ejecución de aquellos operandos que tengan el tipo en tiempo de compilación dynamic.
Para una operación de la forma x << count o x >> count, se aplica la resolución de sobrecarga de operadores binarios (sección 12.4.5) para seleccionar una implementación de operador específica. Los operandos se convierten a los tipos de parámetros del operador seleccionado, y el tipo del resultado es el tipo de retorno del operador.
Al declarar un operador de desplazamiento sobrecargado, el tipo del primer operando será siempre la clase o estructura que contiene la declaración del operador, y el tipo del segundo operando será siempre int.
A continuación se enumeran los operadores de desplazamiento predefinidos.
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
<<desplazaxa la izquierda por un número de bits calculados como se describe a continuación.Los bits de orden superior fuera del rango del tipo de resultado de
xse descartan, los bits restantes se desplazan a la izquierda, y las posiciones de bits vacíos de orden inferior se ponen a 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
>>desplazaxa la derecha por un número de bits calculado como se describe a continuación.Cuando
xes del tipointolong, los bits de bajo orden dexse descartan, los bits restantes se desplazan a la derecha y las posiciones de bits vacíos de alto orden se ponen a cero sixes no negativo y a uno sixes negativo.Cuando
xes de tipouintoulong, los bits de orden inferior dexse descartan, los bits restantes se desplazan a la derecha y las posiciones de bits vacíos de orden superior se ponen a cero.
Para los operadores predefinidos, el número de bits a desplazar se calcula de la siguiente manera:
- Cuando el tipo de
xesintouint, el recuento de desplazamientos lo proporcionan los cinco bits de orden bajo decount. En otras palabras, el número de bits a desplazar se calcula a partir decount & 0x1F. - Cuando el tipo de
xeslongoulong, el recuento de desplazamientos lo proporcionan los seis bits de orden bajo decount. En otras palabras, el número de bits a desplazar se calcula a partir decount & 0x3F.
Si el recuento de desplazamientos resultante es cero, los operadores de desplazamiento simplemente devuelven el valor de x.
Las operaciones de desplazamiento nunca provocan desbordamientos y generan los mismos resultados en contextos controlados y no controlados.
Cuando el operando izquierdo del operador >> es de un tipo entero con signo, 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 a la inferida del tipo de operando, se pueden usar conversiones explícitas.
Ejemplo: Si
xes una variable de tipoint, la operaciónunchecked ((int)((uint)x >> y))realiza un desplazamiento lógico a la derecha dex. ejemplo final
Las formas elevadas (§12.4.8) de los operadores de desplazamiento predefinidos sin elevar definidos anteriormente también están predefinidas.
12.14 Operadores relacionales y de pruebas de tipos
12.14.1 General
Los operadores ==, !=, <, >, <=, >=, is y as, y se denominan operadores relacionales y de comprobación de tipo.
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 operador
isdebe 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 expresión, la expresión de patrón debe tener prioridad al menos tan alta como shift_expression. nota final
Nota: Hay una ambigüedad gramatical entre el tipo y constant_pattern en un
relational_expressionen el lado derecho deis; puede ser un análisis válido de un identificador completo. En tal caso, solo si no se puede enlazar como un tipo (por compatibilidad con versiones anteriores del lenguaje), se resuelve como lo primero que se encuentra (que debe ser una constante o un tipo). Esta ambigüedad solo está presente en el lado derecho de dicha expresión.
El is operador se describe en §12.14.12 y el as operador se describe en §12.14.13.
Los operadores ==, !=, <, >, <= y >= y son operadores de comparación.
Si se utiliza un default_literal (§12.8.21) como operando de un operador <, >, <= o >=, se produce un error en tiempo de compilación.
Si se utiliza un default_literal como ambos operandos de un operador == o !=, se produce un error en tiempo de compilación. Si se utiliza un default_literal como operando izquierdo del operador is o as, se produce un error de compilación.
Si un operando de un operador de comparación tiene el tipo en tiempo de compilación dynamic, entonces la expresión está ligada dinámicamente (sección 12.3.3). En este caso, el tipo en tiempo de compilación de la expresión es dynamic, y la resolución descrita a continuación tendrá lugar en tiempo de ejecución utilizando el tipo en tiempo de ejecución de aquellos operandos que tengan el tipo en tiempo de compilación dynamic.
Para una operación de la forma x «op» y, donde "op" es un operador de comparación, se aplica la resolución de sobrecarga (sección 12.4.5) para seleccionar una implementación específica del operador. Los operandos se convierten a los tipos de parámetros del operador seleccionado, y el tipo del resultado es el tipo de retorno del operador. Si ambos operandos de una equality_expression son el literal null, no se realiza la resolución de sobrecarga y la expresión se evalúa como un valor constante de true o false, dependiendo de si el operador es == o !=.
Los operadores de comparación predefinidos se describen en las siguientes subcláusulas. Todos los operadores de comparación predefinidos devuelven un resultado de tipo bool, como se describe en la siguiente tabla.
| Operación | Resultado |
|---|---|
x == y |
true si x es igual a y, false en caso contrario |
x != y |
truesi x no es igual a y, en caso contrario es 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.14.2 Operadores de comparación de 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 valor bool que indica si la relación particular es true o false.
Las formas elevadas (§12.4.8) de los operadores de comparación de enteros predefinidos sin elevar definidos anteriormente también están predefinidas.
12.14.3 Operadores de comparación de punto flotante
Los operadores de comparación de coma 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 de la norma IEC 60559:
Si cualquiera de los operandos es NaN, el resultado es false para todos los operadores excepto !=, para el cual el resultado es true. Para dos operandos cualesquiera, 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
xyyes NaN, entoncesx < yesfalse, pero!(x >= y)estrue. ejemplo final
Cuando ninguno de los operandos es NaN, los operadores comparan los valores de los dos operandos en coma 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ño y más grande que pueden representarse en el formato de coma flotante dado. 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 elevadas (§12.4.8) de los operadores de comparación de punto flotante predefinidos sin elevar definidos anteriormente también están predefinidas.
12.14.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 valor bool que indica si la relación concreta es true o false. Cada comparación decimal equivale a utilizar el correspondiente operador relacional o de igualdad de tipo System.Decimal.
Las formas elevadas (§12.4.8) de los operadores de comparación de decimal predefinidos sin elevar definidos anteriormente también están predefinidas.
12.14.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 ambos x y y son true o si ambos x y y son false. De lo contrario, el resultado es false.
El resultado de != es false si ambos x y 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 != produce el mismo resultado que el operador ^.
Las formas elevadas (§12.4.8) de los operadores de igualdad booleanos predefinidos sin elevar definidos anteriormente también están predefinidas.
12.14.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 de enumeración E con un tipo subyacente U, y «op» es uno de los operadores de comparación, es exactamente el mismo que evaluar ((U)x) «op» ((U)y). En otras palabras, los operadores de comparación de tipo enumeración simplemente comparan los valores integrales subyacentes de los dos operandos.
Las formas elevadas (§12.4.8) de los operadores de comparación de enumeración predefinidos sin elevar definidos anteriormente también están predefinidas.
12.14.7 Operadores de igualdad de tipos de referencia
Cada tipo de clase C proporciona implícitamente los siguientes operadores de igualdad predefinidos de tipo referencia:
bool operator ==(C x, C y);
bool operator !=(C x, C y);
a menos que existan operadores de igualdad previamente definidos para C (por ejemplo, cuando C es string o System.Delegate).
Los operadores devuelven el resultado de comparar las dos referencias para igualdad o no igualdad.
operator == devuelve true si y solo si x y y se refieren a la misma instancia o son ambos null, mientras operator != que devuelve true si y solo si operator == con los mismos operandos devolvería false.
Además de las reglas de aplicabilidad normales (sección 12.6.4.2), los operadores de igualdad de tipos de referencia predefinidos requieren una de las siguientes condiciones para ser aplicables:
- Ambos operandos son un valor de un tipo conocido como reference_type o el literal
null. Además, existe una conversión de identidad o de referencia explícita (§10.3.5) desde cualquiera de los operandos al tipo del otro operando. - Un operando es el literal
null, y el otro operando es un valor de tipoTdondeTes 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
Tes un tipo de valor no anulable, el resultado de==esfalsey el resultado de!=estrue. - Si en tiempo de ejecución
Tes un tipo de valor que acepta valores NULL, el resultado se calcula a partir de laHasValuepropiedad del operando, como se describe en (§12.14.10). - Si en tiempo de ejecución
Tes un tipo de referencia, el resultado estruesi el operando esnullyfalsede lo contrario.
- Si en tiempo de ejecución
A menos que una de estas condiciones sea cierta, se produce un error de vinculación.
Nota: son notables las implicaciones de estas reglas:
- Es un error durante el tiempo de enlace usar los operadores predefinidos de igualdad de tipos de referencia para comparar dos referencias que se sabe que son diferentes durante el tiempo de enlace. Por ejemplo, si los tipos en tiempo de enlace de los operandos son dos tipos de clase, y si ninguno deriva del otro, entonces sería imposible que los dos operandos hicieran referencia al mismo objeto. Por lo tanto, la operación se considera un error de vinculación.
- 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, lo cual se maneja de manera especial).- Los operandos de los operadores predefinidos de igualdad de tipos de referencia nunca se encapsulan. No tendría sentido realizar tales operaciones de empaquetamiento, ya que las referencias a las nuevas instancias empaquetadas necesariamente diferirían de todas las demás referencias.
Para una operación de la forma
x == yox != y, si existe algún operadoroperator ==ooperator !=definido por el usuario aplicable, las reglas de resolución de sobrecarga de operadores (sección 12.4.5) seleccionarán ese operador en lugar del operador de igualdad de tipo de referencia predefinido. Siempre es posible seleccionar el operador de igualdad de tipo de referencia predefinido mediante la conversión explícita de uno o ambos operandos al tipoobject.nota final
Ejemplo: En el siguiente ejemplo se verifica si un argumento de un parámetro de tipo no restringido es
null.class C<T> { void F(T x) { if (x == null) { throw new ArgumentNullException(); } ... } }La construcción
x == nullestá permitida aunqueTpueda representar un tipo de valor no anulable, y el resultado se define simplemente comofalsecuandoTes un tipo de valor no anulable.ejemplo final
Para una operación de la forma x == y o x != y, si existe cualquier operator == o operator != aplicable, las reglas de resolución de sobrecarga de operadores (sección 12.4.5) seleccionarán ese operador en lugar del operador de igualdad de tipo de referencia predefinido.
Nota: Siempre es posible seleccionar el operador de igualdad de tipo 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 FalseLas variables
sytse refieren a dos instancias de cadena distintas que contienen los mismos caracteres. La primera comparación generaTrueporque el operador de igualdad de cadenas predefinido (§12.14.8) se selecciona cuando ambos operandos son de tipostring. Todas las comparaciones restantes producenFalseporque la sobrecarga deoperator ==en el tipo destringno es aplicable cuando alguno de los operandos tiene un tipoobjectde tiempo de enlace.Tenga en cuenta que la técnica anterior no tiene sentido 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); } }genera
Falseporque las conversiones crean referencias a dos instancias independientes de valores encapsulados deint.ejemplo final
12.14.8 Operadores de igualdad de cadenas
Los operadores de igualdad de cadena predefinidos son:
bool operator ==(string x, string y);
bool operator !=(string x, string y);
Dos valores string se consideran iguales cuando se cumple una de las siguientes condiciones:
- Ambos valores son
null. - Ambos valores no son referencias
nulla 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 cadenas en lugar de referencias de cadenas. Cuando dos instancias de cadena separadas 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.14.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.14.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 de la siguiente manera:
- Si cualquiera de las instancias de delegado es
null, son iguales y solo si ambas sonnull. - Si los delegados tienen tipos de tiempo de ejecución diferentes, nunca son iguales.
- Si ambas instancias del delegado tienen una lista de invocación (§21.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 reglas siguientes determinan la igualdad de las entradas en las listas de invocación:
- Si dos entradas de la lista de invocación hacen referencia al mismo método estático, las entradas son iguales.
- Si dos entradas de la lista de invocación se refieren al mismo método no estático en el mismo objeto de destino (como se define 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.21) 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 §21 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 comparaciones que nunca pueden considerar valores no
nullcomo iguales, ya que son referencias a instancias de diferentes tipos de delegados. nota final
12.14.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 null, incluso si no existe ningún operador predefinido o definido por el usuario (en forma no elevada o elevada) para la operación.
Para una operación de uno de los formatos
x == null null == x x != null null != x
donde x es una expresión de un tipo de valor anulable, si la resolución de sobrecarga del operador (§12.4.5) no encuentra un operador aplicable, el resultado se calcula en su lugar a partir de la propiedad HasValue de x. En concreto, las dos primeras formas se convierten en !x.HasValue y las dos últimas en x.HasValue.
12.14.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 uno de los operandos x y 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 para tuplas .
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 deben tener la misma aridad; de lo contrario, 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 bool o un tipo que defina los operadores true y false.
El operador de igualdad de tupla x == y se evalúa de la siguiente manera:
- Se evalúa el operando del lado izquierdo
x. - Se evalúa el operando del lado derecho,
y. - Para cada par de elementos
xiyyien orden léxico:- Se evalúa el operador
xi == yiy se obtiene un resultado de tipoboolde la siguiente manera:- Si el resultado de la comparación es
bool, este es el resultado. - En caso contrario, si el resultado de la comparación es un
dynamic, el operadorfalsese invoca dinámicamente sobre él, y el valorboolresultante se niega con el operador lógico de negación (!). - En caso contrario, si el tipo de la comparación tiene una conversión implícita a
bool, se aplica dicha conversión. - En caso contrario, si el tipo de la comparación tiene un operador
false, se invoca dicho operador y el valorboolresultante se niega con el operador lógico de negación (!).
- Si el resultado de la comparación es
- Si el
boolresultante esfalse, no se realiza ninguna evaluación adicional y el resultado del operador de igualdad en tuplas esfalse.
- Se evalúa el operador
- Si todas las comparaciones de elementos arrojaron
true, el resultado del operador de igualdad de la tupla estrue.
El operador de igualdad de tupla x != y se evalúa de la siguiente manera:
- Se evalúa el operando del lado izquierdo
x. - Se evalúa el operando del lado derecho,
y. - Para cada par de elementos
xiyyien orden léxico:- Se evalúa el operador
xi != yiy se obtiene un resultado de tipoboolde la siguiente manera:- Si el resultado de la comparación es
bool, este es el resultado. - Si el resultado de la comparación es
dynamic, se invoca dinámicamente el operadortruey el valor resultantebooles el resultado. - En caso contrario, si el tipo de la comparación tiene una conversión implícita a
bool, se aplica dicha conversión. - De lo contrario, si el tipo de la comparación tiene un operador
true, ese operador se invoca y el valor resultantebooles el resultado.
- Si el resultado de la comparación es
- Si el
boolresultante estrue, no se realiza ninguna evaluación adicional y el resultado del operador de igualdad en tuplas estrue.
- Se evalúa el operador
- Si todas las comparaciones de elementos arrojaron
false, el resultado del operador de igualdad de la tupla esfalse.
12.14.12 El operador is
Existen dos formas de operador is. Uno de ellos 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.14.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 de dynamic, es un valor booleano que indica si E no es nulo y puede convertirse correctamente al tipo T mediante una conversión de referencia, una conversión boxing, una conversión de desencapsulado, una conversión de ajuste o una conversión de desencapsulado.
La operación E is T se evalúa de la siguiente manera:
- Si
Ees una función anónima o un grupo de métodos, se produce un error en tiempo de compilación. - Si
Tes un tipo de referencia que acepta valores NULL (§8.9.3), se produce un error en tiempo de compilación. - Si
Ees el literalnull, o si el valor deEesnull, el resultado esfalse. - De lo contrario:
- Sea
Rel tipo en tiempo de ejecución deE. - Vamos
Da derivar deRlo siguiente:- Si
Res un tipo de valor que acepta valores nulos,Des el tipo subyacente deR. - En caso contrario,
DesR.
- Si
- El resultado depende
Dde yTde la manera siguiente:- Si
Tes un tipo de referencia, el resultado estruesi:- Existe una conversión de identidad entre
DyT, o -
Des un tipo de referencia y existe una conversión de referencia implícita deDaT, o -
Des un tipo de valor y una conversión boxing deDaTexiste.
- Existe una conversión de identidad entre
- Si
Tes un tipo de valor que acepta valores NULL, el resultado estruesiDes el tipo subyacente deT. - Si
Tes un tipo de valor no anulable, el resultado estruesiDyTson del mismo tipo. - De lo contrario, el resultado es
false.
- Si
- Sea
El operador is no tiene en cuenta las conversiones definidas por el usuario.
Nota: Como el operador
isse evalúa en tiempo de ejecución, todos los argumentos de tipo han sido sustituidos y no hay tipos abiertos (sección 8.4.3) a considerar. nota final
Nota: El operador
ispuede entenderse en términos de tipos en tiempo de compilación y conversiones como sigue, dondeCes el tipo en tiempo de compilación deE:
- Si el tipo en tiempo de compilación de
ees el mismo que el deT, o si existe una conversión de referencia implícita (§10.2.8), una conversión de caja (§10.2.9), una conversión de envoltura (§10.6) o una conversión de desenvoltura explícita (§10.6) del tipo en tiempo de compilación deEaT:
- Si
Ces de un tipo de valor no nulo, el resultado de la operación estrue.- En caso contrario, el resultado de la operación equivale a evaluar
E != null.- De lo contrario, si existe una conversión de referencia explícita (§10.3.5) o conversión unboxing (§10.3.7) de
CaTo siCoTes 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, ajuste o desajuste de
Eal tipoTy el resultado de la operación esfalse. Un compilador puede implementar optimizaciones basadas en el tipo en tiempo de compilación.nota final
12.14.12.2 El operador is-pattern
El operador is-pattern se utiliza para comprobar si el valor calculado por una expresión coincide con un patrón dado (sección 11). La comprobación se realiza en tiempo de ejecución. El resultado del operador is-pattern es verdadero si el valor coincide con el patrón; en caso contrario, es falso.
Para una expresión de la forma E is P, donde E es una expresión relacional de tipo T y es un patrón P, es un error en tiempo de compilación si se cumple alguna de las siguientes condiciones:
-
Eno designa un valor o no tiene un tipo. - El patrón
Pno es aplicable (sección 11.2) al tipoT.
Cada single_variable_designation del patrón introduce una nueva variable local que definitivamente se asigna (§9.4) cuando el relational_expression correspondiente comprueba true.
12.14.13 Operador as
El operador as se usa para convertir explícitamente un valor en un tipo de referencia determinado o un tipo de valor anulable. A diferencia de una expresión de conversión (§12.9.8), el as operador nunca produce una excepción. En cambio, si la conversión indicada no es posible, el valor resultante es null.
En una operación de la forma E as T, E será una expresión y T será un tipo de referencia, un parámetro de tipo conocido por ser un tipo de referencia o un tipo de valor anulable. Además, al menos uno de los siguientes debe ser verdadero o, de lo contrario, habrá un error en tiempo de compilación.
- Una conversión de identidad (§10.2.2), que admite valores NULL implícita (§10.2.6), referencia implícita (§10.2.8), boxing (§10.2.9), que admite valores NULL explícita (§10.3.4), referencia explícita (§10.3.5) o de ajuste (§8.3.12) existe de
EaT. - El tipo de
EoTes un tipo abierto. -
Ees el literalnull.
Si el tipo en tiempo de compilación de E no es dynamic, la operación E as T produce el mismo resultado que
E is T ? (T)(E) : (T)null
salvo que E solo se evalúa una vez. Se puede esperar que un compilador optimice E as T para realizar como máximo una comprobación de tipo en tiempo de ejecución en lugar de las dos comprobaciones de tipo en tiempo de ejecución que implica la expansión anterior.
Si el tipo de tiempo de compilación de E es dynamic, a diferencia del operador de conversión, el operador as no está enlazado dinámicamente (§12.3.3). 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 operador as y, en su lugar, deben realizarse mediante expresiones de casting.
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 } }El parámetro de tipo
TdeGse conoce como un tipo de referencia porque tiene la restricción de clase. Sin embargo, el parámetro de tipoUdeHno es así; por lo tanto, no se permite el uso del operadorasenH.ejemplo final
12.15 Operadores lógicos
12.15.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 en tiempo de compilación dynamic, entonces la expresión está ligada dinámicamente (sección 12.3.3). En este caso, el tipo en tiempo de compilación de la expresión es dynamic, y la resolución descrita a continuación tendrá lugar en tiempo de ejecución utilizando el tipo en tiempo de ejecución de aquellos operandos que tengan el tipo en tiempo de compilación dynamic.
Para una operación de la forma x «op» y, donde "op" es uno de los operadores lógicos, se aplica la resolución de sobrecarga (sección 12.4.5) para seleccionar una implementación específica del operador. Los operandos se convierten a los tipos de parámetros del operador seleccionado, y el tipo del resultado es el tipo de retorno del operador.
Los operadores lógicos predefinidos se describen en las siguientes subcláusulas.
12.15.2 Operadores lógicos enteros
Los operadores lógicos de 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 por bits de los dos operandos, el operador | calcula el OR lógico por bits de los dos operandos y el operador ^ calcula el OR lógico exclusivo por bits de los dos operandos. No es posible que estas operaciones se desborden.
Las formas elevadas (§12.4.8) de los operadores lógicos de enteros predefinidos sin elevar definidos anteriormente también están predefinidas.
12.15.3 Operadores lógicos de enumeración
Cada tipo de enumeración E 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 de enumeración E con un tipo subyacente U, y «op» es uno de los operadores lógicos, es exactamente el mismo que el de evaluar (E)((U)x «op» (U)y). En otras palabras, los operadores lógicos de tipo enumeración simplemente realizan la operación lógica sobre el tipo subyacente de los dos operandos.
Las formas elevadas (§12.4.8) de los operadores lógicos de enumeración predefinidos sin elevar definidos anteriormente también están predefinidas.
12.15.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 x es y o es true. De lo contrario, el resultado es false.
El resultado de x ^ y es true si x es true y y es false, 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.15.5 Boolean y | Operadores
El tipo booleano anulable bool? puede representar tres valores, true, false y null.
Al igual que con los otros operadores binarios, las formas levantadas de los operadores & lógicos y | (§12.15.4) también se definen previamente:
bool? operator &(bool? x, bool? y);
bool? operator |(bool? x, bool? y);
La semántica de los operadores levantados & 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 tipo
bool?es conceptualmente similar al tipo de tres valores utilizado para las expresiones booleanas en SQL. La tabla anterior sigue la misma semántica que SQL, sin embargo, aplicar las reglas de §12.4.8 a los operadores de&y|no seguiría la misma semántica. Las reglas de §12.4.8 ya proporcionan una semántica similar a la de SQL para el operador elevado de^. nota final
12.16 Operadores lógicos condicionales
12.16.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 && ycorresponde a la operaciónx & y, excepto queyserá evaluado solo sixno esfalse. - La operación
x || ycorresponde a la operaciónx | y, excepto queyserá evaluado solo sixno estrue.
Nota: La razón por la que el cortocircuitado utiliza las condiciones "not true" y "not false" es para permitir que los operadores condicionales definidos por el usuario establezcan cuándo se aplica el cortocircuitado. Los tipos definidos por el usuario podrían estar en un estado en el que
operator truedevuelvefalseyoperator falsedevuelvefalse. En esos casos, ni&&ni||cortocircuitarían. nota final
Si un operando de un operador lógico condicional tiene el tipo en tiempo de compilación dynamic, entonces la expresión está ligada dinámicamente (sección 12.3.3). En este caso, el tipo en tiempo de compilación de la expresión es dynamic, y la resolución descrita a continuación tendrá lugar en tiempo de ejecución utilizando el tipo en tiempo de ejecución de aquellos operandos que tengan el tipo en tiempo de compilación dynamic.
Una operación de la forma x && y o x || y se procesa aplicando la resolución de sobrecarga (sección 12.4.5) como si la operación estuviera escrita x & y o x | y. A continuación,
- Si la resolución de sobrecarga no encuentra un único operador óptimo, 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.15.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.15.4), la operación se procesa como se describe en §12.16.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.16.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 regulares, las sobrecargas de los operadores lógicos regulares se consideran también, con ciertas restricciones, sobrecargas de los operadores lógicos condicionales. Esto se describe más adelante en §12.16.3.
12.16.2 Operadores lógicos condicionales booleanos
Cuando los operandos de && o || son del tipo bool, o cuando los operandos son de tipos que no definen un operator & o operator | aplicable, pero sí definen conversiones implícitas a bool, la operación se procesa como sigue:
- La operación
x && yse evalúa comox ? y : false. En otras palabras, primero se evalúaxy se convierte al tipobool. Entonces, sixestrue,yse evalúa y se convierte al tipobool, y este se convierte en el resultado de la operación. En caso contrario, el resultado de la operación esfalse. - La operación
x || yse evalúa comox ? true : y. En otras palabras, primero se evalúaxy se convierte al tipobool. Entonces, sixestrue, el resultado de la operación estrue. En caso contrario,yse evalúa y se convierte a tipobool, y este se convierte en el resultado de la operación.
12.16.3 Operadores lógicos condicionales definidos por el usuario
Cuando los operandos de && o || son de tipos que declaran un operator & o operator |definido por el usuario aplicable, ambos de los siguientes deberán ser verdaderos, 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. En otras palabras, el operador calculará el AND lógico o el OR lógico de dos operandos de tipoT, y devolverá un resultado de tipoT. -
Tdebe contener declaraciones deoperator trueyoperator false.
Se produce un error en tiempo de vinculación si no se cumple alguno de estos requisitos. En caso contrario, la operación && o || se evalúa combinando el operador definido por el usuario operator true o operator false con el operador definido por el usuario seleccionado:
- La operación
x && yse evalúa comoT.false(x) ? x : T.&(x, y), dondeT.false(x)es una invocación deloperator falsedeclarado enT, yT.&(x, y)es una invocación deloperator &seleccionado. En otras palabras, primero se evalúaxy luego se invocaoperator falsesobre el resultado para determinar sixes definitivamente falso. Entonces, sixes definitivamente falso, el resultado de la operación es el valor calculado previamente parax. En caso contrario,yse evalúa y se invoca unoperator &seleccionado sobre el valor calculado previamente paraxy el valor calculado paraypara producir el resultado de la operación. - La operación
x || yse evalúa comoT.true(x) ? x : T.|(x, y), dondeT.true(x)es una invocación deloperator truedeclarado enT, yT.|(x, y)es una invocación deloperator |seleccionado. En otras palabras, primero se evalúaxy se invocaoperator truesobre el resultado para determinar sixes definitivamente verdadero. Entonces, sixes definitivamente cierto, el resultado de la operación es el valor previamente calculado parax. En caso contrario,yse evalúa y se invoca unoperator |seleccionado sobre el valor calculado previamente paraxy el valor calculado paraypara producir 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 se evalúa exactamente una vez.
12.17 Operador de fusión null
Al operador ?? se le llama el operador de uso combinado de NULL.
null_coalescing_expression
: conditional_or_expression
| conditional_or_expression '??' null_coalescing_expression
| throw_expression
;
En una expresión de combinado de NULL de la forma a ?? b, si a no es null, el resultado es a; en caso contrario, el resultado es b. La operación solo evalúa b si a es null.
El operador de coalecencia nula es asociativo hacia la derecha, lo que significa que las operaciones se agrupan de derecha a izquierda.
Ejemplo: una expresión de la forma
a ?? b ?? cse evalúa comoa ?? (b ?? c). En términos generales, una expresión de la formaE1 ?? E2 ?? ... ?? ENdevuelve el primero de los operandos que no esnullonullsi todos los operandos sonnull. 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₀, A o B, donde A es el tipo de a (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 valor anulable, o A en caso contrario. En concreto, a ?? b se procesa como sigue:
- Si
Aexiste y es un tipo no administrado (§8.8) o se sabe que es un tipo de valor que no acepta valores NULL, se produce un error en tiempo de compilación. - En caso contrario, si
Aexiste ybes una expresión dinámica, el tipo resultante esdynamic. En tiempo de ejecución, primero se evalúaa. Siano esnull,ase convierte adynamic, y este se convierte en el resultado. De lo contrario, se evalúaby este se convierte en el resultado. - En caso contrario, si
Aexiste y es un tipo de valor anulable y existe una conversión implícita debaA₀, el tipo de resultado esA₀. En tiempo de ejecución, primero se evalúaa. Siano esnull,ase desencapsula en el tipoA₀y se convierte en el resultado. De lo contrario,bse evalúa y se convierte al tipoA₀, y esto se convierte en el resultado. - De lo contrario, si
Aexiste y existe una conversión implícita debaA, el tipo resultante esA. En tiempo de ejecución, primero se evalúaa. Siano esnull,ase convierte en el resultado. De lo contrario,bse evalúa y se convierte al tipoA, y esto se convierte en el resultado. - De lo contrario, si
Aexiste y es un tipo de valor anulable,btiene un tipoBy existe una conversión implícita deA₀aB, el tipo de resultado esB. En tiempo de ejecución, primero se evalúaa. Siano esnull,ase desencapsula en el tipoA₀y se convierte en el tipoB, y esto se convierte en el resultado. De lo contrario,bse evalúa y se convierte en el resultado. - De lo contrario, si
btiene un tipoBy existe una conversión implícita deaaB, el tipo resultante esB. En tiempo de ejecución, primero se evalúaa. Siano esnull,ase convierte en el tipoB, y este se convierte en el resultado. De lo contrario,bse evalúa y se convierte en el resultado. - De lo contrario,
aybson incompatibles y se produce un error en tiempo de compilación.
Ejemplo:
T M<T>(T a, T b) => a ?? b; string s = M(null, "text"); int i = M(19, 23);El parámetro
Tde tipo para el métodoMestá sin restricciones. Por lo tanto, el argumento type puede ser un tipo de referencia o un tipo de valor nullable, como se muestra en la primera llamada aM. El argumento type también puede ser un tipo de valor no anulable, como se muestra en la segunda llamada aM. Cuando el argumento de tipo es un tipo de valor que no acepta valores NULL, el valor de la expresióna ?? besa.ejemplo final
12.18 Operador de expresión throw
throw_expression
: 'throw' null_coalescing_expression
;
Una throw_expression lanza el valor producido al evaluar la null_coalescing_expression. La expresión se convertirá implícitamente en System.Exception, y el resultado de evaluar la expresión se convierte en System.Exception antes de lanzarse. El comportamiento en tiempo de ejecución de la evaluación de una expresión throw es el mismo que está especificado para una declaración throw (§13.10.6).
Una throw_expression no tiene ningún tipo. Una throw_expression se puede convertir a cualquier tipo mediante una conversión implícita de throw.
Una expresión throw solo se producirá en los siguientes contextos sintácticos:
- De lo contrario, se evalúa y se convierte en el resultado (
?:). - Como segundo operando de un operador de fusión null (
??). - Como cuerpo de una expresión o miembro lambda.
12.19 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 simple de nombres no encontró una declaración asociada (sección 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 puede aparecer en los siguientes contextos sintácticos:
- Como
outargument_value en una argument_list. - Como descarte
_simple que comprende el lado izquierdo de una asignación simple (§12.23.2). - Como un tuple_element en una o varias tuple_expressions anidadas recursivamente, la más externa de las cuales compone el lado izquierdo de una asignación deconstruida. Una expresión de deconstrucción produce expresiones de declaración en esta posición, aunque estas no estén presentes de manera sintáctica.
Nota: esto significa que una expresión de declaración no se puede ir entre paréntesis. nota final
Es un error que una variable de tipo implícito declarada con una expresión de declaración sea referenciada dentro de la lista de argumentos donde se declara.
Es un error que se haga referencia a una variable declarada con una declaration_expression dentro de la asignación deconstrucción donde se produce.
Una 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 tipo, y el tipo de la variable local se infiere basándose en el contexto sintáctico como sigue:
- En una argument_list, el tipo inferido de la variable es el tipo declarado del parámetro correspondiente.
- Como en el lado izquierdo de una asignación simple, el tipo inferido de la variable es el tipo del lado derecho de la asignación.
- En una 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 de la asignación (después de la deconstrucció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 (sección 9.2.9.2), 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 variables locales 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
s1muestra expresiones de declaración explícita e implícitamente con tipo. El tipo inferido deb1esboolporque ese es el tipo del parámetro de salida correspondiente enM1. El siguienteWriteLinepuede acceder ai1yb1, que se han introducido en el ámbito contenedor.La declaración de
s2muestra un intento de usari2en la llamada anidada aM, que no se permite dado que la referencia ocurre dentro de la lista de argumentos donde se declarói2. Por otro lado, la referencia ab2en el argumento final está permitida, porque se produce después del final de la lista de argumentos anidada dondeb2se declaró.La declaración de
s3muestra el uso de expresiones de declaración implícita y explícitamente con tipo que se descartan. Debido a que los descartes no declaran una variable con nombre, las múltiples ocurrencias del identificador_están permitidas.(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 con tipo para variables y descartes en una asignación de deconstrucción. El simple_name
_es equivalente avar _cuando no se encuentra la declaración de_.void M1(out int i) { ... } void M2(string _) { M1(out _); // Error: `_` is a string M1(out var _); }Este ejemplo muestra el uso de
var _para proporcionar un descarte implícitamente tipado cuando_no está disponible, porque designa una variable en el ámbito envolvente.ejemplo final
Operador condicional 12.20
Este operador ?: se denomina operador condicional. A veces 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.18) en un operador condicional si ref está presente.
Una expresión condicional de la forma 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 tanto x y y.
El operador condicional es de asociación a la derecha, lo que significa que las operaciones se agrupan de derecha a izquierda.
Ejemplo: una expresión de la forma
a ? b : c ? d : ese evalúa comoa ? b : (c ? d : e). ejemplo final
El primer operando del operador ?: debe ser una expresión que pueda convertirse 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 de compilación.
Si ref está presente:
- Debe existir una conversión de identidad entre los tipos de las dos variable_reference y el tipo del resultado puede ser cualquiera de los dos. Si cualquiera de los dos tipos es
dynamic, se prefiere la inferencia de tiposdynamic(sección 8.7). Si cualquiera de los tipos es un tipo de tupla (§8.3.11), la inferencia de tipos incluye los nombres de los elementos cuando los nombres de los elementos en la misma posición ordinal son iguales en ambas tuplas. - El resultado es una referencia de variable, que es escribible si ambas variable_references son escribibles.
Nota: cuando
refestá presente, la conditional_expression devuelve una referencia de variable, que se puede asignar a una variable de referencia mediante el operador= refo pasar como parámetro de referencia/entrada/salida. nota final
Si ref no está presente, el segundo y tercer operandos, x y y, del operador ?: controlan el tipo de la expresión condicional:
- Si
xtiene tipoXyytiene tipoY,- Si existe una conversión de identidad entre
XyY, el resultado es el mejor tipo común de un conjunto de expresiones (§12.6.3.16). Si cualquiera de los dos tipos esdynamic, se prefiere la inferencia de tiposdynamic(sección 8.7). Si cualquiera de los tipos es un tipo de tupla (§8.3.11), la inferencia de tipos incluye los nombres de los elementos cuando los nombres de los elementos en la misma posición ordinal son iguales en ambas tuplas. - En caso contrario, si existe una conversión implícita (sección 10.2) de
XaY, pero no deYaX, entoncesYes el tipo de la expresión condicional. - En caso contrario, si existe una conversión implícita de enumeración (sección 10.2.4) de
XaY, entoncesYes el tipo de la expresión condicional. - En caso contrario, si existe una conversión implícita de enumeración (sección 10.2.4) de
YaX, entoncesXes el tipo de la expresión condicional. - En caso contrario, si existe una conversión implícita (sección 10.2) de
YaX, pero no deXaY, entoncesXes el tipo de la expresión condicional. - En caso contrario, no se puede determinar el tipo de la expresión y se produce un error de compilación.
- Si existe una conversión de identidad entre
- Si solo uno de
xyytiene un tipo, y ambosxyyson implícitamente convertibles a ese tipo, entonces ese es el tipo de la expresión condicional. - En caso contrario, no se puede determinar el tipo de la expresión y se produce un error de compilación.
El procesamiento en tiempo de ejecución de una expresión condicional ref de la forma b ? ref x : ref y consiste en los siguientes pasos:
- Primero,
bse evalúa, y se determina el valorbooldeb:- Si existe una conversión implícita del tipo de
babool, entonces se realiza esta conversión implícita para producir un valorbool. - De lo contrario, el
operator truedefinido por el tipo debse invoca para producir un valor debool.
- Si existe una conversión implícita del tipo de
- Si el valor
boolproducido por el paso anterior estrue, entoncesxse evalúa y la referencia variable resultante se convierte en el resultado de la expresión condicional. - De lo contrario, se evalúa
yy la referencia a la variable resultante se convierte en el resultado de la expresión condicional.
El procesamiento en tiempo de ejecución de una expresión condicional de la forma b ? x : y consiste en los siguientes pasos:
- Primero,
bse evalúa, y se determina el valorbooldeb:- Si existe una conversión implícita del tipo de
babool, entonces se realiza esta conversión implícita para producir un valorbool. - De lo contrario, el
operator truedefinido por el tipo debse invoca para producir un valor debool.
- Si existe una conversión implícita del tipo de
- Si el valor
boolproducido por el paso anterior estrue, entoncesxse evalúa y se convierte al tipo de la expresión condicional, y este se convierte en el resultado de la expresión condicional. - En caso contrario,
yse evalúa y se convierte al tipo de la expresión condicional, y este se convierte en el resultado de la expresión condicional.
12.21 Expresiones de función anónimas
12.21.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 sí misma, pero es convertible a un tipo de delegado o de árbol de expresiones 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 de tipo árbol de expresiones, la conversión se evalúa como un árbol de expresiones que representa la estructura del método como una estructura de objetos.
Nota: Por razones históricas, existen dos tipos sintácticos de funciones anónimas, a saber, lambda_expression y anonymous_method_expression. Para casi todos los propósitos, las lambda_expressions son más concisas y expresivas que las 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 un anonymous_function_body, si tanto la null_conditional_invocation_expression como la expresión son aplicables, se elegirá la primera.
Nota: El solapamiento de, y la prioridad entre, alternativas aquí es solo por conveniencia descriptiva; las reglas gramaticales podrían ser elaboradas para eliminar el solapamiento. ANTLR, y otros sistemas gramaticales, adoptan la misma conveniencia y así anonymous_function_body tiene la semántica especificada automáticamente. 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 deMesvoid(§12.8.13). Pero cuando se trata como una null_conditional_invocation_expression, se permite que el tipo de resultado pueda servoid. nota final
Ejemplo: el tipo de resultado de
List<T>.Reverseesvoid. En el código siguiente, el cuerpo de la expresión anónima es una 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.14.
Los parámetros de una función anónima en forma de lambda_expression pueden estar tipados explícita o implícitamente. En una lista de parámetros explícitamente tipada, el tipo de cada parámetro se indica explícitamente. En una lista de parámetros de tipado 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 a un tipo de delegado compatible o a un tipo de árbol de expresión, ese tipo proporciona los tipos de los parámetros (sección 10.7).
En una lambda_expression con un único parámetro de tipo implícito, pueden omitirse los paréntesis de la lista de parámetros. En otras palabras, una función anónima de la forma
( «param» ) => «expr»
puede abreviarse como
«param» => «expr»
La lista de parámetros de una función anónima en forma de anonymous_method_expression es opcional. Si se da, los parámetros se tipificarán explícitamente. Si no, la función anónima es convertible en un delegado con cualquier lista de parámetros que no contenga parámetros de salida.
Un cuerpo block de una función anónima siempre es accesible (§13.2).
Ejemplos: 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 omittedejemplo final
El comportamiento de las lambda_expression y las anonymous_method_expression es el mismo salvo en los siguientes puntos:
- anonymous_method_expressions permiten 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_expressions permiten que los tipos de parámetro se omitan e infieran, mientras que anonymous_method_expressions requieren que se indiquen explícitamente los tipos de parámetros.
- 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 las lambda_expressions tienen conversiones a tipos de árbol de expresión compatibles (§8.6).
12.21.2 Firmas de función anónimas
La anonymous_function_signature de una función anónima define los nombres y, opcionalmente, los tipos de los parámetros de la función anónima. El ámbito de los parámetros de la función anónima es el anonymous_function_body (sección 7.7). Junto con la lista de parámetros (si se da), el cuerpo del método anónimo constituye un espacio de declaración (sección 7.3). Por lo tanto, es un error de tiempo de compilación 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 que esté en el ámbito de anonymous_method_expression o lambda_expression.
Si una función anónima tiene una explicit_anonymous_function_signature, el conjunto de tipos de delegado y tipos de árbol de expresión compatibles se restringe a los que tienen los mismos tipos de parámetros y modificadores en el mismo orden (sección 10.7). A diferencia de las conversiones de grupo de métodos (§10.8), no se admite la contravariante de los tipos de parámetros de funciones anónimas. Si una función anónima no tiene una anonymous_function_signature, el conjunto de tipos de delegado y tipos de árbol de expresión compatibles se restringe 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. No obstante, una anonymous_function_signature puede ser compatible con un tipo de delegado cuya lista de parámetros contenga una matriz de parámetros.
Observe también que la conversión a un tipo de árbol de expresión, incluso si es compatible, puede fallar en tiempo de compilación (sección 8.6).
12.21.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 firma, puede convertirse en un tipo delegado o un tipo expresión con parámetros (sección 10.7), pero no se puede acceder a los parámetros en el cuerpo.
- Salvo los parámetros pasados por referencia especificados en la firma (si los hay) de la función anónima más cercana, es un error de compilación que el cuerpo acceda a un parámetro pasado por referencia.
- Salvo para los parámetros especificados en la declaración (si existe) de la función anónima de cierre más cercana, es un error en tiempo de compilación que el cuerpo acceda a un parámetro de tipo
ref struct. - Cuando el tipo de
thises un tipo struct, es un error de compilación que el cuerpo acceda athis. Esto es así tanto si el acceso es explícito (como enthis.x) como implícito (como enxdondexun miembro de instancia de la estructura). Esta regla simplemente prohíbe tal acceso y no afecta a si la búsqueda de miembros resulta en un miembro de la estructura. - El cuerpo tiene acceso a las variables externas (§12.21.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.21.7).
- Es un error en tiempo de compilación que el cuerpo contenga una instrucción
goto, una instrucciónbreako una instruccióncontinuecuyo destino esté fuera del cuerpo o dentro del cuerpo de una función anónima contenida. - Una instrucción
returnen el cuerpo devuelve el control de una invocación de la función anónima más cercana que la envuelve, no del miembro de la función contenedora.
No se especifica explícitamente si hay alguna forma 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 particular, un compilador puede elegir implementar una función anónima sintetizando uno o más métodos o tipos con nombre. Los nombres de tales elementos sintetizados deberán tener una forma reservada para uso del compilador (sección 6.4.3).
12.21.4 Resolución de sobrecarga
Las funciones anónimas de una lista de argumentos participan en la inferencia de tipos y en la resolución de sobrecargas. Consulte las reglas exactas en la sección 12.6.3 y sección 12.6.4.
Ejemplo: El siguiente ejemplo ilustra 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 clase
ItemList<T>tiene dos métodosSum. Cada uno toma un argumentoselector, que extrae el valor para sumar de un elemento de la lista. El valor extraído puede ser uninto undoubley la suma resultante es igualmente uninto undouble.Los métodos
Sumpodrían usarse, por ejemplo, para calcular sumas de una lista de líneas detalladas en un pedido.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 métodosSumson aplicables porque la función anónimad => d.UnitCountes compatible con ambosFunc<Detail,int>yFunc<Detail,double>. Sin embargo, la resolución de sobrecarga elige el primer método deSumporque la conversión aFunc<Detail,int>es mejor que la conversión aFunc<Detail,double>.En la segunda invocación de
orderDetails.Sum, solo es aplicable el segundo métodoSumporque la función anónimad => d.UnitPrice * d.UnitCountproduce un valor de tipodouble. Así, la resolución de sobrecargas elige el segundo métodoSumpara esa invocación.ejemplo final
12.21.5 Funciones anónimas y enlace dinámico
Una función anónima no puede ser receptora, argumento u operando de una operación ligada dinámicamente.
12.21.6 Variables externas
12.21.6.1 General
Cualquier variable local, parámetro de valor o matriz de parámetros cuyo ámbito incluya la 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 de valor y es una variable externa de cualquier función anónima contenida dentro del miembro de función.
12.21.6.2 Variables externas capturadas
Cuando una función anónima hace referencia a una variable externa, se dice que la variable externa es capturada por la función anónima. Normalmente, el tiempo de vida de una variable local se limita a la ejecución del bloque o sentencia a la que está asociada (sección 9.2.9.1). Sin embargo, la duración de una variable externa capturada se extiende al menos hasta que el delegado o el árbol de expresiones creado a partir de la función anónima sea elegible 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 local
xy la duración dexse extiende al menos hasta que el delegado devuelto deFsea apto para la recolección de elementos no utilizados. Dado que cada invocación de la función anónima opera sobre la misma instancia dex, la salida del ejemplo es:1 2 3ejemplo 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 (§24.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 (§24.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 puede estar expuesta simultáneamente a varios hilos de ejecución. nota final
12.21.6.3 Creación de instancias de variables locales
Una variable local se considera instanciada cuando la ejecución entra en el ámbito de la variable.
Ejemplo: Por ejemplo, cuando el siguiente método es invocado, la variable local
xes instanciada e inicializada tres veces (una por 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
xfuera del bucle resulta en una única instanciación dex.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 instanciación se hacen 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 5Sin embargo, cuando la declaración de
xse coloca 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 5Nótese que un compilador puede (pero no está obligado) a optimizar las tres instancias en una sola instancia de delegado (sección 10.7.2).
ejemplo final
Si un bucle for declara una variable de iteración, dicha variable se considera declarada fuera del bucle.
Ejemplo: Así, 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 produce la salida:
3 3 3ejemplo final
Es posible que los delegados de funciones anónimas compartan algunas variables capturadas pero tengan instancias separadas de otras.
Ejemplo: por ejemplo, si se cambia
Fastatic 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
xpero separan instancias deyy la salida es:1 1 2 1 3 1ejemplo final
Funciones anónimas separadas 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 local
x, y por lo tanto pueden "comunicarse" a través de esa variable. La salida del ejemplo es:5 10ejemplo final
12.21.7 Evaluación de expresiones de función anónimas
Una función anónima F siempre se convertirá a un tipo de delegado D o a un tipo de árbol de expresión E, ya sea directamente o mediante la ejecución de una expresión de creación de delegado new D(F). Esta conversión determina el resultado de la función anónima, como se describe en la sección 10.7.
Ejemplo de implementación 12.21.8
Esta subcláusula es informativa.
Esta subcláusula describe una posible implementación de conversiones de funciones anónimas en términos de otras construcciones de C#. La implementación aquí descrita se basa en los mismos principios utilizados por un compilador comercial de C#, pero no es en absoluto una implementación obligatoria, ni la única posible. Solo menciona brevemente las conversiones a árboles de expresión, ya que su semántica exacta queda fuera del ámbito de esta especificación.
El resto de esta subcláusula ofrece varios ejemplos de código que contiene funciones anónimas con diferentes características. Para cada ejemplo, se proporciona una traducción correspondiente a código que solo utiliza otras construcciones de C#. En los ejemplos, se asume que el identificador D representa el siguiente tipo de delegado:
public delegate void D();
La forma más simple de una función anónima es aquella 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 siguiente ejemplo, la función anónima hace referencia a 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);
}
}
El tiempo de vida de la variable local debe extenderse al menos al tiempo de vida del delegado de la función anónima. Esto se puede conseguir "elevando" la variable local a un campo de una clase generada por el compilador. La creación de instancias de la variable local (§12.21.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 además dos variables locales con tiempos de vida diferentes:
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 variables locales, de forma que las variables locales de los distintos bloques puedan tener tiempos de vida independientes. Una instancia de __Locals2, la clase generada por el compilador para el bloque interno, contiene la variable local z 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 local y y un campo que hace referencia a this del 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 __Local2, y el código de la función anónima puede implementarse así 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 aplicada aquí para capturar variables locales puede utilizarse también al convertir funciones anónimas en árboles de expresión: las referencias a los objetos generados por el compilador pueden almacenarse en el árbol de expresión, y el acceso a las variables locales puede representarse como accesos a campos de 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.22 Expresiones de consulta
12.22.1 General
Las expresiones de consulta proporcionan una sintaxis integrada en el lenguaje para consultas que es similar 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_clause* select_or_group_clause query_continuation?
;
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 cláusula from y termina con una cláusula select o group. La cláusula from inicial puede ir seguida de cero o más from, let, where, join o orderby cláusulas. Cada cláusula from es un generador que introduce una variable de rango que recorre los elementos de una secuencia. Cada cláusula let introduce una variable de rango que representa un valor calculado mediante variables de rango anteriores. Cada cláusula where es un filtro que excluye elementos del resultado. Cada cláusula join compara claves especificadas de la secuencia de origen con claves de otra secuencia, obteniendo pares coincidentes. La cláusula orderby final o especifica la forma del resultado en términos de las variables de rango. La cláusula final select o group especifica la forma del resultado en términos de las variables de rango. Por último, se puede usar una cláusula into para “empalmar” consultas, tratando los resultados de una consulta como un generador en una consulta posterior.
12.22.2 Ambigüedades en expresiones de consulta
Las expresiones de consulta utilizan una serie de palabras clave contextuales (sección 6.4.4): ascending, by, descending, equals, from, group, into, join, let, on, orderby, select y where.
Para evitar las ambigüedades que podrían surgir del uso de estos identificadores tanto como palabras clave como simples nombres, estos identificadores se consideran palabras clave en cualquier lugar dentro de una expresión de consulta, a menos que lleven el prefijo "@" (sección 6.4.4), en cuyo caso se consideran identificadores. A estos efectos, una expresión de consulta es cualquier expresión que empiece por "fromidentificador" seguida de cualquier token excepto “;”, “=” o “,”.
12.22.3 Traducción de expresiones de consulta
12.22.3.1 General
El lenguaje C# no especifica la semántica de ejecución de las 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.22.4). En concreto, las expresiones de consulta se traducen en invocaciones a métodos denominados Where, Select, SelectMany, Join, GroupJoin, OrderBy, OrderByDescending, ThenBy, ThenByDescending, GroupBy y Cast. Se espera que estos métodos tengan firmas y tipos de valor devuelto concretos, como se describe en §12.22.4. Estos métodos pueden ser métodos de instancia del objeto consultado o métodos de extensión 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étodos es un mapeo sintáctico que ocurre antes de que se haya realizado cualquier vinculación de tipos o resolución de sobrecargas. Tras la traducción de las expresiones de la consulta, las invocaciones a métodos resultantes se procesan como invocaciones a métodos normales, lo que a su vez puede revelar errores en tiempo de compilación. Estas condiciones de error incluyen, entre otras, métodos que no existen, argumentos de tipos erróneos y métodos genéricos en los que falla la inferencia de tipos.
Una expresión de consulta se procesa aplicando repetidamente las siguientes traducciones hasta que no es posible realizar más reducciones. Las traducciones se enumeran por orden de aplicación: cada sección asume que las traducciones de las secciones precedentes se han realizado exhaustivamente, y una vez agotadas, una sección no se volverá a visitar posteriormente en el procesamiento de la misma expresión de consulta.
Es un error en tiempo de compilación que una expresión de consulta incluya una asignación a una variable de rango o el uso de una variable de rango como argumento para un parámetro de referencia o de salida.
Ciertas traducciones inyectan variables de rango con identificadores transparentes denotados por *. Estos se describen más adelante en §12.22.3.8.
12.22.3.2 Expresiones de consulta con continuaciones
Expresión de consulta con una continuación del cuerpo de la consulta.
from «x1» in «e1» «b1» into «x2» «b2»
se traduce por
from «x2» in ( from «x1» in «e1» «b1» ) «b2»
Las traducciones de las secciones siguientes suponen que las consultas no tienen continuaciones.
Ejemplo: El ejemplo:
from c in customers group c by c.Country into g select new { Country = g.Key, CustCount = g.Count() }se traduce por:
from g in (from c in customers group c by c.Country) select new { Country = g.Key, CustCount = g.Count() }cuya traducción final es:
customers. GroupBy(c => c.Country). Select(g => new { Country = g.Key, CustCount = g.Count() })ejemplo final
12.22.3.3 Tipos de variables de rango explícitos
Una cláusula from que especifica explícitamente un tipo de variable de rango
from «T» «x» in «e»
se traduce por
from «x» in ( «e» ) . Cast < «T» > ( )
Una cláusula join que especifica explícitamente un tipo de variable de rango
join «T» «x» in «e» on «k1» equals «k2»
se traduce por
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 rango explícitos.
Ejemplo: el ejemplo
from Customer c in customers where c.City == "London" select cse traduce por
from c in (customers).Cast<Customer>() where c.City == "London" select ccuya traducción final es
customers. Cast<Customer>(). Where(c => c.City == "London")ejemplo final
Nota: Los tipos de variable de rango explícitos son útiles para consultar colecciones que implementan la interfaz no genérica
IEnumerable, pero no la interfaz genéricaIEnumerable<T>. En el ejemplo anterior, este sería el caso si los clientes fueran de tipoArrayList. nota final
12.22.3.4 Degenerar expresiones de consulta
Una expresión de consulta del formulario
from «x» in «e» select «x»
se traduce por
( «e» ) . Select ( «x» => «x» )
Ejemplo: el ejemplo
from c in customers select cse traduce por
(customers).Select(c => c)ejemplo final
Una expresión de consulta degenerada es aquella que selecciona trivialmente los elementos de la fuente.
Nota: Las fases posteriores de la traducción (§12.22.3.6 y §12.22.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 sea el propio objeto fuente. De lo contrario, la devolución del resultado de una consulta de este tipo podría exponer inadvertidamente datos privados (por ejemplo, una matriz de elementos) a la persona que llama. Por lo tanto, este paso protege las consultas degeneradas escritas directamente en el código fuente llamando explícitamente a
Selecten el código fuente. Por tanto, corresponde a los implementadores deSelecty otros operadores de consulta garantizar que estos métodos nunca devuelvan el objeto fuente. nota final
12.22.3.5 Desde, let, where, join y orderby cláusulas
Una expresión de consulta con una segunda cláusula seguida from de una cláusula select
from «x1» in «e1»
from «x2» in «e2»
select «v»
se traduce por
( «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 por
(customers). SelectMany(c => c.Orders, (c,o) => new { c.Name, o.OrderID, o.Total } )ejemplo final
Una expresión de consulta con una segunda cláusula from seguida de un cuerpo de consulta Q 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 por
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 por
from * in (customers). SelectMany(c => c.Orders, (c,o) => new { c, o }) orderby o.Total descending select new { c.Name, o.OrderID, o.Total }cuya traducción final 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
xes un identificador generado por el compilador que, de lo contrario, es invisible e inaccesible.ejemplo final
La expresión let junto con su cláusula from anterior:
from «x» in «e»
let «y» = «f»
...
se traduce por
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 por
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 }cuya traducción final 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
xes un identificador generado por el compilador que, de lo contrario, es invisible e inaccesible.ejemplo final
La expresión where junto con su cláusula from anterior:
from «x» in «e»
where «f»
...
se traduce por
from «x» in ( «e» ) . Where ( «x» => «f» )
...
Una cláusula join inmediatamente seguida de una cláusula select
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2»
select «v»
se traduce por
( «e1» ) . Join( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «x2» ) => «v» )
Ejemplo: el ejemplo
from c in customers join o in orders on c.CustomerID equals o.CustomerID select new { c.Name, o.OrderDate, o.Total }se traduce por
(customers).Join( orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c.Name, o.OrderDate, o.Total })ejemplo final
Una cláusula join seguida de una cláusula de cuerpo de consulta:
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2»
...
se traduce por
from * in ( «e1» ) . Join(
«e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «x2» ) => new { «x1» , «x2» })
...
Una cláusula join-into inmediatamente seguida de una cláusula select
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2» into «g»
select «v»
se traduce por
( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «g» ) => «v» )
Una cláusula join into 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 por
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 por
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 }cuya traducción final 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
xyyson identificadores generados por el compilador que, de otro modo, serían invisibles e inaccesibles.ejemplo final
Una cláusula orderby y su cláusula precedente from:
from «x» in «e»
orderby «k1» , «k2» , ... , «kn»
...
se traduce por
from «x» in ( «e» ) .
OrderBy ( «x» => «k1» ) .
ThenBy ( «x» => «k2» ) .
... .
ThenBy ( «x» => «kn» )
...
Si una cláusula ordering especifica un indicador de dirección descendente, se produce en su lugar una invocación de OrderByDescending o ThenByDescending.
Ejemplo: el ejemplo
from o in orders orderby o.Customer.Name, o.Total descending select otiene la traducción definitiva
(orders) .OrderBy(o => o.Customer.Name) .ThenByDescending(o => o.Total)ejemplo final
En las traducciones siguientes se supone que no hay cláusulas let, where, join o orderby, y no más de una cláusula inicial from en cada expresión de consulta.
12.22.3.6 Cláusulas Select
Una expresión de consulta del formulario
from «x» in «e» select «v»
se traduce por
( «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 cse traduce simplemente en
(customers).Where(c => c.City == "London")ejemplo final
12.22.3.7 Cláusulas group
Una cláusula group
from «x» in «e» group «v» by «k»
se traduce por
( «e» ) . GroupBy ( «x» => «k» , «x» => «v» )
excepto cuando «v» es el identificador «x», la traducción es simplemente
( «e» ) . GroupBy ( «x» => «k» )
Ejemplo: el ejemplo
from c in customers group c.Name by c.Countryse traduce por
(customers).GroupBy(c => c.Country, c => c.Name)ejemplo final
12.22.3.8 Identificadores transparentes
Algunas traducciones insertan variables de rango con identificador transparenteindicado por *. Los identificadores transparentes existen solo como un paso intermedio en el proceso de traducción de una expresión de consulta.
Cuando una traducción de consulta inyecta un identificador transparente, otros pasos de traducción propagan el identificador transparente a funciones anónimas e inicializadores de objetos anónimos. En esos contextos, los identificadores transparentes tienen el siguiente comportamiento:
- Cuando un identificador transparente aparece como parámetro en una función anónima, los miembros del tipo anónimo asociado están 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 lo están.
- Cuando un identificador transparente aparece como 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 introducen junto con tipos anónimos, con la intención de capturar múltiples variables de ámbito como miembros de un único objeto. Una implementación de C# puede utilizar un mecanismo diferente a los tipos anónimos para agrupar múltiples variables de rango. Los siguientes ejemplos de traducción asumen que se utilizan tipos anónimos, y muestran 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 por
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 ademá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
xes un identificador generado por el compilador que, de lo contrario, es invisible e 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 por
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 })cuya traducción final 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
xyyson identificadores generados por el compilador que, de otro modo, serían invisibles e inaccesibles. ejemplo final
12.22.4 El patrón query-expression
El patrón Query-expression establece un patrón de métodos que los tipos pueden implementar para soportar expresiones de consulta.
Un tipo genérico C<T> admite el patrón de expresión de consulta si sus métodos de miembro públicos y los métodos de extensión públicamente accesibles pueden ser reemplazados por la siguiente definición de clase. Los miembros y los métodos de extensión accesibles se conocen como la "forma" de un tipo C<T>genérico . Se utiliza un tipo genérico para ilustrar las relaciones adecuadas entre los tipos de parámetros y de retorno, 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 utilizan los tipos de delegado genéricos Func<T1, R> y Func<T1, T2, R>, pero podrían haber utilizado igualmente otros tipos de delegado o expression-tree con las mismas relaciones en los tipos parámetro y devolución.
Nota: La relación recomendada entre
C<T>yO<T>que asegura que los métodosThenByyThenByDescendingestán disponibles solo en el resultado de un métodoOrderByoOrderByDescending. nota final
Nota: La forma recomendada del resultado de
GroupBy—una secuencia de secuencias, donde cada secuencia interior tiene una propiedad adicionalKey—. nota final
Nota: Dado que las expresiones de consulta se traducen a invocaciones de métodos mediante un mapeo sintáctico, los tipos tienen una flexibilidad considerable a la hora de implementar alguno o todos los métodos del patrón de expresiones de consulta. Por ejemplo, los métodos del patrón pueden implementarse como métodos de instancia o como métodos de extensión porque ambos tienen la misma sintaxis de invocación, y los métodos pueden solicitar delegados o árboles de expresiones porque las funciones anónimas son convertibles a ambos. Los tipos que implementan solo algunos patrones de expresión de consulta únicamente admiten traducciones de expresiones que coinciden con los métodos que ese tipo admite. nota final
Nota: El espacio de nombres
System.Linqproporciona una implementación del patrón de expresión de consulta para cualquier tipo que implemente la interfazSystem.Collections.Generic.IEnumerable<T>. nota final
12.23 Operadores de asignación
12.23.1 General
Todos los operadores de asignación, excepto uno, asignan un nuevo valor a una variable, una propiedad, un evento o un elemento indexador. La excepción, = ref, asigna una referencia de variable (sección 9.5) a una variable de referencia (sección 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 que se clasifique como una variable o, excepto para = ref, un acceso a una propiedad, un acceso de indexador, un acceso a un evento o una tupla. Una expresión de declaración no se permite directamente como operando izquierdo, pero puede ocurrir como un paso en la evaluación de una asignación deconstruida.
El operador = se denomina operador de asignación simple. Asigna el valor o valores del operando derecho a la variable, propiedad, elemento indexador o elementos de tupla dados 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.23.2.
Este operador = ref se denomina operador de asignación ref. Convierte al operando derecho en un variable_reference (§9.5), que será el referente de la variable de referencia designada por el operando izquierdo. El operador de asignación ref se describe en §12.23.3.
Los operadores de asignación distintos del = operador y = ref se denominan operadores de asignación compuestos. Estos operadores se procesan de la siguiente manera:
- Para el operador
??=, se evalúa el operando derecho y el resultado se asigna al elemento variable, propiedad o indexador dado por el operando izquierdo, solo si el valor del operando izquierdo esnull. - De lo contrario, la operación indicada se realiza en los dos operandos y, a continuación, el valor resultante se asigna a la variable, propiedad o elemento de indexador proporcionado por el operando izquierdo. Los operadores de asignación compuesta se describen en §12.23.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.23.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 de la forma
a = b = cse evalúa comoa = (b = c). ejemplo final
12.23.2 Asignación simple
El operador = se denomina operador de asignación simple.
Si el operando izquierdo de una asignación simple está en la forma E.P o E[Ei] donde E tiene el tipo de tiempo de compilación dynamic, entonces la asignación se enlaza dinámicamente (§12.3.3). En este caso, el tipo en tiempo de compilación de la expresión de asignación es dynamic y la resolución descrita a continuación se realizará en tiempo de ejecución en función del tipo de tiempo de ejecución de E. Si el operando izquierdo es de la forma E[Ei] en la que al menos un elemento Ei tiene el tipo en tiempo de compilación dynamic, y el tipo en tiempo de compilación de E no es un array, el acceso al indexador resultante está ligado dinámicamente, pero con una comprobación limitada en tiempo de compilación (sección 12.6.5).
Una asignación sencilla en la que el operando izquierdo se clasifica como una tupla también se denomina asignación de deconstrucción. Si alguno de los elementos de la tupla del operando izquierdo tiene un nombre de elemento, se produce un error de compilación. Si alguno de los elementos de tupla del operando izquierdo es una declaration_expression y cualquier otro elemento no es una declaration_expression o un descarte simple, ocurre un error en tiempo de compilación.
El tipo de una asignación simple x = y es el tipo de una asignación a x de y, que se determina recursivamente como sigue:
- Si
xes una expresión de tupla(x1, ..., xn), yypuede descomponerse en una expresión de tupla(y1, ..., yn)con elementosn(sección 12.7), y cada asignación axideyitiene el tipoTi, entonces la asignación tiene el tipo(T1, ..., Tn). - En caso contrario, si
xse clasifica como variable, la variable no esreadonly,xtiene un tipoT, yytiene una conversión implícita aT, entonces la asignación tiene el tipoT. - De lo contrario, si
xse clasifica como una variable implícitamente tipada (es decir, una expresión de declaración implícitamente tipada) yytiene un tipoT, entonces el tipo inferido de la variable esT, y la asignación tiene el tipoT. - En caso contrario, si
xse clasifica como un acceso a una propiedad o a un indexador, la propiedad o el indexador tiene un descriptor de acceso de conjunto accesible,xtiene un tipoT, yytiene una conversión implícita aT, entonces la asignación tiene el tipoT. - De lo contrario, la asignación no es válida y se produce un error en tiempo de vinculación.
El procesamiento en tiempo de ejecución de una asignación simple de la forma x = y con tipo T se realiza como una asignación a x de y con tipo T, que consiste en los siguientes pasos recursivos:
-
xse evalúa si no se ha evaluado ya. - Si
xse clasifica como variable,yse evalúa y, si es necesario, se convierte aTmediante una conversión implícita (sección 10.2).- Si la variable dada por
xes un elemento de matriz de un reference_type, se realiza una comprobación en tiempo de ejecución para garantizar que el valor calculado parayes compatible con la instancia de matriz de la quexes un elemento. La comprobación tiene éxito siyesnull, o si existe una conversión de referencia implícita (sección 10.2.8) desde el tipo de la instancia referenciada poryal tipo de elemento real de la instancia de matriz que contienex. De lo contrario, se produce una excepciónSystem.ArrayTypeMismatchException. - El valor resultante de la evaluación y conversión de
yse almacena en la ubicación determinada por la evaluación dexy se obtiene como resultado de la asignación.
- Si la variable dada por
- Si
xse clasifica como acceso a una propiedad o indexador:-
yse evalúa y, si es necesario, se convierte aTmediante una conversión implícita (sección 10.2). - El descriptor de acceso set de
xse invoca con el valor que resulta de la evaluación y conversión deycomo su argumento de valor. - El valor resultante de la evaluación y conversión de
yse produce como resultado de la asignación.
-
- Si
xse clasifica como la tupla(x1, ..., xn)con aridadn:-
yse deconstruye utilizando elementos denen una expresión de tuplae. - una tupla de resultados
tse crea al convertireaTmediante una conversión implícita de tupla. - para cada
xien orden de izquierda a derecha, se realiza una asignación axidet.Itemi, excepto que losxino se evalúan de nuevo. -
tse obtiene como resultado de la asignación.
-
Nota: si el tipo en tiempo de compilación de
xesdynamicy existe una conversión implícita del tipo en tiempo de compilación deyadynamic, no se requiere resolución en tiempo de ejecución. nota final
Nota: Las reglas de covarianza de matrices (sección 17.6) permiten que un valor de un tipo de matriz
A[]sea una referencia a una instancia de un tipo de matrizB[], siempre que exista una conversión implícita de referencia deBaA. 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 garantizar que el valor que se asigna es compatible con la instancia de matriz. En el ejemplostring[] sa = new string[10]; object[] oa = sa; oa[0] = null; // OK oa[1] = "Hello"; // OK oa[2] = new ArrayList(); // ArrayTypeMismatchExceptionla última asignación provoca que se lance una
System.ArrayTypeMismatchExceptionporque una referencia a unaArrayListno puede almacenarse en un elemento de unastring[].nota final
Cuando una propiedad o indexador declarado en un struct_type es el objetivo de una asignación, la expresión de instancia asociada con el acceso a la propiedad o indexador se clasificará como una variable. Si la expresión de instancia se clasifica como valor, se produce un error de vinculación.
Nota: Debido a sección 12.8.7, la misma regla se aplica también 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.Ayr.Bestán permitidas porquepyrson variables. Sin embargo, en el ejemploRectangle 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.Ayr.Bno son variables.ejemplo final
12.23.3 Asignación ref
El operador = ref se conoce como el operador de asignación de referencia.
El operando izquierdo debe ser una expresión que se vincule a una variable de referencia (sección 9.7), un parámetro de referencia (distinto de this), un parámetro de salida o un parámetro de entrada. El operando derecho será una expresión que produzca una variable_reference que designe un valor del mismo tipo que el operando izquierdo.
Es un error de tiempo de compilación si el ref-safe-context (§9.7.2) del operando izquierdo es más amplio que el ref-safe-context del operando derecho.
El operando derecho debe estar definitivamente asignado en el momento de la asignación de referencia.
Cuando el operando izquierdo se vincula a un parámetro de salida, se produce un error si dicho parámetro de salida no se ha asignado definitivamente al principio del operador de asignación ref.
Si el operando izquierdo es una ref escribible (es decir, designa cualquier cosa que no sea un parámetro local o de entrada ref readonly), entonces el operando derecho será una variable_reference escribible. Si la variable del operando derecho es escribible, el operando izquierdo puede ser una referencia escribible o de solo lectura.
La operación convierte el operando izquierdo en un alias de la variable del operando derecho. El alias puede convertirse en solo lectura incluso si la variable en el operando derecho es escribible.
El operador de asignación de referencia produce una variable_reference del tipo asignado. Se puede escribir si el operando izquierdo es editable.
El operador de asignación de referencia no leerá la ubicación de almacenamiento a la que hace referencia el operando derecho.
Ejemplo: Estos son algunos ejemplos de uso de
= ref: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: Cuando se lee código utilizando un operador
= ref, puede ser tentador leer la parterefcomo si fuera parte del operando. Esto es especialmente confuso cuando el operando es una expresión condicional?:. Por ejemplo, al leerref int a = ref b ? ref x : ref y;, es importante leerlo como= refsiendo el operador yb ? ref x : ref ysiendo el operando a la derecha:ref int a = ref (b ? ref x : ref y);. Es importante tener en cuenta que la expresiónref bno es parte de esa instrucción, aunque pueda parecerlo a primera vista. nota final
12.23.4 Asignación compuesta
Si el operando izquierdo de una asignación compuesta es de la forma E.P o E[Ei] donde E tiene el tipo en tiempo de compilación dynamic, entonces la asignación está ligada dinámicamente (sección 12.3.3). En este caso, el tipo en tiempo de compilación de la expresión de asignación es dynamic y la resolución descrita a continuación se realizará en tiempo de ejecución en función del tipo de tiempo de ejecución de E. Si el operando izquierdo es de la forma E[Ei] en la que al menos un elemento Ei tiene el tipo en tiempo de compilación dynamic, y el tipo en tiempo de compilación de E no es un array, el acceso al indexador resultante está ligado dinámicamente, pero con una comprobación limitada en tiempo de compilación (sección 12.6.5).
a ??= b es equivalente a (T) (a ?? (a = b)), excepto que a se evalúa una sola vez, donde T es el tipo de a cuando el tipo de b es dinámico y, de lo contrario T , es el tipo de a ?? b.
De lo contrario, 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. Entonces
- Si el tipo de retorno del operador seleccionado es implícitamente convertible al tipo de
x, la operación se evalúa comox = x «op» y, excepto quexse evalúa una sola vez. - En caso contrario, si el operador seleccionado es un operador predefinido, si el tipo de devolución del operador seleccionado es explícitamente convertible al tipo de
x, y siyes implícitamente convertible al tipo dexo el operador es un operador de desplazamiento, entonces la operación se evalúa comox = (T)(x «op» y), dondeTes el tipo dex, excepto quexse evalúe solo una vez. - En caso contrario, la asignación compuesta no es válida y se produce un error de vinculación.
El término "solo se evalúa 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 luego se reutilizan al realizar la asignación a x.
Ejemplo: en el
A()[B()] += C()de asignación, dondeAes un método que devuelveint[], yByCson métodos que devuelvenint, los métodos se invocan solo una vez, en el ordenA,B,C. ejemplo final
Cuando el operando izquierdo de una asignación compuesta es un acceso a propiedad o a indexador, la propiedad o el indexador deberán tener 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 evaluar x «op»= y como x = (T)(x «op» y) en determinados contextos. La regla existe para que los operadores predefinidos puedan utilizarse como operadores compuestos cuando el operando izquierdo es del tipo sbyte, byte, short, ushort o char. Incluso cuando ambos argumentos son de uno de esos tipos, los operadores predefinidos producen un resultado de tipo int, como se describe en la sección 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 está permitido si tanto x «op» y como x = y están permitidos.
Example: En el código de ejemplo 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; // OKla 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 de elevación. Dado que una asignación compuesta
x «op»= yse evalúa comox = x «op» yox = (T)(x «op» y), las reglas de evaluación cubren implícitamente operadores elevados. nota final
12.23.5 Asignación de eventos
Si el operando izquierdo del operador a += or -= se clasifica como un acceso de evento, entonces la expresión se evalúa como sigue:
- Si existe, la expresión de instancia 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 mediante una conversión implícita (sección 10.2). - Se invoca el descriptor de acceso del evento, con una lista de argumentos que consta del valor calculado en el paso anterior. Si el operador era
+=, se invoca el método de acceso 'add'; si el operador era-=, se invoca el método de acceso 'remove'.
Una expresión de asignación de evento no produce un valor. Por lo tanto, una expresión de asignación de suceso solo es válida en el contexto de una statement_expression (sección 13.7).
Expresión 12.24
Una expresión es una non_assignment_expression o una asignación.
expression
: non_assignment_expression
| assignment
;
non_assignment_expression
: declaration_expression
| conditional_expression
| lambda_expression
| query_expression
;
12.25 Expresiones constantes
Una expresión constante es una expresión que debe evaluarse completamente en tiempo de compilación.
constant_expression
: expression
;
Una expresión constante debe tener el valor null o uno de los siguientes tipos:
-
sbyte,byte,short,ushort,int,uint,long,ulong,char,float,double,decimal,bool,string - un tipo de enumeración; o
- una expresión de valor por defecto (sección 12.8.21) para un tipo de referencia.
Solo se permiten las siguientes construcciones en las expresiones constantes:
- Literales (incluido el literal
null). - Referencias a
constmiembros de tipos de clase, estructura e interfaz. - Referencias a miembros de tipos de enumeración.
- Referencias a constantes locales.
- Subexpresiones entre paréntesis, que son a su vez expresiones constantes.
- Expresiones de conversión.
- Expresiones
checkedyunchecked. - Expresiones
nameof. - Los operadores unarios predefinidos
+,-,!(negación lógica) y~. - Los operadores binarios predefinidos
+,-,*,/,%,<<,>>,&,|,^,&&,||,==,!=,<,>,<=y>=. - El operador condicional
?:. - El operador que acepta valores NULL
!(§12.8.9). -
sizeofexpresiones, siempre que el tipo no administrado sea uno de los tipos especificados en §24.6.9 para los quesizeofdevuelve un valor constante. - Expresiones de valor por defecto, siempre que el tipo sea uno de los enumerados anteriormente.
Las siguientes conversiones están permitidas en las expresiones constantes:
- Conversiones de identidad
- Conversiones numéricas
- Conversiones de enumeración
- Conversiones de expresiones de constantes
- Conversiones de referencia implícitas y explícitas, siempre que el origen de las conversiones sea una expresión constante que se evalúe al valor
null.
Nota: no se permiten otras conversiones como boxing, unboxing y conversiones implícitas de valores que no son
nullen expresiones de constantes. nota final
Example: En el código de ejemplo siguiente
class C { const object i = 5; // error: boxing conversion not permitted const object str = "hello"; // error: implicit reference conversion }la inicialización de
ies un error porque es necesario realizar una conversión boxing. La inicialización destres un error porque se requiere una conversión de referencia implícita a partir de un no valornull.ejemplo final
Siempre que una expresión cumpla los requisitos enumerados anteriormente, se evaluará 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 no constantes.
La evaluación en tiempo de compilación de expresiones constantes utiliza las mismas reglas que la evaluación en tiempo de ejecución de expresiones no constantes, con la salvedad de que en los casos en que la evaluación en tiempo de ejecución hubiera lanzado una excepción, la evaluación en tiempo de compilación provoca un error de compilación.
A menos que una expresión constante se coloque explícitamente en un contexto de unchecked, los desbordamientos que se producen en operaciones aritméticas de tipo integral 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 enumerados a continuación, lo que se indica en la gramática mediante constant_expression. En estos contextos, se produce un error de compilación si una expresión no puede evaluarse completamente en tiempo de compilación.
- Declaraciones constantes (sección 15.4)
- Declaraciones de miembro de enumeración (§20.4)
- Argumentos por defecto de las listas de parámetros (sección 15.6.2)
- etiquetas
casede una instrucciónswitch(§13.8.3). - instrucciones
goto case(§13.10.4) - Longitudes de dimensión en una expresión de creación de matriz (§12.8.17.4) que incluye un inicializador.
- Atributos (§23)
- En un constant_pattern (§11.2.3)
Una conversión implícita de expresiones constantes (sección 10.2.11) permite convertir una expresión constante de tipo int a sbyte, byte, short, ushort, uint o ulong, siempre que el valor de la expresión constante esté dentro del rango del tipo de destino.
12.26 Expresiones booleanas
Una boolean_expression es una expresión que produce un resultado de tipo bool; ya sea directamente o mediante la aplicación de operator true en determinados contextos como se especifica a continuación:
boolean_expression
: expression
;
La expresión condicional controladora 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.20) sigue las mismas reglas que una boolean_expression, pero por motivos de precedencia del operador se clasifica como un null_coalescing_expression.
Se requiere que una boolean_expressionE pueda producir un valor de tipo bool, como se indica a continuación:
- Si E es implícitamente convertible a
bool, entonces en tiempo de ejecución se aplica esa conversión implícita. - En caso contrario, se utiliza la resolución de sobrecarga de operadores unarios (sección 12.4.4) para encontrar la mejor implementación única de
operator trueenE, y dicha implementación se aplica en tiempo de ejecución. - Si no se encuentra dicho operador, se produce un error de vinculación.
ECMA C# draft specification