12 expresiones
12.1 General
Una expresión es una secuencia de operadores y operandos. Esta cláusula define la sintaxis, el orden de evaluación de operandos y operadores, y el significado de las expresiones.
12.2 Clasificaciones de expresiones
12.2.1 General
El resultado de una expresión se clasifica como uno de los siguientes:
- Valor . Todos los valores tienen un tipo asociado.
- Una variable . A menos que se especifique lo contrario, se escribe explícitamente una variable y tiene un tipo asociado, es decir, el tipo declarado de la variable. Una variable con tipo implícito no tiene ningún tipo asociado.
- Literal null. Una expresión con esta clasificación se puede convertir implícitamente en un tipo de referencia o un tipo de valor que acepta valores NULL.
- Una función anónima. Una expresión con esta clasificación se puede convertir implícitamente en un tipo de delegado compatible o tipo de árbol de expresión.
- Una tupla. Cada tupla tiene un número fijo de elementos, cada uno con una expresión y un nombre de elemento de tupla opcional.
- Acceso a propiedades. Cada acceso de propiedad tiene un tipo asociado, es decir, el tipo de la propiedad. Además, un acceso a propiedades puede tener una expresión de instancia asociada. Cuando se invoca un descriptor de acceso de una propiedad de instancia, el resultado de evaluar la expresión de instancia se convierte en la instancia representada por
this
(§12.8.14). - Acceso a un indexador. Cada acceso al indexador tiene un tipo asociado, es decir, el tipo de elemento del indexador. Además, un acceso de indexador tiene una expresión de instancia asociada y una lista de argumentos asociada. Cuando se invoca un descriptor de acceso de indexador, el resultado de evaluar la expresión de instancia se convierte en la instancia representada por
this
(§12.8.14) y el resultado de evaluar la lista de argumentos se convierte en la lista de parámetros de la invocación. - Nada. Esto ocurre cuando la expresión es una invocación de un método con un tipo de valor devuelto de
void
. Una expresión clasificada como nada solo es válida en el contexto de un statement_expression (§13.7) o como el cuerpo de un lambda_expression (§12.19).
En el caso de las expresiones que se producen como subexpresiones de expresiones más grandes, con las restricciones indicadas, el resultado también se puede clasificar como uno de los siguientes:
- Espacio de nombres. Una expresión con esta clasificación solo puede aparecer como el lado izquierdo de un member_access (§12.8.7). En cualquier otro contexto, una expresión clasificada como un espacio de nombres produce un error en tiempo de compilación.
- Tipo . Una expresión con esta clasificación solo puede aparecer como el lado izquierdo de un member_access (§12.8.7). En cualquier otro contexto, una expresión clasificada como un tipo produce un error en tiempo de compilación.
- Un grupo de métodos, que es un conjunto de métodos sobrecargados resultantes de una búsqueda de miembros (§12.5). Un grupo de métodos puede tener una expresión de instancia asociada y una lista de argumentos de tipo asociado. Cuando se invoca un método de instancia, el resultado de evaluar la expresión de instancia se convierte en la instancia representada por
this
(§12.8.14). Se permite un grupo de métodos en un invocation_expression (§12.8.10) o en un delegate_creation_expression (§12.8.17.6) y se puede convertir implícitamente en un tipo de delegado compatible (§10.8). En cualquier otro contexto, una expresión clasificada como un grupo de métodos produce un error en tiempo de compilación. - Acceso a eventos. Cada acceso a eventos tiene un tipo asociado, es decir, el tipo del evento. Además, un acceso a eventos puede tener una expresión de instancia asociada. Un acceso a eventos puede aparecer como el operando izquierdo de los
+=
operadores y-=
(§12.21.5). En cualquier otro contexto, una expresión clasificada como acceso a eventos provoca un error en tiempo de compilación. Cuando se invoca un descriptor de acceso de evento de instancia, el resultado de evaluar la expresión de instancia se convierte en la instancia representada porthis
(§12.8.14). - Una expresión throw, que se puede usar es varios contextos para iniciar una excepción en una expresión. Una expresión throw se puede convertir mediante una conversión implícita a cualquier tipo.
Un acceso a propiedades o acceso al indexador siempre se vuelve a clasificar como un valor mediante la realización de una invocación del descriptor de acceso get o del descriptor de acceso set. El descriptor de acceso determinado viene determinado por el contexto de la propiedad o el acceso al indexador: si el acceso es el destino de una asignación, se invoca el descriptor de acceso set para asignar un nuevo valor (§12.21.2). De lo contrario, se invoca el descriptor de acceso get para obtener el valor actual (§12.2.2).
Un descriptor de acceso de instancia es un acceso de propiedad en una instancia, un acceso a eventos en una instancia o un acceso de indexador.
12.2.2 Valores de expresiones
La mayoría de las construcciones que implican una expresión requieren en última instancia la expresión para indicar un valor. En tales casos, si la expresión real denota un espacio de nombres, un tipo, un grupo de métodos o nada, se produce un error en tiempo de compilación. Sin embargo, si la expresión denota un acceso de propiedad, un acceso de indexador o una variable, el valor de la propiedad, el indexador o la variable se sustituye implícitamente:
- El valor de una variable es simplemente el valor almacenado actualmente en la ubicación de almacenamiento identificada por la variable. Una variable se considerará asignada definitivamente (§9.4) antes de que se pueda obtener su valor o, de lo contrario, se produzca un error en tiempo de compilación.
- El valor de una expresión de acceso de propiedad se obtiene invocando el descriptor de acceso get de la propiedad . Si la propiedad no tiene ningún descriptor de acceso get, se produce un error en tiempo de compilación. De lo contrario, se realiza una invocación de miembro de función (§12.6.6) y el resultado de la invocación se convierte en el valor de la expresión de acceso de propiedad.
- El valor de una expresión de acceso del indexador se obtiene invocando el descriptor de acceso get del indexador. Si el indexador no tiene ningún descriptor de acceso get, se produce un error en tiempo de compilación. De lo contrario, se realiza una invocación de miembro de función (§12.6.6) con la lista de argumentos asociada a la expresión de acceso del indexador y el resultado de la invocación se convierte en el valor de la expresión de acceso del indexador.
- El valor de una expresión de tupla se obtiene aplicando una conversión de tupla implícita (§10.2.13) al tipo de la expresión de tupla. Se trata de un error para obtener el valor de una expresión de tupla que no tiene un tipo.
12.3 Enlace estático y dinámico
12.3.1 General
El enlace es el proceso de determinar a qué se refiere una operación, en función del tipo o valor de las expresiones (argumentos, operandos, receptores). Por ejemplo, el enlace de una llamada de método se determina en función del tipo del receptor y los argumentos. El enlace de un operador se determina en función del tipo de sus operandos.
En C# el enlace de una operación se determina normalmente en tiempo de compilación, en función del tipo en tiempo de compilación de sus subexpresiones. Del mismo modo, si una expresión contiene un error, el compilador detecta y notifica el error. Este enfoque se conoce como enlace estático.
Sin embargo, si una expresión es una expresión dinámica (es decir, tiene el tipo dynamic
), esto indica que cualquier enlace en el que participa debe basarse en su tipo en tiempo de ejecución en lugar del tipo que tiene en tiempo de compilación. Por lo tanto, el enlace de dicha operación se aplaza hasta el momento en que se va a ejecutar la operación durante la ejecución del programa. Esto se conoce como enlace dinámico.
Cuando una operación está enlazada dinámicamente, el compilador realiza poca o ninguna comprobación. En su lugar, si se produce un error en el enlace en tiempo de ejecución, los errores se notifican como excepciones en tiempo de ejecución.
Las siguientes operaciones en C# están sujetas al enlace:
- Acceso a miembros:
e.M
- Invocación de método:
e.M(e₁,...,eᵥ)
- Invocación de delegado:
e(e₁,...,eᵥ)
- Acceso a elementos:
e[e₁,...,eᵥ]
- Creación de objetos: nuevo
C(e₁,...,eᵥ)
- Operadores unarios sobrecargados:
+
, ,!
-
(solo negación lógica),~
,++
,--
, ,true
,false
- Operadores binarios sobrecargados: , , ,
%
!=
&
<
<<
>
||
??
^
==
>=
&&
>>
/
|
*
-
+
<=
- Operadores de asignación: , , ,
+=
-=
,%=
,^=
<<=
&=
*=
|=
/=
= ref
=
>>=
- Conversiones implícitas y explícitas
Cuando no hay expresiones dinámicas implicadas, el valor predeterminado de C# es el enlace estático, lo que significa que los tipos en tiempo de compilación de subexpresiones se usan en el proceso de selección. Sin embargo, cuando una de las subexpresiones de las operaciones enumeradas anteriormente es una expresión dinámica, la operación se enlaza dinámicamente.
Es un error en tiempo de compilación si una invocación de método está enlazada dinámicamente y cualquiera de los parámetros, incluido el receptor, son parámetros de entrada.
12.3.2 Tiempo de enlace
El enlace estático tiene lugar en tiempo de compilación, mientras que el enlace dinámico tiene lugar en tiempo de ejecución. En las subclases siguientes, el término tiempo de enlace hace referencia a tiempo de compilación o en tiempo de ejecución, en función de cuándo se produzca el enlace.
Ejemplo: a continuación se muestran las nociones de enlace estático y dinámico y de tiempo de enlace:
object o = 5; dynamic d = 5; Console.WriteLine(5); // static binding to Console.WriteLine(int) Console.WriteLine(o); // static binding to Console.WriteLine(object) Console.WriteLine(d); // dynamic binding to Console.WriteLine(int)
Las dos primeras llamadas se enlazan estáticamente: la sobrecarga de
Console.WriteLine
se selecciona en función del tipo de tiempo de compilación de su argumento. Por lo tanto, el tiempo de enlace es tiempo de compilación.La tercera llamada se enlaza dinámicamente: la sobrecarga de
Console.WriteLine
se selecciona en función del tipo en tiempo de ejecución de su argumento. Esto sucede porque el argumento es una expresión dinámica: su tipo en tiempo de compilación es dinámico. Por lo tanto, el tiempo de enlace de la tercera llamada es en tiempo de ejecución.ejemplo final
12.3.3 Enlace dinámico
Esta subclausa es informativa.
El enlace dinámico permite a los programas de C# interactuar con objetos dinámicos, es decir, objetos que no siguen las reglas normales del sistema de tipos de C#. Los objetos dinámicos pueden ser objetos de otros lenguajes de programación con distintos sistemas de tipos, o pueden ser objetos que se configuran mediante programación para implementar su propia semántica de enlace para diferentes operaciones.
El mecanismo por el que un objeto dinámico implementa su propia semántica es definido por la implementación. Una interfaz determinada , definida de nuevo por la implementación, se implementa mediante objetos dinámicos para indicar al tiempo de ejecución de C# que tienen semántica especial. Por lo tanto, cada vez que las operaciones de un objeto dinámico están enlazadas dinámicamente, su propia semántica de enlace, en lugar de las de C# especificadas en esta especificación, tome el control.
Aunque el propósito del enlace dinámico es permitir la interoperación con objetos dinámicos, C# permite el enlace dinámico en todos los objetos, tanto si son dinámicos como si no. Esto permite una integración más fluida de objetos dinámicos, ya que los resultados de las operaciones en ellos pueden no ser objetos dinámicos, pero siguen siendo de un tipo desconocido para el programador en tiempo de compilación. Además, el enlace dinámico puede ayudar a eliminar el código basado en reflexión propenso a errores incluso cuando no hay objetos implicados en objetos dinámicos.
12.3.4 Tipos de subexpresiones
Cuando una operación está enlazada estáticamente, el tipo de una subexpresión (por ejemplo, un receptor y un argumento, un índice o un operando) siempre se considera el tipo en tiempo de compilación de esa expresión.
Cuando una operación se enlaza dinámicamente, el tipo de una subexpresión se determina de diferentes maneras en función del tipo de tiempo de compilación de la subexpresión:
- Se considera que una subexpresión del tipo en tiempo de compilación dinámico tiene el tipo del valor real que la expresión se evalúa en tiempo de ejecución.
- Subexpresión cuyo tipo de tiempo de compilación es un parámetro de tipo se considera que tiene el tipo al que está enlazado el parámetro de tipo en tiempo de ejecución.
- De lo contrario, se considera que la subexpresión tiene su tipo en tiempo de compilación.
Operadores 12.4
12.4.1 General
Las expresiones se construyen con operandos y operadores. Los operadores de una expresión indican qué operaciones se aplican a los operandos.
Ejemplo: Algunos ejemplos de operadores son
+
,-
,*
,/
ynew
. Algunos ejemplos de operandos son literales, campos, variables locales y expresiones. ejemplo final
Hay tres tipos de operadores:
- Operadores unarios. Los operadores unarios toman un operando y usan notación de prefijo (como
–x
) o notación postfijo (comox++
). - Operadores binarios. Los operadores binarios toman dos operandos y todos usan la notación infijo (como
x + y
). - Operador ternario. Solo existe un operador ternario,
?:
, y toma tres operandos y usa la notación de infijo (c ? x : y
).
El orden de evaluación de los operadores en una expresión viene determinado por la precedencia y la asociatividad de los operadores (§12.4.2).
Los operandos de una expresión se evalúan de izquierda a derecha.
Ejemplo: En
F(i) + G(i++) * H(i)
, se llama al métodoF
mediante el valor anterior dei
, se llama al métodoG
con el valor anterior dei
y, por último, se llama al métodoH
con el nuevo valor de i. Esto es independiente de y no está relacionado con la precedencia del operador. ejemplo final
Algunos operadores se pueden sobrecargar. La sobrecarga del operador (§12.4.3) permite especificar implementaciones de operador definidas por el usuario para las operaciones en las que uno o ambos operandos son de una clase o un tipo de estructura definidos por el usuario.
12.4.2 Precedencia y asociatividad del operador
Cuando una expresión contiene varios operadores, su precedencia controla el orden en el que se evalúan los operadores individuales.
Nota: Por ejemplo, la expresión
x + y * z
se evalúa comox + (y * z)
porque el*
operador tiene mayor prioridad que el operador binario+
. nota final
La precedencia de un operador se establece mediante la definición de su producción gramatical asociada.
Nota: Por ejemplo, un additive_expression consta de una secuencia de multiplicative_expressionseparados por
+
operadores o-
, lo que proporciona a los+
operadores y-
una prioridad inferior a los*
operadores ,/
y%
. nota final
Nota: En la tabla siguiente se resumen todos los operadores en orden de prioridad de mayor a menor:
Subclause Categoría Operadores §12.8 Principal x.y
x?.y
f(x)
a[x]
a?[x]
x++
x--
x!
new
typeof
default
checked
unchecked
delegate
stackalloc
§12.9 Unario +
-
!x
~
++x
--x
(T)x
await x
§12.10 Multiplicativo *
/
%
§12.10 Aditivo +
-
§12.11 Shift <<
>>
§12.12 Comprobación de tipos y relacional <
>
<=
>=
is
as
§12.12 Igualdad ==
!=
§12.13 Y lógico &
§12.13 XOR lógico ^
§12.13 O lógico \|
§12.14 AND condicional &&
§12.14 OR condicional \|\|
§12.15 y §12.16 Expresión de fusión y lanzamiento nulas ??
throw x
§12.18 Condicional ?:
§12.21 y §12.19 Asignación y expresión lambda =
= ref
*=
/=
%=
+=
-=
<<=
>>=
&=
^=
\|=
=>
nota final
Cuando un operando se encuentra entre dos operadores con la misma precedencia, la asociatividad de los operadores controla el orden en que se realizan las operaciones:
- Excepto los operadores de asignación y el operador de fusión null, todos los operadores binarios son asociativos a la izquierda, lo que significa que las operaciones se realizan de izquierda a derecha.
Ejemplo:
x + y + z
se evalúa como(x + y) + z
. ejemplo final - Los operadores de asignación, el operador de fusión null y el operador condicional (
?:
) son asociativos a la derecha, lo que significa que las operaciones se realizan de derecha a izquierda.Ejemplo:
x = y = z
se evalúa comox = (y = z)
. ejemplo final
La precedencia y la asociatividad pueden controlarse mediante paréntesis.
Ejemplo:
x + y * z
primero multiplicay
porz
y, a continuación, agrega el resultado ax
, pero(x + y) * z
primero agregax
yy
, a continuación, multiplica el resultado porz
. ejemplo final
Sobrecarga del operador 12.4.3
Todos los operadores unarios y binarios tienen implementaciones predefinidas. Además, las implementaciones definidas por el usuario se pueden introducir mediante la inclusión de declaraciones de operador (§15.10) en clases y estructuras. Las implementaciones de operador definidas por el usuario siempre tienen prioridad sobre las implementaciones de operador predefinidas: solo cuando no existan implementaciones de operador definidas por el usuario, se considerarán las implementaciones de operador predefinidas, como se describe en §12.4.4 y §12.4.5.
Los operadores unarios sobrecargables son:
+ - !
(solo negación lógica) ~ ++ -- true false
Nota: Aunque
true
yfalse
no se usan explícitamente en expresiones (y, por tanto, no se incluyen en la tabla de precedencia de §12.4.2), se consideran operadores porque se invocan en varios contextos de expresión: expresiones booleanas (§12.24) y expresiones que implican los operadores lógicos condicionales (§12.18) y condicionales (§12.14). nota final
Nota: El operador null-forgiving (postfix
!
, §12.8.9) no es un operador sobrecargable. nota final
Los operadores binarios sobrecargables son:
+ - * / % & | ^ << >> == != > < <= >=
Solo se pueden sobrecargar los operadores enumerados anteriormente. En concreto, no es posible sobrecargar el acceso a miembros, la invocación de método o los =
operadores , &&
, =>
checked
??
||
typeof
new
?:
unchecked
, default
, y . as
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 del operador*=
. Esto se describe más adelante en §12.21. ejemplo final
El propio (=)
operador de asignación no se puede sobrecargar. Una asignación siempre realiza un almacén sencillo de un valor en una variable (§12.21.2).
Las operaciones de conversión, como (T)x
, se sobrecargan proporcionando conversiones definidas por el usuario (§10.5).
Nota: Las conversiones definidas por el usuario no afectan al comportamiento de los
is
operadores oas
. nota final
El acceso a elementos, como a[x]
, no se considera un operador sobrecargable. En su lugar, la indexación definida por el usuario se admite mediante indexadores (§15.9).
En expresiones, se hace referencia a los operadores mediante notación de operador y, en declaraciones, se hace referencia a los operadores mediante notación funcional. En la tabla siguiente se muestra la relación entre las notaciones funcionales y de operador para operadores unarios y binarios. En la primera entrada, «op» denota cualquier operador de prefijo unario sobrecargable. En la segunda entrada, «op» denota los operadores y --
postfijo ++
unarios. En la tercera entrada, «op» denota cualquier operador binario sobrecargable.
Nota: Para obtener un ejemplo de sobrecarga de los
++
operadores y--
, consulte §15.10.2. nota final
Notación del operador | Notación funcional |
---|---|
«op» x |
operator «op»(x) |
x «op» |
operator «op»(x) |
x «op» y |
operator «op»(x, y) |
Las declaraciones de operador definidas por el usuario siempre requieren que al menos uno de los parámetros sea del tipo de clase o estructura que contiene la declaración del operador.
Nota: Por lo tanto, no es posible que un operador definido por el usuario tenga la misma firma que un operador predefinido. nota final
Las declaraciones de operador definidas por el usuario no pueden modificar la sintaxis, la precedencia ni la asociatividad de un operador.
Ejemplo: el
/
operador siempre es un operador binario, siempre tiene el nivel de precedencia especificado en §12.4.2 y siempre es asociativo a la izquierda. ejemplo final
Nota: Aunque es posible que un operador definido por el usuario realice cualquier cálculo por favor, las implementaciones que producen resultados distintos de los que se esperan de forma intuitiva se desaconsejan. Por ejemplo, una implementación del operador
==
debe comparar los dos operandos para obtener igualdad y devolver un resultado adecuadobool
. nota final
Las descripciones de operadores individuales de §12.9 a §12.21 especifican las implementaciones predefinidas de los operadores y las reglas adicionales que se aplican a cada operador. Las descripciones hacen uso de los términos resolución de sobrecarga del operador unario, resolución de sobrecarga de operadores binarios, promoción numérica y definiciones de operador elevado de las cuales se encuentran en las subclases siguientes.
12.4.4 Resolución de sobrecarga del operador unario
Una operación del formulario «op» x
o x «op»
, donde «op» es un operador unario sobrecargable, y x
es una expresión de tipo X
, se procesa de la siguiente manera:
- El conjunto de operadores candidatos definidos por el usuario proporcionados por
X
para la operaciónoperator «op»(x)
se determina mediante las reglas de §12.4.6. - Si el conjunto de operadores candidatos definidos por el usuario no está vacío, se convierte en el conjunto de operadores candidatos para la operación. De lo contrario, las implementaciones binarias
operator «op»
predefinidas, incluidas sus formas levantadas, se convierten en el conjunto de operadores candidatos para la operación. Las implementaciones predefinidas de un operador determinado se especifican en la descripción del operador. Los operadores predefinidos proporcionados por un tipo de enumeración o delegado solo se incluyen en este conjunto cuando el tipo de tiempo de enlace (o el tipo subyacente si es un tipo que acepta valores NULL) de cualquiera de los operandos es la enumeración o el tipo delegado. - Las reglas de resolución de sobrecarga de §12.6.4 se aplican al conjunto de operadores candidatos para seleccionar el mejor operador con respecto a la lista
(x)
de argumentos y este operador se convierte en el resultado del proceso de resolución de sobrecarga. Si la resolución de sobrecarga no puede seleccionar un único operador mejor, se produce un error en tiempo de enlace.
Resolución de sobrecarga del operador binario 12.4.5
Una operación del formulario x «op» y
, donde «op» es un operador binario sobrecargable, x
es una expresión de tipo X
, y y
es una expresión de tipo Y
, se procesa de la siguiente manera:
- Se determina el conjunto de operadores candidatos definidos por el usuario proporcionados por
X
yY
para la operaciónoperator «op»(x, y)
. El conjunto consta de la unión de los operadores candidatos proporcionados porX
y los operadores candidatos proporcionados porY
, cada uno determinado mediante las reglas de §12.4.6. Para el conjunto combinado, los candidatos se combinan de la siguiente manera:- Si
X
yY
son convertibles de identidad, o siX
yY
se derivan de un tipo base común, los operadores candidatos compartidos solo se producen en el conjunto combinado una vez. - Si hay una conversión de identidad entre
X
yY
, un operador«op»Y
proporcionado porY
tiene el mismo tipo«op»X
de valor devuelto proporcionado porX
y los tipos de operando de tienen una conversión de«op»Y
identidad a los tipos de operando correspondientes de«op»X
, solo«op»X
se produce en el conjunto.
- Si
- Si el conjunto de operadores candidatos definidos por el usuario no está vacío, se convierte en el conjunto de operadores candidatos para la operación. De lo contrario, las implementaciones binarias
operator «op»
predefinidas, incluidas sus formas levantadas, se convierten en el conjunto de operadores candidatos para la operación. Las implementaciones predefinidas de un operador determinado se especifican en la descripción del operador. Para los operadores de enumeración y delegado predefinidos, los únicos operadores considerados son los proporcionados por un tipo de enumeración o delegado que es el tipo de tiempo de enlace de uno de los operandos. - Las reglas de resolución de sobrecarga de §12.6.4 se aplican al conjunto de operadores candidatos para seleccionar el mejor operador con respecto a la lista
(x, y)
de argumentos y este operador se convierte en el resultado del proceso de resolución de sobrecarga. Si la resolución de sobrecarga no puede seleccionar un único operador mejor, se produce un error en tiempo de enlace.
12.4.6 Operadores definidos por el usuario candidatos
Dado un tipo T
y una operación operator «op»(A)
, donde «op» es un operador sobrecargable y A
es una lista de argumentos, el conjunto de operadores candidatos definidos por el usuario proporcionados por T
para el operador «op»(A)
se determina de la siguiente manera:
- Determine el tipo
T₀
. SiT
es un tipo de valor que acepta valores NULL,T₀
es su tipo subyacente; de lo contrario,T₀
es igual aT
. - Para todas las declaraciones de
T₀
y todas lasoperator «op»
formas levantadas de estos operadores, si al menos un operador es aplicable (§12.6.4.2) con respecto a la listaA
de argumentos , el conjunto de operadores candidatos consta de todos estos operadores aplicables enT₀
. - De lo contrario, si
T₀
esobject
, el conjunto de operadores candidatos está vacío. - De lo contrario, el conjunto de operadores candidatos proporcionado por
T₀
es el conjunto de operadores candidatos proporcionados por la clase base directa deT₀
o la clase base efectiva de siT₀
es un parámetro deT₀
tipo.
12.4.7 Promociones numéricas
12.4.7.1 General
Esta subclausa es informativa.
§12.4.7 y sus subclases son un resumen del efecto combinado de:
- reglas para conversiones numéricas implícitas (§10.2.3);
- las reglas para mejorar la conversión (§12.6.4.7); y
- los operadores aritméticos disponibles (§12.10), relacionales (§12.12) y lógicos enteros (§12.13.2).
La promoción numérica consta de realizar automáticamente ciertas conversiones implícitas de los operandos de los operadores unarios predefinidos y numéricos binarios. La promoción numérica no es un mecanismo distinto, sino un efecto de aplicar la resolución de sobrecarga a los operadores predefinidos. La promoción numérica no afecta específicamente a la evaluación de operadores definidos por el usuario, aunque los operadores definidos por el usuario se pueden implementar para mostrar efectos similares.
Como ejemplo de promoción numérica, considere las implementaciones predefinidas del operador binario *
:
int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);
Cuando se aplican reglas de resolución de sobrecarga (§12.6.4) a este conjunto de operadores, el efecto es seleccionar el primero de los operadores para los que existen conversiones implícitas de los tipos de operando.
Ejemplo: para la operación
b * s
, dondeb
es ybyte
s
es unashort
, la resolución de sobrecarga seleccionaoperator *(int, int)
como el mejor operador. Por lo tanto, el efecto es queb
y se convierten enint
y el tipo del resultado esint
s
. Del mismo modo, para la operación , dondei
es yd
int
es ,overload
double
la resolución seleccionaoperator *(double, double)
como el mejori * d
operador. ejemplo final
Fin del texto informativo.
12.4.7.2 Promociones numéricas unarias
Esta subclausa es informativa.
La promoción numérica unaria se produce para los operandos de los operadores unarios predefinidos +
, -
y ~
. La promoción numérica unaria consiste simplemente en convertir operandos de tipo sbyte
, , 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
en el tipo long
.
Fin del texto informativo.
12.4.7.3 Promociones numéricas binarias
Esta subclausa es informativa.
La promoción numérica binaria se produce para los operandos de los operadores binarios predefinidos +
, , -
, ==
/
&
|
^
%
*
>
<
!=
>=
y <=
binarios. La promoción numérica binaria convierte implícitamente ambos operandos en un tipo común que, en el caso de los operadores no relacionales, también se convierte en el tipo de resultado de la operación. La promoción numérica binaria consta de aplicar las siguientes reglas, en el orden en que aparecen aquí:
- Si cualquiera de los operandos es de tipo
decimal
, el otro operando se convierte en el tipodecimal
o se produce un error en tiempo de enlace si el otro operando es de tipofloat
odouble
. - De lo contrario, si cualquiera de los operandos es de tipo
double
, el otro operando se convierte en el tipodouble
. - De lo contrario, si cualquiera de los operandos es de tipo
float
, el otro operando se convierte en el tipofloat
. - De lo contrario, si cualquiera de los operandos es de tipo
ulong
, el otro operando se convierte en el tipoulong
o se produce un error en tiempo de enlace si el otro operando es detype sbyte
,short
,int
olong
. - De lo contrario, si cualquiera de los operandos es de tipo
long
, el otro operando se convierte en el tipolong
. - De lo contrario, si cualquiera de los operandos es de tipo
uint
y el otro operando es de tiposbyte
,short
oint
, ambos operandos se convierten al tipolong
. - De lo contrario, si cualquiera de los operandos es de tipo
uint
, el otro operando se convierte en el tipouint
. - De lo contrario, ambos operandos se convierten en el tipo
int
.
Nota: La primera regla no permite ninguna operación que combine el
decimal
tipo con losdouble
tipos yfloat
. La regla sigue del hecho de que no hay conversiones implícitas entre eldecimal
tipo y losdouble
tipos yfloat
. nota final
Nota: Tenga en cuenta también que no es posible que un operando sea de tipo
ulong
cuando el otro operando es de un tipo entero firmado. El motivo es que no existe ningún tipo entero que pueda representar el intervalo completo deulong
tipos enteros así como los tipos enteros firmados. nota final
En ambos casos anteriores, se puede usar una expresión de conversión para convertir explícitamente un operando a un tipo compatible con el otro operando.
Ejemplo: en el código siguiente
decimal AddPercent(decimal x, double percent) => x * (1.0 + percent / 100.0);
Se produce un error en tiempo de enlace porque no
decimal
se puede multiplicar por .double
El error se resuelve convirtiendo explícitamente el segundo operando 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 de elevación
Los operadores de elevación permiten que los operadores predefinidos y definidos por el usuario que operan en tipos de valor que no aceptan valores NULL también se usen con formas que aceptan valores NULL de esos tipos. Los operadores de elevación se construyen a partir de operadores predefinidos y definidos por el usuario que cumplen ciertos requisitos, como se describe en lo siguiente:
- Para los operadores
+
unarios ,++
,-
,--
(!
negación lógica) y~
, existe una forma levantada de un operador si los tipos de operando y resultado son tipos de valor que no aceptan valores NULL. El formulario elevado se construye agregando un único?
modificador al operando y a los tipos de resultado. El operador elevado genera unnull
valor si el operando esnull
. De lo contrario, el operador elevado desencapsula el operando, aplica el operador subyacente y ajusta el resultado. - Para los operadores
+
binarios ,-
,*
,%
|
&
^
/
<<
y>>
, existe una forma levantada de un operador si los tipos de resultado y operando son todos los tipos de valor que no aceptan valores NULL. El formulario elevado se construye agregando un único?
modificador a cada operando y tipo de resultado. El operador elevado genera unnull
valor si uno o ambos operandos sonnull
(una excepción que es los&
operadores y|
delbool?
tipo, como se describe en §12.13.5). De lo contrario, el operador elevado desencapsula los operandos, aplica el operador subyacente y ajusta el resultado. - Para los operadores
==
de igualdad y!=
, existe una forma levantada de un operador si los tipos de operando son tipos de valor que no aceptan valores NULL y si el tipo de resultado esbool
. El formulario elevado se construye agregando un único?
modificador a cada tipo de operando. El operador elevado considera dosnull
valores iguales y unnull
valor distinto de cualquier valor que nonull
sea. Si ambos operandos no son,null
el operador elevado desencapsula los operandos y aplica el operador subyacente para generar elbool
resultado. - Para los operadores
<
relacionales ,>
,<=
y>=
, existe una forma levantada de un operador si los tipos de operando son tipos de valor que no aceptan valores NULL y si el tipo de resultado esbool
. El formulario elevado se construye agregando un único?
modificador a cada tipo de operando. El operador elevado genera el valorfalse
si uno o ambos operandos sonnull
. De lo contrario, el operador elevado desencapsula los operandos y aplica el operador subyacente para generar elbool
resultado.
12.5 Búsqueda de miembros
12.5.1 General
Una búsqueda de miembros es el proceso por el que se determina el significado de un nombre en el contexto de un tipo. Una búsqueda de miembros puede producirse como parte de la evaluación de un simple_name (§12.8.4) o un member_access (§12.8.7) en una expresión. Si el simple_name o member_access se produce como el primary_expression de un invocation_expression (§12.8.10.2), se dice que se invoca al miembro.
Si un miembro es un método o evento, o si es una constante, campo o propiedad de un tipo delegado (§20) o el tipo dynamic
(§8.2.4), se dice que el miembro es invocable.
La búsqueda de miembros considera no solo el nombre de un miembro, sino también el número de parámetros de tipo que tiene el miembro y si el miembro es accesible. Para la búsqueda de miembros, los métodos genéricos y los tipos genéricos anidados tienen el número de parámetros de tipo indicados en sus declaraciones respectivas y todos los demás miembros tienen cero parámetros de tipo.
Una búsqueda de miembro de un nombre N
con K
argumentos de tipo en un tipo T
se procesa de la siguiente manera:
- En primer lugar, se determina un conjunto de miembros accesibles denominados
N
:- Si
T
es un parámetro de tipo, el conjunto es la unión de los conjuntos de miembros accesibles denominadosN
en cada uno de los tipos especificados como una restricción principal o una restricción secundaria (§15.2.5) paraT
, junto con el conjunto de miembros accesibles denominadosN
enobject
. - De lo contrario, el conjunto consta de todos los miembros accesibles (§7.5) denominados
N
enT
, incluidos los miembros heredados y los miembros accesibles denominadosN
enobject
. SiT
es un tipo construido, el conjunto de miembros se obtiene sustituyendo argumentos de tipo como se describe en §15.3.3. Los miembros que incluyen unoverride
modificador se excluyen del conjunto.
- Si
- A continuación, si
K
es cero, se quitan todos los tipos anidados cuyas declaraciones incluyen parámetros de tipo. SiK
no es cero, se quitan todos los miembros con un número diferente de parámetros de tipo. CuandoK
es cero, los métodos que tienen parámetros de tipo no se quitan, ya que el proceso de inferencia de tipos (§12.6.3) podría deducir los argumentos de tipo. - A continuación, si se invoca el miembro, todos los miembros no invocables se quitan del conjunto.
- A continuación, los miembros ocultos por otros miembros se quitan del conjunto. Para cada miembro
S.M
del conjunto, dondeS
es el tipo en el que se declara el miembroM
, se aplican las reglas siguientes:- Si
M
es un miembro constante, campo, propiedad, evento o enumeración, todos los miembros declarados en un tipo base deS
se quitan del conjunto. - Si
M
es una declaración de tipo, todos los tipos no declarados en un tipo base deS
se quitan del conjunto y todas las declaraciones de tipo con el mismo número de parámetrosM
de tipo que se declaran en un tipo base deS
se quitan del conjunto. - Si
M
es un método, todos los miembros que no son métodos declarados en un tipo base deS
se quitan del conjunto.
- Si
- A continuación, los miembros de interfaz ocultos por los miembros de clase se quitan del conjunto. Este paso solo tiene un efecto si
T
es un parámetro de tipo yT
tiene una clase base efectiva distintaobject
de y un conjunto de interfaz efectivo no vacío (§15.2.5). Para cada miembroS.M
del conjunto, dondeS
es el tipo en el que se declara el miembroM
, se aplican las reglas siguientes siS
es una declaración de clase distinta deobject
:- Si
M
es una constante, campo, propiedad, evento, miembro de enumeración o declaración de tipo, todos los miembros declarados en una declaración de interfaz se quitan del conjunto. - Si
M
es un método, todos los miembros que no son métodos declarados en una declaración de interfaz se quitan del conjunto y todos los métodos con la misma firmaM
declarada en una declaración de interfaz se quitan del conjunto.
- Si
- Por último, después de quitar miembros ocultos, se determina el resultado de la búsqueda:
- Si el conjunto consta de un único miembro que no es un método, este miembro es el resultado de la búsqueda.
- De lo contrario, si el conjunto contiene solo métodos, este grupo de métodos es el resultado de la búsqueda.
- De lo contrario, la búsqueda es ambigua y se produce un error en tiempo de enlace.
Para las búsquedas de miembros en tipos distintos de los parámetros de tipo e interfaces, y las búsquedas de miembros en interfaces que son estrictamente de herencia única (cada interfaz de la cadena de herencia tiene exactamente cero o una interfaz base directa), el efecto de las reglas de búsqueda es simplemente que los miembros derivados ocultan los miembros base con el mismo nombre o firma. Estas búsquedas de herencia única nunca son ambiguas. Las ambigüedades que pueden surgir de búsquedas de miembros en interfaces de herencia múltiple se describen en §18.4.6.
Nota: Esta fase solo tiene en cuenta un tipo de ambigüedad. Si la búsqueda de miembros da como resultado un grupo de métodos, es posible que se produzca un error en los usos adicionales del grupo de métodos debido a ambigüedad, como se describe en §12.6.4.1 y §12.6.6.2. nota final
12.5.2 Tipos base
Para fines de búsqueda de miembros, se considera que un tipo T
tiene los siguientes tipos base:
- Si
T
esobject
odynamic
,T
no tiene ningún tipo base. - Si
T
es un enum_type, los tipos base de son los tiposSystem.Enum
deT
clase ,System.ValueType
yobject
. - Si
T
es un struct_type, los tipos base de son los tiposSystem.ValueType
deT
clase yobject
.Nota: Un nullable_value_type es un struct_type (§8.3.1). nota final
- Si
T
es un class_type, los tipos base de son las clases base deT
, incluido el tipoobject
deT
clase . - Si
T
es un interface_type, los tipos base deT
son las interfaces base deT
y el tipoobject
de clase . - Si
T
es un array_type, los tipos base de son los tiposSystem.Array
deT
clase yobject
. - Si
T
es un delegate_type, los tipos base de son los tiposSystem.Delegate
deT
clase yobject
.
12.6 Miembros de la función
12.6.1 General
Los miembros de función son miembros que contienen instrucciones ejecutables. Los miembros de función son siempre miembros de tipos y no pueden ser miembros de espacios de nombres. C# define las siguientes categorías de miembros de función:
- Métodos
- Propiedades
- Eventos
- Indizadores
- Operadores definidos por el usuario
- Constructores de instancias
- Constructores estáticos
- Finalizadores
Excepto los finalizadores y los constructores estáticos (que no se pueden invocar explícitamente), las instrucciones contenidas en los miembros de función se ejecutan a través de invocaciones de miembro de función. La sintaxis real para escribir una invocación de miembro de función depende de la categoría de miembro de función determinada.
La lista de argumentos (§12.6.2) de una invocación de miembro de función proporciona valores reales o referencias de variables para los parámetros del miembro de función.
Las invocaciones de métodos genéricos pueden emplear la inferencia de tipos para determinar el conjunto de argumentos de tipo que se van a pasar al método . Este proceso se describe en §12.6.3.
Las invocaciones de métodos, indizadores, operadores y constructores de instancia emplean resolución de sobrecarga para determinar cuál de un conjunto candidato de miembros de función que se va a invocar. Este proceso se describe en §12.6.4.
Una vez que se ha identificado un miembro de función determinado en tiempo de enlace, posiblemente mediante la resolución de sobrecarga, el proceso en tiempo de ejecución real de invocar al miembro de función se describe en §12.6.6.
Nota: En la tabla siguiente se resume el procesamiento que tiene lugar en construcciones que implican las seis categorías de miembros de función que se pueden invocar explícitamente. En la tabla ,
e
,x
,y
evalue
indican expresiones clasificadas como variables o valores,T
indica una expresión clasificada como un tipo,F
es el nombre simple de un método yP
es el nombre simple de una propiedad.
Construcción Ejemplo Descripción Invocación de método F(x, y)
La resolución de sobrecarga se aplica para seleccionar el mejor método F
de la clase o estructura contenedora. El método se invoca con la lista(x, y)
de argumentos . Si el método nostatic
es , la expresión de instancia esthis
.T.F(x, y)
La resolución de sobrecarga se aplica para seleccionar el mejor método F
de la clase o estructuraT
. Se produce un error en tiempo de enlace si el método nostatic
es . El método se invoca con la lista(x, y)
de argumentos .e.F(x, y)
La resolución de sobrecarga se aplica para seleccionar el mejor método F
de la clase, estructura o interfaz dada por el tipo dee
. Se produce un error en tiempo de enlace si el método esstatic
. El método se invoca con la expresióne
de instancia y la lista(x, y)
de argumentos .Property Access P
Se invoca el descriptor de acceso get de la propiedad P
en la clase o estructura contenedora. Se produce un error en tiempo de compilación siP
es de solo escritura. SiP
nostatic
es , la expresión de instancia esthis
.P = value
El descriptor de acceso set de la propiedad P
de la clase o estructura contenedora se invoca con la lista(value)
de argumentos . Se produce un error en tiempo de compilación siP
es de solo lectura. SiP
nostatic
es , la expresión de instancia esthis
.T.P
Se invoca el descriptor de acceso get de la propiedad P
en la clase o estructuraT
. Se produce un error en tiempo de compilación siP
nostatic
es o siP
es de solo escritura.T.P = value
El descriptor de acceso set de la propiedad P
de la clase o estructuraT
se invoca con la lista(value)
de argumentos . Se produce un error en tiempo de compilación siP
nostatic
es o siP
es de solo lectura.e.P
El descriptor de acceso get de la propiedad P
de la clase, estructura o interfaz dada por el tipo deE
se invoca con la expresióne
de instancia . Se produce un error en tiempo de enlace siP
esstatic
o siP
es de solo escritura.e.P = value
El descriptor de acceso set de la propiedad P
de la clase, estructura o interfaz dada por el tipo deE
se invoca con la expresióne
de instancia y la lista(value)
de argumentos . Se produce un error en tiempo de enlace siP
esstatic
o siP
es de solo lectura.Acceso a eventos E += value
Se invoca el descriptor de acceso add del evento E
en la clase o estructura contenedora. SiE
nostatic
es , la expresión de instancia esthis
.E -= value
Se invoca el descriptor de acceso remove del evento E
en la clase o estructura contenedora. SiE
nostatic
es , la expresión de instancia esthis
.T.E += value
Se invoca el descriptor de acceso add del evento E
en la clase o estructuraT
. Se produce un error en tiempo de enlace siE
nostatic
es .T.E -= value
Se invoca el descriptor de acceso remove del evento E
en la clase o estructuraT
. Se produce un error en tiempo de enlace siE
nostatic
es .e.E += value
El descriptor de acceso add del evento E
en la clase, estructura o interfaz dada por el tipo deE
se invoca con la expresióne
de instancia . Se produce un error en tiempo de enlace siE
esstatic
.e.E -= value
El descriptor de acceso remove del evento E
en la clase, estructura o interfaz dada por el tipo deE
se invoca con la expresióne
de instancia . Se produce un error en tiempo de enlace siE
esstatic
.Acceso a indizador e[x, y]
La resolución de sobrecarga se aplica para seleccionar el mejor indexador de la clase, estructura o interfaz dada por el tipo de e
. El descriptor de acceso get del indexador se invoca con la expresióne
de instancia y la lista(x, y)
de argumentos . Se produce un error en tiempo de enlace si el indexador es de solo escritura.e[x, y] = value
La resolución de sobrecarga se aplica para seleccionar el mejor indexador de la clase, estructura o interfaz dada por el tipo de e
. El descriptor de acceso set del indexador se invoca con la expresióne
de instancia y la lista(x, y, value)
de argumentos . Se produce un error en tiempo de enlace si el indexador es de solo lectura.Invocación de operador -x
La resolución de sobrecarga se aplica para seleccionar el mejor operador unario de la clase o estructura dada por el tipo de x
. El operador seleccionado se invoca con la lista(x)
de argumentos .x + y
La resolución de sobrecarga se aplica para seleccionar el mejor operador binario de las clases o estructuras dadas por los tipos de x
yy
. El operador seleccionado se invoca con la lista(x, y)
de argumentos .Invocación del constructor de instancia new T(x, y)
La resolución de sobrecarga se aplica para seleccionar el mejor constructor de instancia de la clase o estructura T
. El constructor de instancia se invoca con la lista(x, y)
de argumentos .nota final
12.6.2 Listas de argumentos
12.6.2.1 General
Cada invocación de miembro de función y delegado incluye una lista de argumentos, que proporciona valores reales o referencias a variables para los parámetros del miembro de función. La sintaxis para especificar la lista de argumentos de una invocación de miembro de función depende de la categoría de miembro de función:
- Por ejemplo, los constructores, métodos, indizadores y delegados, los argumentos se especifican como un argument_list, como se describe a continuación. Para los indexadores, al invocar el descriptor de acceso set, la lista de argumentos incluye además la expresión especificada como el operando derecho del operador de asignación.
Nota: Este argumento adicional no se usa para la resolución de sobrecargas, solo durante la invocación del descriptor de acceso set. nota final
- En el caso de las propiedades, la lista de argumentos está vacía al invocar el descriptor de acceso get y consta de la expresión especificada como el operando derecho del operador de asignación al invocar el descriptor de acceso set.
- En el caso de los eventos, la lista de argumentos consta de la expresión especificada como el operando derecho del
+=
operador o-=
. - En el caso de los operadores definidos por el usuario, la lista de argumentos consta del operando único del operador unario o de los dos operandos del operador binario.
Los argumentos de las propiedades (§15.7) y los eventos (§15.8) siempre se pasan como parámetros de valor (§15.6.2.2). Los argumentos de los operadores definidos por el usuario (§15.10) siempre se pasan como parámetros de valor (§15.6.2.2) o parámetros de entrada (§9.2.8). Los argumentos de los indexadores (§15.9) siempre se pasan como parámetros de valor (§15.6.2.2), parámetros de entrada (§9.2.8) o matrices de parámetros (§15.6.2.4). Los parámetros de salida y referencia no se admiten para estas categorías de miembros de función.
Los argumentos de un constructor de instancia, método, indexador o invocación de delegado se especifican como un argument_list:
argument_list
: argument (',' argument)*
;
argument
: argument_name? argument_value
;
argument_name
: identifier ':'
;
argument_value
: expression
| 'in' variable_reference
| 'ref' variable_reference
| 'out' variable_reference
;
Un argument_list consta de uno o varios argumentos, separados por comas. Cada argumento consta de un argument_name opcional seguido de un argument_value. Un argumento con un argument_name se conoce como argumento con nombre, mientras que un argumento sin un argument_name es un argumento posicional.
El argument_value puede adoptar una de las siguientes formas:
- Expresión que indica que el argumento se pasa como un parámetro de valor o se transforma en un parámetro de entrada y, a continuación, se pasa como eso, según lo determinado por (§12.6.4.2 y descrito en §12.6.2.3.
- La palabra clave
in
seguida de un variable_reference (§9.5), que indica que el argumento se pasa como parámetro de entrada (§15.6.2.3.2). Se asignará definitivamente una variable (§9.4) antes de que se pueda pasar como parámetro de entrada. - Palabra clave
ref
seguida de un variable_reference (§9.5), que indica que el argumento se pasa como parámetro de referencia (§15.6.2.3.3). Se asignará definitivamente una variable (§9.4) antes de que se pueda pasar como parámetro de referencia. - La palabra clave
out
seguida de un variable_reference (§9.5), que indica que el argumento se pasa como parámetro de salida (§15.6.2.3.4). Una variable se considera asignada definitivamente (§9.4) después de una invocación de miembro de función en la que la variable se pasa como parámetro de salida.
El formulario determina el modo de paso de parámetros del argumento: valor, entrada, referencia o salida, respectivamente. Sin embargo, como se mencionó anteriormente, un argumento con el modo de paso de valores puede transformarse en uno con el modo de paso de entrada.
Pasar un campo volátil (§15.5.4) como parámetro de entrada, salida o referencia provoca una advertencia, ya que el método invocado no puede tratar el campo como volátil.
12.6.2.2 Parámetros correspondientes
Para cada argumento de una lista de argumentos, debe haber un parámetro correspondiente en el miembro de función o delegado que se invoca.
La lista de parámetros usada en lo siguiente se determina de la siguiente manera:
- En el caso de los métodos virtuales e indexadores definidos en clases, la lista de parámetros se elige de la primera declaración o invalidación del miembro de función que se encuentra al comenzar con el tipo estático del receptor y buscar en sus clases base.
- Para los métodos parciales, se usa la lista de parámetros de la declaración de método parcial de definición.
- Para todos los demás miembros de función y delegados solo hay una lista de parámetros única, que es la que se usa.
La posición de un argumento o parámetro se define como el número de argumentos o parámetros anteriores a él en la lista de argumentos o la lista de parámetros.
Los parámetros correspondientes para los argumentos miembro de función se establecen de la siguiente manera:
- Argumentos de la argument_list de constructores de instancia, métodos, indexadores y delegados:
- Argumento posicional donde un parámetro se produce en la misma posición de la lista de parámetros corresponde a ese parámetro, a menos que el parámetro sea una matriz de parámetros y se invoque el miembro de función en su forma expandida.
- Argumento posicional de un miembro de función con una matriz de parámetros invocada en su forma expandida, que se produce en o después de la posición de la matriz de parámetros en la lista de parámetros, corresponde a un elemento de la matriz de parámetros.
- Un argumento con nombre corresponde al parámetro del mismo nombre en la lista de parámetros.
- Para los indexadores, al invocar el descriptor de acceso set, la expresión especificada como operando derecho del operador de asignación corresponde al parámetro implícito
value
de la declaración del descriptor de acceso set.
- En el caso de las propiedades, al invocar el descriptor de acceso get no hay argumentos. Al invocar el descriptor de acceso set, la expresión especificada como operando derecho del operador de asignación corresponde al parámetro de valor implícito de la declaración del descriptor de acceso set.
- Para los operadores unarios definidos por el usuario (incluidas las conversiones), el operando único corresponde al parámetro único de la declaración de operador.
- Para los operadores binarios definidos por el usuario, el operando izquierdo corresponde al primer parámetro y el operando derecho corresponde al segundo parámetro de la declaración de operador.
- Un argumento sin nombre corresponde a ningún parámetro cuando es después de un argumento con nombre fuera de posición o un argumento con nombre que corresponde a una matriz de parámetros.
Nota: Esto impide
void M(bool a = true, bool b = true, bool c = true);
queM(c: false, valueB);
. El primer argumento se usa fuera de posición (el argumento se usa en la primera posición, pero el parámetro denominadoc
está en tercera posición), por lo que se debe nombrar los argumentos siguientes. En otras palabras, los argumentos con nombre no finales solo se permiten cuando el nombre y la posición dan como resultado encontrar el mismo parámetro correspondiente. nota final
12.6.2.3 Evaluación en tiempo de ejecución de listas de argumentos
Durante el procesamiento en tiempo de ejecución de una invocación de miembro de función (§12.6.6), las expresiones o referencias de variables de una lista de argumentos se evalúan en orden, de izquierda a derecha, como se indica a continuación:
Para un argumento value, si el modo de paso del parámetro es value
la expresión de argumento se evalúa y se realiza una conversión implícita (§10.2) al tipo de parámetro correspondiente. El valor resultante se convierte en el valor inicial del parámetro value en la invocación del miembro de función.
De lo contrario, el modo de paso del parámetro es input. Si el argumento es una referencia de variable y existe una conversión de identidad (§10.2.2) entre el tipo del argumento y el tipo del parámetro, la ubicación de almacenamiento resultante se convierte en la ubicación de almacenamiento representada por el parámetro en la invocación del miembro de función. De lo contrario, se crea una ubicación de almacenamiento con el mismo tipo que el del parámetro correspondiente. La expresión de argumento se evalúa y se realiza una conversión implícita (§10.2) al tipo de parámetro correspondiente. El valor resultante se almacena dentro de esa ubicación de almacenamiento. Esa ubicación de almacenamiento se representa mediante el parámetro de entrada en la invocación del miembro de función.
Ejemplo: dadas las siguientes declaraciones y llamadas a métodos:
static void M1(in int p1) { ... } int i = 10; M1(i); // i is passed as an input argument M1(i + 5); // transformed to a temporary input argument
En la
M1(i)
llamada al método,i
se pasa como argumento de entrada, porque se clasifica como una variable y tiene el mismo tipoint
que el parámetro de entrada. En laM1(i + 5)
llamada al método, se crea una variable sinint
nombre, se inicializa con el valor del argumento y, a continuación, se pasa como argumento de entrada. Consulte §12.6.4.2 y §12.6.4.4.ejemplo final
Para un argumento de entrada, salida o referencia, la referencia de variable se evalúa y la ubicación de almacenamiento resultante se convierte en la ubicación de almacenamiento representada por el parámetro en la invocación del miembro de función. Para un argumento de entrada o referencia, la variable se asignará definitivamente en el punto de la llamada al método. Si la referencia de variable se proporciona como argumento de salida o es un elemento de matriz de un reference_type, se realiza una comprobación en tiempo de ejecución para asegurarse de que el tipo de elemento de la matriz es idéntico al tipo del parámetro . Si se produce un error en esta comprobación, se produce una
System.ArrayTypeMismatchException
excepción .
Nota: Esta comprobación en tiempo de ejecución es necesaria debido a la covarianza de matriz (§17.6). nota final
Ejemplo: en el código siguiente
class Test { static void F(ref object x) {...} static void Main() { object[] a = new object[10]; object[] b = new string[10]; F(ref a[0]); // Ok F(ref b[1]); // ArrayTypeMismatchException } }
la segunda invocación de
F
hace que se produzca unSystem.ArrayTypeMismatchException
elemento porque el tipo de elemento real deb
esstring
y noobject
es .ejemplo final
Los métodos, indizadores y constructores de instancia pueden declarar su parámetro más adecuado para que sea una matriz de parámetros (§15.6.2.4). Estos miembros de función se invocan en su forma normal o en su forma expandida según cuál sea aplicable (§12.6.4.2):
- Cuando se invoca un miembro de función con una matriz de parámetros en su forma normal, el argumento proporcionado para la matriz de parámetros será una expresión única que sea implícitamente convertible (§10.2) al tipo de matriz de parámetros. En este caso, la matriz de parámetros actúa exactamente como un parámetro de valor.
- Cuando se invoca un miembro de función con una matriz de parámetros en su forma expandida, la invocación especificará cero o más argumentos posicionales para la matriz de parámetros, donde cada argumento es una expresión que es implícitamente convertible (§10.2) al tipo de elemento de la matriz de parámetros. En este caso, la invocación crea una instancia del tipo de matriz de parámetros con una longitud correspondiente al número de argumentos, inicializa los elementos de la instancia de matriz con los valores de argumento especificados y usa la instancia de matriz recién creada como argumento real.
Las expresiones de una lista de argumentos siempre se evalúan en orden textual.
Ejemplo: Por lo tanto, el ejemplo
class Test { static void F(int x, int y = -1, int z = -2) => Console.WriteLine($"x = {x}, y = {y}, z = {z}"); static void Main() { int i = 0; F(i++, i++, i++); F(z: i++, x: i++); } }
genera el resultado
x = 0, y = 1, z = 2 x = 4, y = -1, z = 3
ejemplo final
Cuando se invoca un miembro de función con una matriz de parámetros en su forma expandida con al menos un argumento expandido, la invocación se procesa como si una expresión de creación de matriz con un inicializador de matriz (§12.8.17.5) se insertara alrededor de los argumentos expandidos. Se pasa una matriz vacía cuando no hay argumentos para la matriz de parámetros; no se especifica si la referencia pasada es a una matriz vacía recién asignada o existente.
Ejemplo: Dada la declaración
void F(int x, int y, params object[] args);
las siguientes invocaciones del formato expandido del método
F(10, 20, 30, 40); F(10, 20, 1, "hello", 3.0);
corresponde exactamente a
F(10, 20, new object[] { 30, 40 }); F(10, 20, new object[] { 1, "hello", 3.0 });
ejemplo final
Cuando se omiten argumentos de un miembro de función con los parámetros opcionales correspondientes, los argumentos predeterminados de la declaración de miembro de función se pasan implícitamente. (Esto puede implicar la creación de una ubicación de almacenamiento, como se ha descrito anteriormente).
Nota: Dado que siempre son constantes, su evaluación no afectará a la evaluación de los argumentos restantes. nota final
12.6.3 Inferencia de tipos
12.6.3.1 General
Cuando se llama a un método genérico sin especificar argumentos de tipo, un proceso de inferencia de tipos intenta inferir argumentos de tipo para la llamada. La presencia de la inferencia de tipos permite usar una sintaxis más conveniente para llamar a un método genérico y permite al programador evitar especificar información de tipo redundante.
Ejemplo:
class Chooser { static Random rand = new Random(); public static T Choose<T>(T first, T second) => rand.Next(2) == 0 ? first : second; } class A { static void M() { int i = Chooser.Choose(5, 213); // Calls Choose<int> string s = Chooser.Choose("apple", "banana"); // Calls Choose<string> } }
A través de la inferencia de tipos, los argumentos
int
de tipo ystring
se determinan de los argumentos al método .ejemplo final
La inferencia de tipos se produce como parte del procesamiento en tiempo de enlace de una invocación de método (§12.8.10.2) y tiene lugar antes del paso de resolución de sobrecarga de la invocación. Cuando se especifica un grupo de métodos determinado en una invocación de método y no se especifica ningún argumento de tipo como parte de la invocación de método, la inferencia de tipos se aplica a cada método genérico del grupo de métodos. Si la inferencia de tipos se realiza correctamente, los argumentos de tipo inferido se usan para determinar los tipos de argumentos para la resolución de sobrecarga posterior. Si la resolución de sobrecarga elige un método genérico como el que se va a invocar, los argumentos de tipo inferido se usan como argumentos de tipo para la invocación. Si se produce un error en la inferencia de tipos para un método determinado, ese método no participa en la resolución de sobrecargas. El error de inferencia de tipo, en y de sí mismo, no provoca un error en tiempo de enlace. Sin embargo, a menudo conduce a un error en tiempo de enlace cuando la resolución de sobrecarga no encuentra ningún método aplicable.
Si cada argumento proporcionado no corresponde exactamente a un parámetro del método (§12.6.2.2), o si hay un parámetro no opcional sin ningún argumento correspondiente, se produce un error inmediatamente en la inferencia. De lo contrario, supongamos que el método genérico tiene la siguiente firma:
Tₑ M<X₁...Xᵥ>(T₁ p₁ ... Tₓ pₓ)
Con una llamada de método del formulario M(E₁ ...Eₓ)
, la tarea de inferencia de tipo es buscar argumentos de tipo únicos para cada uno de los parámetros S₁...Sᵥ
X₁...Xᵥ
de tipo para que la llamada M<S₁...Sᵥ>(E₁...Eₓ)
sea válida.
El proceso de inferencia de tipos se describe a continuación como un algoritmo. Un compilador conforme se puede implementar mediante un enfoque alternativo, siempre que alcance el mismo resultado en todos los casos.
Durante el proceso de inferencia, cada parámetro Xᵢ
de tipo se fija en un tipo Sᵢ
determinado o no se fija con un conjunto asociado de límites. Cada uno de los límites es de algún tipo T
. Inicialmente, cada variable Xᵢ
de tipo está sin fijar con un conjunto vacío de límites.
La inferencia de tipos tiene lugar en fases. Cada fase intentará deducir argumentos de tipo para más variables de tipo en función de los resultados de la fase anterior. La primera fase realiza algunas inferencias iniciales de límites, mientras que la segunda fase corrige variables de tipo a tipos específicos e infiere más límites. Es posible que la segunda fase tenga que repetirse varias veces.
Nota: La inferencia de tipos también se usa en otros contextos, incluida la conversión de grupos de métodos (§12.6.3.14) y la búsqueda del mejor tipo común de un conjunto de expresiones (§12.6.3.15). nota final
12.6.3.2 La primera fase
Para cada uno de los argumentos Eᵢ
del método :
- Si
Eᵢ
es una función anónima, se realiza una inferencia de tipo de parámetro explícita (§12.6.3.8) deEᵢ
a .Tᵢ
- De lo contrario, si
Eᵢ
tiene un tipoU
y el parámetro correspondiente es un parámetro de valor (§15.6.2.2), se realizaU
Tᵢ
una inferencia de límite inferior (§12.6.3.10). - De lo contrario, si
Eᵢ
tiene un tipoU
y 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.9) desdeU
a.Tᵢ
- De lo contrario, si
Eᵢ
tiene un tipoU
y el parámetro correspondiente es un parámetro de entrada (§15.6.2.3.2) yEᵢ
es un argumento de entrada, se realizaTᵢ
U
una inferencia exacta (§12.6.3.9). - De lo contrario, si
Eᵢ
tiene un tipoU
y el parámetro correspondiente es un parámetro de entrada (§15.6.2.3.2), se realizaTᵢ
U
una inferencia de límite inferior (§12.6.3.10). - De lo contrario, no se realiza ninguna inferencia para este argumento.
12.6.3.3 La segunda fase
La segunda fase continúa de la siguiente manera:
- Todas las variables de tipo sin fijar que no dependen de (§12.6.3.6)
Xₑ
son fijas (§12.6.3.12Xᵢ
). - Si no existen estas variables de tipo, se fijan todas las variables
Xᵢ
de tipo sin fijar para las que se mantienen todas las siguientes operaciones:- Hay al menos una variable
Xₑ
de tipo que depende deXᵢ
Xᵢ
tiene un conjunto no vacío de límites
- Hay al menos una variable
- Si no existen estas variables de tipo y todavía hay variables de tipo sin fijar , se produce un error en la inferencia de tipos.
- De lo contrario, si no existen variables de tipo sin fijar, la inferencia de tipos se realiza correctamente.
- De lo contrario, para todos los argumentos
Eᵢ
con el tipoTᵢ
de parámetro correspondiente donde 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ᵢ
Tᵢ
una inferencia de tipo de salida (§12.6.3.7). A continuación, se repite la segunda fase.
12.6.3.4 Tipos de entrada
Si E
es un grupo de métodos o una función anónima con tipo implícito y T
es un tipo delegado o tipo de árbol de expresión, todos los tipos de parámetro de son tipos de T
entrada deE
tipo con tipo .T
12.6.3.5 Tipos de salida
Si E
es un grupo de métodos o una función anónima y T
es un tipo de delegado o tipo de árbol de expresión, el tipo de valor devuelto de es un tipo de T
salida deE
con tipo .T
12.6.3.6 Dependencia
Una variable Xᵢ
de tipo sin fijar depende directamente de una variable Xₑ
de tipo sin fijar si para algún argumento Eᵥ
con tipo Tᵥ
Xₑ
se produce en un tipo de entrada de Eᵥ
con tipo Tᵥ
y Xᵢ
se produce en un tipo de salida de Eᵥ
con tipo Tᵥ
.
Xₑ
DependeXᵢ
si Xₑ
depende directamente deXᵢ
o si Xᵢ
depende directamenteXᵥ
de y Xᵥ
depende deXₑ
. Por lo tanto, "depende" es el cierre transitivo pero no reflexivo de "depende directamente".
12.6.3.7 Inferencias de tipo de salida
Una inferencia de tipo de salida se realiza desde una expresión E
a un tipo T de la siguiente manera:
- Si
E
es una función anónima con tipoU
de valor devuelto inferido (§12.6.3.13) yT
es 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.10) desdeU
a.Tₓ
- De lo contrario, si
E
es un grupo de métodos yT
es un tipo delegado o tipo de árbol de expresión con tiposT₁...Tᵥ
de parámetros y tipoTₓ
de valor devuelto , y la resolución de sobrecarga deE
con los tiposT₁...Tᵥ
produce un único método con tipoU
de valor devuelto y, a continuación, se realiza una inferencia de límite inferior desdeU
a .Tₓ
- De lo contrario, si
E
es una expresión con el tipoU
, se realiza una inferencia de límite inferior desdeU
aT
. - De lo contrario, no se realizan inferencias.
12.6.3.8 Inferencias de tipo de parámetro explícito
Una inferencia de tipo de parámetro explícita se realiza desde una expresión E
a un tipo T
de la siguiente manera:
- Si
E
es una función anónima explícitamente tipada con tiposU₁...Uᵥ
de parámetros yT
es 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.9) se realiza deUᵢ
a la correspondiente .Vᵢ
12.6.3.9 Inferencias exactas
Se realiza una inferencia exacta de un tipo U
a un tipo V
de la siguiente manera:
- Si
V
es uno de los sinfijosXᵢ
,U
se agrega al conjunto de límites exactos paraXᵢ
. - De lo contrario, los conjuntos
V₁...Vₑ
yU₁...Uₑ
se determinan comprobando si se aplica alguno de los casos siguientes:V
es un tipoV₁[...]
de matriz yU
es un tipoU₁[...]
de matriz del mismo rango.V
es el tipoV₁?
yU
es el tipoU₁
V
es un tipoC<V₁...Vₑ>
construido yU
es un tipo construidoC<U₁...Uₑ>
Si alguno de estos casos se aplica, se realiza una inferencia exacta de cadaUᵢ
una a la correspondienteVᵢ
.
- De lo contrario, no se realizan inferencias.
12.6.3.10 Inferencias enlazadas a inferior
Una inferencia de límite inferior de un tipo U
a un tipo V
se realiza de la siguiente manera:
- Si
V
es uno de los sinfijosXᵢ
,U
se agrega al conjunto de límites inferiores paraXᵢ
. - De lo contrario, si
V
es el tipoV₁?
yU
es el tipoU₁?
, se realiza una inferencia de límite inferior deU₁
aV₁
. - De lo contrario, los conjuntos
U₁...Uₑ
yV₁...Vₑ
se determinan comprobando si se aplica alguno de los casos siguientes:V
es un tipoV₁[...]
de matriz yU
es un tipoU₁[...]
de matriz del mismo rango.V
es uno de los tipos de matriz unidimensional ,IReadOnlyList<V₁>>
ICollection<V₁>
,IReadOnlyCollection<V₁>
oIList<V₁>
yU
es un tipo deIEnumerable<V₁>
matriz unidimensional.U₁[]
V
es un tipoC<V₁...Vₑ>
construidoclass
,struct
interface
odelegate
y hay un tipoC<U₁...Uₑ>
único de modo queU
(o, siU
es un tipoparameter
, su clase base efectiva o cualquier miembro de su conjunto de interfaz efectivo) es idéntico a,inherits
desde (directa o indirectamente), o implementa (directa o indirectamente).C<U₁...Uₑ>
- (La restricción de "unicidad" significa que, en la interfaz
C<T>{} class U: C<X>, C<Y>{}
de mayúsculas y minúsculas, no se realiza ninguna inferencia cuando se deduce deU
aC<T>
porqueU₁
podría serX
oY
).
Si alguno de estos casos se aplica, se realiza una inferencia de cadaUᵢ
a la correspondienteVᵢ
como se indica a continuación: - Si
Uᵢ
no se sabe que es un tipo de referencia, se realiza una inferencia exacta. - De lo contrario, si
U
es un tipo de matriz, se realiza una inferencia de límite inferior. - De lo contrario, si
V
esC<V₁...Vₑ>
entonces la inferencia depende deli-th
parámetro de tipo deC
:- Si es covariante, se realiza una inferencia de límite inferior.
- Si es contravariante, se realiza una inferencia de límite superior.
- Si es invariable, se realiza una inferencia exacta.
- De lo contrario, no se realizan inferencias.
12.6.3.11 Inferencias enlazadas superior
Una inferencia de límite superior de un tipo U
a un tipo V
se realiza de la siguiente manera:
- Si
V
es uno de los sinfijosXᵢ
,U
se agrega al conjunto de límites superiores paraXᵢ
. - De lo contrario, los conjuntos
V₁...Vₑ
yU₁...Uₑ
se determinan comprobando si se aplica alguno de los casos siguientes:U
es un tipoU₁[...]
de matriz yV
es un tipoV₁[...]
de matriz del mismo rango.U
es uno de los tipos de matriz unidimensional ,IReadOnlyList<Uₑ>
ICollection<Uₑ>
,IReadOnlyCollection<Uₑ>
oIList<Uₑ>
yV
es un tipo deIEnumerable<Uₑ>
matriz unidimensional.Vₑ[]
U
es el tipoU1?
yV
es el tipoV1?
U
se construye la clase, estructura, interfaz o tipoC<U₁...Uₑ>
delegado yV
es unclass, struct, interface
tipo odelegate
que esidentical
,inherits
desde (directa o indirectamente) o implementa (directa o indirectamente) un tipo único.C<V₁...Vₑ>
- (La restricción de "unicidad" significa que, dado una interfaz
C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}
, no se realiza ninguna inferencia cuando se deduce deC<U₁>
aV<Q>
. Las inferencias no se realizan desdeU₁
a niX<Q>
Y<Q>
.
Si alguno de estos casos se aplica, se realiza una inferencia de cadaUᵢ
a la correspondienteVᵢ
como se indica a continuación: - Si
Uᵢ
no se sabe que es un tipo de referencia, se realiza una inferencia exacta. - De lo contrario, si
V
es un tipo de matriz, se realiza una inferencia de límite superior. - De lo contrario, si
U
esC<U₁...Uₑ>
entonces la inferencia depende deli-th
parámetro de tipo deC
:- Si es covariante, se realiza una inferencia de límite superior.
- Si es contravariante, se realiza una inferencia de límite inferior.
- Si es invariable, se realiza una inferencia exacta.
- De lo contrario, no se realizan inferencias.
Corrección 12.6.3.12
Una variable Xᵢ
de tipo sinfijo con un conjunto de límites se fija de la siguiente manera:
- El conjunto de tipos
Uₑ
candidatos comienza como el conjunto de todos los tipos del conjunto de límites paraXᵢ
. - Cada límite de se
Xᵢ
examina a su vez: para cada U enlazado exacto deXᵢ
todos los tiposUₑ
que no son idénticos aU
se quitan del conjunto candidato. Para cada límiteU
inferior deXᵢ
todos los tiposUₑ
a los que no hay una conversión implícita deU
se quitan del conjunto candidato. Para cada U de límite superior deXᵢ
todos los tiposUₑ
desde los que no hay una conversión implícita aU
se quitan del conjunto candidato. - Si entre los tipos
Uₑ
candidatos restantes hay un tipoV
único al que hay una conversión implícita de todos los demás tipos candidatos,Xᵢ
se fija enV
. - De lo contrario, se produce un error en la inferencia de tipos.
12.6.3.13 Tipo de valor devuelto inferido
El tipo de valor devuelto inferido de una función F
anónima se usa durante la inferencia de tipos y la resolución de sobrecarga. El tipo de valor devuelto inferido solo se puede determinar para una función anónima en la que se conocen todos los tipos de parámetro, ya sea porque se proporcionan explícitamente a través de una conversión de función anónima o se deduce durante la inferencia de tipos en una invocación de método genérico envolvente.
El tipo de valor devuelto efectivo inferido se determina de la siguiente manera:
- Si el cuerpo de
F
es una expresión que tiene un tipo, el tipo de valor devuelto efectivo inferido deF
es el tipo de esa expresión. - Si el cuerpo de
F
es un bloque y el conjunto de expresiones de las instrucciones delreturn
bloque tiene un tipoT
común (§12.6.3.15), el tipo de valor devuelto efectivo inferido deF
esT
. - De lo contrario, no se puede deducir un tipo de valor devuelto efectivo para
F
.
El tipo de valor devuelto inferido se determina de la siguiente manera:
- Si
F
es asincrónico y el cuerpo de es una expresión clasificada como nada (§12.2) o un bloque donde ningunareturn
instrucción tiene expresiones, el tipo deF
valor devuelto inferido es«TaskType»
(§15.15.1). - Si
F
es asincrónico y tiene un tipoT
de valor devuelto efectivo inferido , el tipo de valor devuelto inferido es«TaskType»<T>»
(§15.15.1). - Si
F
no es asincrónico y tiene un tipoT
de valor devuelto efectivo inferido , el tipo de valor devuelto inferido esT
. - De lo contrario, no se puede deducir un tipo de valor devuelto para
F
.
Ejemplo: Como ejemplo de inferencia de tipo que implica funciones anónimas, considere el método de
Select
extensión declarado en laSystem.Linq.Enumerable
clase :namespace System.Linq { public static class Enumerable { public static IEnumerable<TResult> Select<TSource,TResult>( this IEnumerable<TSource> source, Func<TSource,TResult> selector) { foreach (TSource element in source) { yield return selector(element); } } } }
Suponiendo que el
System.Linq
espacio de nombres se importó con unausing namespace
directiva y, dado una claseCustomer
con unaName
propiedad de tipostring
, elSelect
método se puede usar para seleccionar los nombres de una lista de clientes:List<Customer> customers = GetCustomerList(); IEnumerable<string> names = customers.Select(c => c.Name);
La invocación del método de extensión (§12.8.10.3) de se procesa mediante la reescritura de
Select
la invocación a una invocación de método estático:IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);
Dado que los argumentos de tipo no se especificaron explícitamente, la inferencia de tipos se usa para inferir los argumentos de tipo. En primer lugar, el argumento customers está relacionado con el parámetro de origen, lo que deduce
TSource
que esCustomer
. A continuación, mediante el proceso de inferencia de tipos de función anónimo descrito anteriormente,c
se da el tipoCustomer
y la expresiónc.Name
está relacionada con el tipo de valor devuelto del parámetro selector, inferenciaTResult
para que seastring
. Por lo tanto, la invocación es equivalente aSequence.Select<Customer,string>(customers, (Customer c) => c.Name)
y el resultado es de tipo
IEnumerable<string>
.En el ejemplo siguiente se muestra cómo la inferencia de tipos de función anónima permite que la información de tipo "fluya" entre argumentos en una invocación de método genérico. Dado el método y la invocación siguientes:
class A { static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2) { return f2(f1(value)); } static void M() { double hours = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalHours); } }
La inferencia de tipos para la invocación continúa de la siguiente manera: En primer lugar, el argumento "1:15:30" está relacionado con el parámetro value, inferencia
X
para que sea cadena. A continuación, el parámetro de la primera función anónima,s
, recibe el tipostring
inferido y la expresiónTimeSpan.Parse(s)
está relacionada con el tipo de valor devuelto def1
, inferirY
que esSystem.TimeSpan
. Por último, el parámetro de la segunda función anónima,t
, recibe el tipoSystem.TimeSpan
inferido y la expresiónt.TotalHours
está relacionada con el tipo de valor devuelto def2
, inferirZ
que debe serdouble
. Por lo tanto, el resultado de la invocación es de tipodouble
.ejemplo final
12.6.3.14 Inferencia de tipos para la conversión de grupos de métodos
De forma similar a las llamadas de métodos genéricos, la inferencia de tipos también se aplicará cuando un grupo M
de métodos que contenga un método genérico se convierta en un tipo D
delegado determinado (§10.8). Dado un método
Tₑ M<X₁...Xᵥ>(T₁ x₁ ... Tₑ xₑ)
y el grupo M
de métodos que se asigna al tipo delegado la tarea de inferencia de tipo D
es buscar argumentos S₁...Sᵥ
de tipo para que la expresión:
M<S₁...Sᵥ>
se vuelve compatible (§20.2) con D
.
A diferencia del algoritmo de inferencia de tipos para las llamadas a métodos genéricos, en este caso, solo hay tipos de argumento, sin expresiones de argumento. En concreto, no hay funciones anónimas y, por tanto, no es necesario realizar varias fases de inferencia.
En su lugar, todos se consideran sin fijar y se realiza una inferencia de límite inferior de a cada tipo Uₑ
de argumento del tipo de D
parámetro Tₑ
correspondiente de M
.Xᵢ
Si para cualquiera de los límites no se encontraron, se produce un error en la Xᵢ
inferencia de tipos. De lo contrario, todos Xᵢ
se fijan en correspondientes Sᵢ
, que son el resultado de la inferencia de tipos.
12.6.3.15 Búsqueda del mejor tipo común de un conjunto de expresiones
En algunos casos, es necesario inferir un tipo común para un conjunto de expresiones. En concreto, los tipos de elementos de matrices con tipo implícito y los tipos devueltos de funciones anónimas con cuerpos de bloque se encuentran de esta manera.
El mejor tipo común para un conjunto de expresiones E₁...Eᵥ
se determina de la siguiente manera:
- Se introduce una nueva variable
X
de tipo sin fijar. - Para cada expresión
Ei
, se realiza una inferencia de tipo de salida (§12.6.3.7) desde ella aX
. X
es fijo (§12.6.3.12), si es posible, y el tipo resultante es el mejor tipo común.- De lo contrario, se produce un error en la inferencia.
Nota: De forma intuitiva, esta inferencia equivale a llamar a un método
void M<X>(X x₁ ... X xᵥ)
con comoEᵢ
argumentos e inferencia .X
nota final
12.6.4 Resolución de sobrecarga
12.6.4.1 General
La resolución de sobrecargas es un mecanismo de tiempo de enlace para seleccionar el mejor miembro de función para invocar dado una lista de argumentos y un conjunto de miembros de función candidatos. La resolución de sobrecarga selecciona el miembro de función que se va a invocar en los siguientes contextos distintos en C#:
- Invocación de un método denominado en un invocation_expression (§12.8.10).
- Invocación de un constructor de instancia denominado en un object_creation_expression (§12.8.17.2).
- Invocación de un descriptor de acceso de indexador a través de un element_access (§12.8.12).
- Invocación de un operador predefinido o definido por el usuario al que se hace referencia en una expresión (§12.4.4 y §12.4.5).
Cada uno de estos contextos define el conjunto de miembros de función candidata y la lista de argumentos de su propia manera única. Por ejemplo, el conjunto de candidatos para una invocación de método no incluye métodos marcados como invalidación (§12.5) y los métodos de una clase base no son candidatos si se aplica algún método de una clase derivada (§12.8.10.2).
Una vez identificados los miembros de la función candidata y la lista de argumentos, la selección del mejor miembro de función es la misma en todos los casos:
- En primer lugar, el conjunto de miembros de función candidatos se reduce a los miembros de función aplicables con respecto a la lista de argumentos especificada (§12.6.4.2). Si este conjunto reducido está vacío, se produce un error en tiempo de compilación.
- A continuación, se encuentra el mejor miembro de función del conjunto de miembros de función candidatos aplicables. Si el conjunto contiene solo un miembro de función, ese miembro de función es el mejor miembro de función. De lo contrario, el mejor miembro de función es el miembro de función que es mejor que todos los demás miembros de función con respecto a la lista de argumentos dada, siempre que cada miembro de función se compare con todos los demás miembros de función mediante las reglas de §12.6.4.3. Si no hay exactamente un miembro de función que sea mejor que todos los demás miembros de función, la invocación del miembro de función es ambigua y se produce un error en tiempo de enlace.
Las subclases siguientes definen los significados exactos de los términos miembro de función aplicable y un miembro de función mejor.
12.6.4.2 Miembro de función aplicable
Se dice que un miembro de función es un miembro de función aplicable con respecto a una lista A
de argumentos cuando se cumplen todas las siguientes condiciones:
- Cada argumento de
A
corresponde a un parámetro de la declaración de miembro de función tal como se describe en §12.6.2.2, como máximo, un argumento corresponde a cada parámetro y cualquier parámetro al que no se corresponda ningún argumento es un parámetro opcional. - Para cada argumento de
A
, el modo de paso de parámetros del argumento es idéntico al modo de paso de parámetros del parámetro correspondiente y- para un parámetro de valor o una matriz de parámetros, existe una conversión implícita (§10.2) desde la expresión de argumento al tipo del parámetro correspondiente o
- para un parámetro de referencia o salida, hay una conversión de identidad entre el tipo de la expresión de argumento (si existe) y el tipo del parámetro correspondiente, o bien
- para un parámetro de entrada cuando el argumento correspondiente tiene el
in
modificador , hay una conversión de identidad entre el tipo de la expresión de argumento (si existe) y el tipo del parámetro correspondiente, o bien - para un parámetro de entrada cuando el argumento correspondiente omite el
in
modificador, existe una conversión implícita (§10.2) desde la expresión de argumento al tipo del parámetro correspondiente.
Para un miembro de función que incluya una matriz de parámetros, si el miembro de función es aplicable por las reglas anteriores, se dice que es aplicable en su forma normal. Si un miembro de función que incluye una matriz de parámetros no es aplicable en su forma normal, el miembro de función podría ser aplicable en su forma expandida:
- El formulario expandido se construye reemplazando la matriz de parámetros de la declaración de miembro de función por cero o más parámetros de valor del tipo de elemento de la matriz de parámetros, de modo que el número de argumentos de la lista
A
de argumentos coincide con el número total de parámetros. SiA
tiene menos argumentos que el número de parámetros fijos en la declaración del miembro de función, la forma expandida del miembro de función no se puede construir y, por tanto, no es aplicable. - De lo contrario, el formulario expandido es aplicable si para cada argumento de
A
, se cumple una de las siguientes condiciones:- el modo de paso de parámetros del argumento es idéntico al modo de paso de parámetros del parámetro correspondiente y
- para un parámetro de valor fijo o un parámetro de valor creado por la expansión, existe una conversión implícita (§10.2) desde la expresión de argumento hasta el tipo del parámetro correspondiente, o bien
- para un parámetro por referencia, el tipo de la expresión de argumento es idéntico al tipo del parámetro correspondiente.
- el modo de paso de parámetros del argumento es value y el modo de paso de parámetros del parámetro correspondiente es input y existe una conversión implícita (§10.2) desde la expresión de argumento al tipo del parámetro correspondiente.
- 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 de argumento al tipo de parámetro de un parámetro de entrada es una conversión implícita dinámica (§10.2.10), los resultados no están definidos.
Ejemplo: dadas las siguientes declaraciones y llamadas a métodos:
public static void M1(int p1) { ... } public static void M1(in int p1) { ... } public static void M2(in int p1) { ... } public static void Test() { int i = 10; uint ui = 34U; M1(in i); // M1(in int) is applicable M1(in ui); // no exact type match, so M1(in int) is not applicable M1(i); // M1(int) and M1(in int) are applicable M1(i + 5); // M1(int) and M1(in int) are applicable M1(100u); // no implicit conversion exists, so M1(int) is not applicable M2(in i); // M2(in int) is applicable M2(i); // M2(in int) is applicable M2(i + 5); // M2(in int) is applicable }
ejemplo final
- Un método estático solo es aplicable si el grupo de métodos da como resultado un simple_name o un member_access a través de un tipo.
- Un método de instancia solo es aplicable si el grupo de métodos da como resultado un simple_name, un member_access a través de una variable o un valor, o un base_access.
- Si el grupo de métodos resulta de un simple_name, un método de instancia solo es aplicable si
this
se permite el acceso §12.8.14.
- Si el grupo de métodos resulta de un simple_name, un método de instancia solo es aplicable si
- Cuando el grupo de métodos resulta de un member_access que podría ser a través de una instancia o de un tipo, tal como se describe en §12.8.7.2, se aplican los métodos estáticos y de instancia.
- Un método genérico cuyos argumentos de tipo (especificados explícitamente o inferidos) no satisfacen todas sus restricciones no son aplicables.
- En el contexto de una conversión de grupo de métodos, existirá una conversión de identidad (§10.2.2) o una conversión de referencia implícita (§10.2.8) desde el tipo de valor devuelto del método al tipo de valor devuelto del delegado. De lo contrario, el método candidato no es aplicable.
12.6.4.3 Miembro de función Better
Para determinar el miembro de función mejor, se construye una lista A
de argumentos eliminados que contiene solo las expresiones de argumento en el orden en que aparecen en la lista de argumentos original y se excluyen los out
argumentos o ref
.
Las listas de parámetros para cada uno de los miembros de función candidatos se construyen de la siguiente manera:
- El formulario expandido se usa si el miembro de función solo se aplica en el formulario expandido.
- Los parámetros opcionales sin argumentos correspondientes se quitan de la lista de parámetros
- Los parámetros de referencia y salida se quitan de la lista de parámetros
- Los parámetros se reordenan para que se produzcan en la misma posición que el argumento correspondiente en la lista de argumentos.
Dada una lista A
de argumentos con un conjunto de expresiones de argumento y dos miembros Mᵥ
de función aplicables {E₁, E₂, ..., Eᵥ}
y Mₓ
con tipos {P₁, P₂, ..., Pᵥ}
de parámetro y {Q₁, Q₂, ..., Qᵥ}
, Mᵥ
se define para ser un miembro de función mejor que Mₓ
si
- para cada argumento, la conversión implícita de a
Qᵥ
Eᵥ
no es mejor que la conversión implícita 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 y sean equivalentes {P₁, P₂, ..., Pᵥ}
(es decir, cada una Pᵢ
tiene una conversión de identidad a la correspondiente Qᵢ
), se aplican las siguientes reglas de desempate, para determinar el miembro de función mejor.{Q₁, Q₂, ..., Qᵥ}
- Si
Mᵢ
es un método no genérico yMₑ
es un método genérico,Mᵢ
es mejor queMₑ
. - De lo contrario, si
Mᵢ
es aplicable en su forma normal yMₑ
tiene una matriz params y solo es aplicable en su forma expandida,Mᵢ
entonces es mejor queMₑ
. - De lo contrario, si ambos métodos tienen matrices de parámetros y solo son aplicables en sus formularios expandidos, y si la matriz de parámetros de
Mᵢ
tiene menos elementos que la matriz de parámetros deMₑ
, entoncesMᵢ
es mejor queMₑ
. - De lo contrario, si
Mᵥ
tiene tipos de parámetro más específicos queMₓ
,Mᵥ
es mejor queMₓ
. Deje y{S1, S2, ..., Sn}
represente{R1, R2, ..., Rn}
los tipos de parámetros no fundamentados y noexpandados deMᵥ
yMₓ
.Mᵥ
Los tipos de parámetro de son más específicos queMₓ
s si, para cada parámetro,Rx
no es menos específico queSx
y, para al menos un parámetro,Rx
es 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 argumento de tipo correspondiente en el otro.
- Un tipo de matriz es más específico que otro tipo de matriz (con el mismo número de dimensiones) si el tipo de elemento del primero es más específico que el tipo de elemento del segundo.
- De lo contrario, si un miembro es un operador no elevado y el otro es un operador elevado, el no elevado es mejor.
- Si no se encontró que ninguno de los miembros de función es mejor y todos los parámetros de
Mᵥ
tienen un argumento correspondiente, mientras que los argumentos predeterminados deben sustituirse por al menos un parámetro opcional enMₓ
, entoncesMᵥ
es mejor queMₓ
. - Si para al menos un parámetro
Mᵥ
usa la mejor opción de paso de parámetros (§12.6.4.4) que el parámetro correspondiente enMₓ
y ninguno de los parámetros enMₓ
que se usa la mejor opción de paso de parámetros queMᵥ
,Mᵥ
es mejor queMₓ
. - De lo contrario, no es mejor ningún miembro de función.
12.6.4.4 Mejor modo de paso de parámetros
Se permite que los parámetros correspondientes en dos métodos sobrecargados solo difieren en el modo de paso de parámetros siempre que uno de los dos parámetros tenga el modo de paso de valores, como se indica a continuación:
public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }
Dado int i = 10;
, según §12.6.4.2, las llamadas M1(i)
y M1(i + 5)
dan lugar a que ambas sobrecargas sean aplicables. En tales casos, el método con el modo de paso de parámetros de valor es la mejor opción del modo de paso de parámetros.
Nota: No es necesario que exista dicha opción para los argumentos de los modos de paso de entrada, salida o referencia, ya que esos argumentos solo coinciden con los mismos modos de paso de parámetros exactos. nota final
12.6.4.5 Mejor conversión de la expresión
Dada una conversión C₁
implícita que convierte de una expresión E
a un tipo T₁
y una conversión implícita que convierte de una expresión E
a un tipo T₂
, C₁
es una conversiónC₂
mejor que C₂
si una de las siguientes contiene:
E
T₁
coincide exactamente yE
no coincideT₂
exactamente (§12.6.4.6)E
coincide exactamente con o ninguno deT₁
y , yT₁
es un destino de conversión mejor queT₂
(§12.6.4.7T₂
)E
es un grupo de métodos (§12.2),T₁
es compatible (§20.4) con el mejor método del grupo de métodos para la conversió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 coincidente exactamente
Dada una expresión E
y un tipo T
, E
coincide T
exactamente con si una de las siguientes suspensiones:
E
tiene un tipoS
y existe una conversión de identidad deS
a .T
E
es una función anónima,T
es un tipo delegado o un tipoD
Expression<D>
de árbol de expresión y una de las siguientes suspensiones:- Existe un tipo
X
de valor devuelto inferido paraE
en el contexto de la lista de parámetros deD
(§12.6.3.12) y existe una conversión de identidad desdeX
hasta el tipo de valor devuelto deD
E
es unaasync
expresión lambda sin valor devuelto yD
tiene un tipo de valor devuelto que es un no genérico.«TaskType»
- No
E
es asincrónico yD
tiene un tipoY
de valor devuelto oE
es asincrónico yD
tiene un tipo«TaskType»<Y>
de valor devuelto (§15.15.1) y una de las siguientes suspensiones:- El cuerpo de
E
es una expresión que coincide exactamente conY
- El cuerpo de
E
es un bloque donde cada instrucción de devolución devuelve una expresión que coincide exactamente conY
- El cuerpo de
- Existe un tipo
12.6.4.7 Mejor destino de conversión
Dados dos tipos y , es un destino de conversión mejor que T₂
si uno de los siguientes contiene: T₁
T₂
T₁
- Existe una conversión implícita de
T₁
aT₂
y no existe ninguna conversión implícita deT₂
aT₁
. T₁
es«TaskType»<S₁>
(§15.15.1),T₂
es«TaskType»<S₂>
, yS₁
es un destino de conversión mejor queS₂
T₁
is«TaskType»<S₁>
(§15.15.1),T₂
es , yT₁
es«TaskType»<S₂>
más especializado queT₂
T₁
es oS₁?
dondeS₁
esS₁
un tipo entero con signo yT₂
esS₂
oS₂?
dondeS₂
es un tipo entero sin signo. En concreto:S₁
es yS₂
esbyte
sbyte
,ushort
,uint
oulong
S₁
es yS₂
esushort
short
,uint
oulong
S₁
es yS₂
esuint
int
, oulong
S₁
eslong
yS₂
esulong
12.6.4.8 Sobrecargado en clases genéricas
Nota: Aunque las firmas declaradas deben ser únicas (§8.6), es posible que la sustitución de argumentos de tipo produzca firmas idénticas. En tal situación, la resolución de sobrecarga elegirá la más específica (§12.6.4.3) de las firmas originales (antes de la sustitución de argumentos de tipo), si existe y, de lo contrario, notificará un error. nota final
Ejemplo: en los ejemplos siguientes se muestran sobrecargas que son válidas y no válidas según esta regla:
public interface I1<T> { ... } public interface I2<T> { ... } public abstract class G1<U> { public abstract int F1(U u); // Overload resolution for G<int>.F1 public abstract int F1(int i); // will pick non-generic public abstract void F2(I1<U> a); // Valid overload public abstract void F2(I2<U> a); } abstract class G2<U,V> { public abstract void F3(U u, V v); // Valid, but overload resolution for public abstract void F3(V v, U u); // G2<int,int>.F3 will fail public abstract void F4(U u, I1<V> v); // Valid, but overload resolution for public abstract void F4(I1<V> v, U u); // G2<I1<int>,int>.F4 will fail public abstract void F5(U u1, I1<V> v2); // Valid overload public abstract void F5(V v1, U u2); public abstract void F6(ref U u); // Valid overload public abstract void F6(out V v); }
ejemplo final
12.6.5 Comprobación en tiempo de compilación de la invocación de miembros dinámicos
Aunque la resolución de sobrecarga de una operación enlazada dinámicamente tiene lugar en tiempo de ejecución, a veces es posible en tiempo de compilación conocer la lista de miembros de función desde los que se elegirá una sobrecarga:
- Para una invocación de delegado (§12.8.10.4), la lista es un miembro de función único con la misma lista de parámetros que el delegate_type de la invocación.
- Para una invocación de método (§12.8.10.2) en un tipo o en un valor cuyo tipo estático no es dinámico, el conjunto de métodos accesibles en el grupo de métodos se conoce en tiempo de compilación.
- Para una expresión de creación de objetos (§12.8.17.2), el conjunto de constructores accesibles en el tipo se conoce en tiempo de compilación.
- Para un acceso de indexador (§12.8.12.3), el conjunto de indexadores accesibles en el receptor se conoce en tiempo de compilación.
En estos casos, se realiza una comprobación limitada en tiempo de compilación en cada miembro del conjunto conocido de miembros de función, para ver si se puede saber que no se puede invocar nunca en tiempo de ejecución. Para cada miembro F
de función se construye un parámetro modificado y una lista de argumentos:
- En primer lugar, si
F
se proporciona un método genérico y argumentos de tipo, se sustituyen por los parámetros de tipo de la lista de parámetros. Sin embargo, si no se proporcionaron argumentos de tipo, no se produce dicha sustitución. - A continuación, cualquier parámetro cuyo tipo esté abierto (es decir, contiene un parámetro de tipo; vea §8.4.3) se elided, junto con sus parámetros correspondientes.
Para F
pasar la comprobación, todas las siguientes tendrán:
- La lista de parámetros modificado para
F
es aplicable a la lista de argumentos modificados en términos de §12.6.4.2. - Todos los tipos construidos de la lista de parámetros modificados cumplen sus restricciones (§8.4.5).
- Si los parámetros de tipo de
F
se sustituyeron en el paso anterior, se cumplen sus restricciones. - Si
F
es un método estático, el grupo de métodos no habrá resultado de un member_access cuyo receptor se conoce en tiempo de compilación para ser una variable o valor. - Si
F
es un método de instancia, el grupo de métodos no habrá resultado de un member_access cuyo receptor se conoce en tiempo de compilación para ser un tipo.
Si ningún candidato supera esta prueba, se produce un error en tiempo de compilación.
12.6.6 Invocación de miembro de función
12.6.6.1 General
Esta subclausa describe el proceso que tiene lugar en tiempo de ejecución para invocar un miembro de función determinado. Se supone que un proceso de tiempo de enlace ya ha determinado el miembro determinado que se va a invocar, posiblemente aplicando la resolución de sobrecarga a un conjunto de miembros de función candidatos.
Para describir el proceso de invocación, los miembros de función se dividen en dos categorías:
- Miembros de función estáticos. Estos son métodos estáticos, descriptores de acceso de propiedad estáticos y operadores definidos por el usuario. Los miembros de función estáticos siempre no son virtuales.
- Miembros de la función de instancia. Estos son métodos de instancia, constructores de instancia, descriptores de acceso de propiedad de instancia y descriptores de acceso del indexador. Los miembros de la función de instancia son no virtuales o virtuales y siempre se invocan en una instancia determinada. La instancia se calcula mediante una expresión de instancia y se vuelve accesible dentro del miembro de función como
this
(§12.8.14). Para un constructor de instancia, se toma la expresión de instancia para que sea el objeto recién asignado.
El procesamiento en tiempo de ejecución de una invocación de miembro de función consta de los pasos siguientes, donde M
es el miembro de función y, si M
es un miembro de instancia, E
es la expresión de instancia:
Si
M
es un miembro de función estática:- La lista de argumentos se evalúa como se describe en §12.6.2.
- Se invoca a
M
.
De lo contrario, si el tipo de es un tipo de
E
valor yM
se declara o invalida enV
:V
E
se evalúa. Si esta evaluación produce una excepción, no se ejecutan más pasos. Para un constructor de instancia, esta evaluación consta de asignar almacenamiento (normalmente desde una pila de ejecución) para el nuevo objeto. En este casoE
se clasifica como una variable.- Si
E
no se clasifica como una variable, o siV
no es un tipo de estructura de solo lectura (§16.2.2), yE
es uno de los siguientes:- un parámetro de entrada (§15.6.2.3.2) o
- un
readonly
campo (§15.5.3) o - una
readonly
variable de referencia o un valor devuelto (§9.7),
a continuación, se crea una variable local temporal del
E
tipo y el valor deE
se asigna a esa variable.E
A continuación, se vuelve a clasificar como referencia a esa variable local temporal. La variable temporal es accesible comothis
dentroM
de , pero no de ninguna otra manera. Por lo tanto, solo cuandoE
se puede escribir es posible que el autor de la llamada observe los cambios queM
realiza enthis
.- La lista de argumentos se evalúa como se describe en §12.6.2.
- Se invoca a
M
. La variable a la que haceE
referencia se convierte en la variable a la que hacethis
referencia .
De lo contrario:
E
se evalúa. Si esta evaluación produce una excepción, no se ejecutan más pasos.- La lista de argumentos se evalúa como se describe en §12.6.2.
- Si el tipo de
E
es un value_type, se realiza una conversión boxing (§10.2.9) para convertirE
a un class_type yE
se considera que es de ese class_type en los pasos siguientes. Si el value_type es un enum_type, el class_type esSystem.Enum;
de lo contrario, esSystem.ValueType
. - Se comprueba que el valor de
E
es válido. Si el valor deE
es NULL, se produce unaSystem.NullReferenceException
excepción y no se ejecutan más pasos. - La implementación del miembro de función que se va a invocar se determina:
- Si el tipo de tiempo de enlace de
E
es una interfaz, el miembro de función que se va a invocar es la implementación de proporcionada por el tipo en tiempo deM
ejecución de la instancia a la que haceE
referencia . Este miembro de función se determina aplicando las reglas de asignación de interfaz (§18.6.5) para determinar la implementación deM
proporcionada por el tipo en tiempo de ejecución de la instancia a la que haceE
referencia . - De lo contrario, si
M
es un miembro de función virtual, el miembro de función que se va a invocar es la implementación de proporcionada por el tipo en tiempo deM
ejecución de la instancia a la que haceE
referencia . Este miembro de función se determina aplicando las reglas para determinar la implementación más derivada (§15.6.4) deM
con respecto al tipo en tiempo de ejecución de la instancia a la que haceE
referencia . - De lo contrario,
M
es un miembro de función no virtual y el miembro de función que se va a invocar esM
el mismo.
- Si el tipo de tiempo de enlace de
- Se invoca la implementación del miembro de función determinada en el paso anterior. El objeto al que hace
E
referencia se convierte en el objeto al que hace referencia.
El resultado de la invocación de un constructor de instancia (§12.8.17.2) es el valor creado. El resultado de la invocación de cualquier otro miembro de función es el valor, si existe, devuelto (§13.10.5) desde su cuerpo.
12.6.6.2 Invocaciones en instancias boxed
Un miembro de función implementado en un value_type se puede invocar a través de una instancia boxeada de ese value_type en las situaciones siguientes:
- Cuando el miembro de función es una invalidación de un método heredado del tipo class_type y se invoca a través de una expresión de instancia de ese class_type.
Nota: El class_type siempre será uno de
System.Object
,System.ValueType
oSystem.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 función se invoca a través de un delegado.
En estas situaciones, la instancia boxed se considera que contiene una variable de la value_type y esta variable se convierte en la variable a la que hace referencia dentro de la invocación del miembro de función.
Nota: En concreto, esto significa que cuando se invoca un miembro de función en una instancia boxed, es posible que el miembro de función modifique el valor contenido en la instancia boxed. nota final
12.7 Deconstrucción
Deconstrucción es un proceso por el que una expresión se convierte en una tupla de expresiones individuales. La deconstrucción se usa cuando el destino de una asignación simple es una expresión de tupla, para obtener valores que se asignan a cada uno de los elementos de esa tupla.
Una expresión se desconstruye en una expresión E
de tupla con n
elementos de la siguiente manera:
- Si
E
es una expresión de tupla conn
elementos , el resultado de la deconstrucción es la propia expresiónE
. - De lo contrario, si
E
tiene un tipo(T1, ..., Tn)
de tupla conn
elementos,E
se evalúa en una variable__v
temporal y el resultado de la deconstrucción es la expresión(__v.Item1, ..., __v.Itemn)
. - De lo contrario, si la expresión
E.Deconstruct(out var __v1, ..., out var __vn)
se resuelve en tiempo de compilación en una instancia única o método de extensión, esa expresión se evalúa y el resultado de la deconstrucción es la expresión(__v1, ..., __vn)
. Este método se conoce como deconstructor. - De lo contrario,
E
no se puede desconstruir.
Aquí, __v
y __v1, ..., __vn
hacen referencia a variables temporales invisibles y inaccesibles.
Nota: No se puede construir una expresión de tipo
dynamic
. nota final
12.8 Expresiones principales
12.8.1 General
Las expresiones principales incluyen las formas más sencillas de expresiones.
primary_expression
: primary_no_array_creation_expression
| array_creation_expression
;
primary_no_array_creation_expression
: literal
| interpolated_string_expression
| simple_name
| parenthesized_expression
| tuple_expression
| member_access
| null_conditional_member_access
| invocation_expression
| element_access
| null_conditional_element_access
| this_access
| base_access
| post_increment_expression
| post_decrement_expression
| null_forgiving_expression
| object_creation_expression
| delegate_creation_expression
| anonymous_object_creation_expression
| typeof_expression
| sizeof_expression
| checked_expression
| unchecked_expression
| default_value_expression
| nameof_expression
| anonymous_method_expression
| pointer_member_access // unsafe code support
| pointer_element_access // unsafe code support
| stackalloc_expression
;
Nota: Estas reglas gramaticales no están listas para ANTLR, ya que forman parte de un conjunto de reglas recursivas mutuamente a la izquierda (
primary_expression
,primary_no_array_creation_expression
,member_access
,element_access
invocation_expression
,post_increment_expression
,post_decrement_expression
, ,null_forgiving_expression
pointer_member_access
ypointer_element_access
) que ANTLR no controla. Las técnicas estándar se pueden usar para transformar la gramática para quitar la recursividad mutua a la izquierda. Esto no se ha hecho, ya que no todas las estrategias de análisis lo requieren (por ejemplo, un analizador LALR no) y hacerlo ofuscaría la estructura y la descripción. nota final
pointer_member_access (§23.6.3) y pointer_element_access (§23.6.4) solo están disponibles en código no seguro (§23).
Las expresiones principales se dividen entre array_creation_expressions y primary_no_array_creation_expressions. Tratar array_creation_expression de esta manera, en lugar de enumerarlo junto con las otras formas de expresión simple, permite que la gramática no permita código potencialmente confuso, como
object o = new int[3][1];
que, de lo contrario, se interpretaría como
object o = (new int[3])[1];
12.8.2 Literales
Un primary_expression que consta de un literal (§6.4.5) se clasifica como un valor.
12.8.3 Expresiones de cadena interpoladas
Un interpolated_string_expression consta de , $@
o @$
, seguido inmediatamente de texto dentro "
de $
caracteres. Dentro del texto entrecomillado hay cero o más interpolaciones delimitadas por {
caracteres y}
, cada una de las cuales incluye una expresión y especificaciones de formato opcionales.
Las expresiones de cadena interpoladas tienen dos formas; regular (interpolated_regular_string_expression) y textual (interpolated_verbatim_string_expression); que son léxicamente similares a, pero se diferencian semánticamente de las dos formas de literales de cadena (§6.4.5.6).
interpolated_string_expression
: interpolated_regular_string_expression
| interpolated_verbatim_string_expression
;
// interpolated regular string expressions
interpolated_regular_string_expression
: Interpolated_Regular_String_Start Interpolated_Regular_String_Mid?
('{' regular_interpolation '}' Interpolated_Regular_String_Mid?)*
Interpolated_Regular_String_End
;
regular_interpolation
: expression (',' interpolation_minimum_width)?
Regular_Interpolation_Format?
;
interpolation_minimum_width
: constant_expression
;
Interpolated_Regular_String_Start
: '$"'
;
// the following three lexical rules are context sensitive, see details below
Interpolated_Regular_String_Mid
: Interpolated_Regular_String_Element+
;
Regular_Interpolation_Format
: ':' Interpolated_Regular_String_Element+
;
Interpolated_Regular_String_End
: '"'
;
fragment Interpolated_Regular_String_Element
: Interpolated_Regular_String_Character
| Simple_Escape_Sequence
| Hexadecimal_Escape_Sequence
| Unicode_Escape_Sequence
| Open_Brace_Escape_Sequence
| Close_Brace_Escape_Sequence
;
fragment Interpolated_Regular_String_Character
// Any character except " (U+0022), \\ (U+005C),
// { (U+007B), } (U+007D), and New_Line_Character.
: ~["\\{}\u000D\u000A\u0085\u2028\u2029]
;
// interpolated verbatim string expressions
interpolated_verbatim_string_expression
: Interpolated_Verbatim_String_Start Interpolated_Verbatim_String_Mid?
('{' verbatim_interpolation '}' Interpolated_Verbatim_String_Mid?)*
Interpolated_Verbatim_String_End
;
verbatim_interpolation
: expression (',' interpolation_minimum_width)?
Verbatim_Interpolation_Format?
;
Interpolated_Verbatim_String_Start
: '$@"'
| '@$"'
;
// the following three lexical rules are context sensitive, see details below
Interpolated_Verbatim_String_Mid
: Interpolated_Verbatim_String_Element+
;
Verbatim_Interpolation_Format
: ':' Interpolated_Verbatim_String_Element+
;
Interpolated_Verbatim_String_End
: '"'
;
fragment Interpolated_Verbatim_String_Element
: Interpolated_Verbatim_String_Character
| Quote_Escape_Sequence
| Open_Brace_Escape_Sequence
| Close_Brace_Escape_Sequence
;
fragment Interpolated_Verbatim_String_Character
: ~["{}] // Any character except " (U+0022), { (U+007B) and } (U+007D)
;
// lexical fragments used by both regular and verbatim interpolated strings
fragment Open_Brace_Escape_Sequence
: '{{'
;
fragment Close_Brace_Escape_Sequence
: '}}'
;
Seis de las reglas léxicas definidas anteriormente distinguen el contexto de la siguiente manera:
Regla | Requisitos contextuales |
---|---|
Interpolated_Regular_String_Mid | Solo se reconoce después de un Interpolated_Regular_String_Start, entre las interpolaciones siguientes y antes del Interpolated_Regular_String_End correspondiente. |
Regular_Interpolation_Format | Solo se reconoce dentro de un regular_interpolation y cuando los dos puntos iniciales (:) no están anidados dentro de ningún tipo de corchete (paréntesis,llaves/cuadrado). |
Interpolated_Regular_String_End | Solo se reconoce después de un Interpolated_Regular_String_Start y solo si los tokens intermedios son Interpolated_Regular_String_Mids o tokens que pueden formar parte de regular_interpolations, incluidos los tokens de cualquier interpolated_regular_string_expressioncontenido en dichas interpolaciones. |
Interpolated_Verbatim_String_Mid Verbatim_Interpolation_Format Interpolated_Verbatim_String_End | El reconocimiento de estas tres reglas sigue al de las reglas correspondientes anteriores con cada regla de gramática regular mencionada reemplazada por la correspondiente textual. |
Nota: Las reglas anteriores distinguen el contexto, ya que sus definiciones se superponen con las de otros tokens en el idioma. nota final
Nota: La gramática anterior no está lista para ANTLR debido a las reglas léxicas contextuales. Al igual que con otros generadores de lexer, ANTLR admite reglas léxicas contextuales, por ejemplo usando sus modos léxicos, pero se trata de un detalle de implementación y, por tanto, no forma parte de esta especificación. nota final
Un interpolated_string_expression se clasifica como un valor. Si se convierte inmediatamente en System.IFormattable
o System.FormattableString
con una conversión implícita de cadena interpolada (§10.2.5), la expresión de cadena interpolada tiene ese tipo. De lo contrario, tiene el tipo string
.
Nota: Las diferencias entre los posibles tipos que puede determinar un interpolated_string_expression se pueden determinar de la documentación de
System.String
(§C.2) 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 un string
objeto según el formato especificado por el Regular_Interpolation_Format o Verbatim_Interpolation_Format, o según un formato predeterminado para el tipo de expresión. A continuación, el interpolation_minimum_width modifica la cadena con formato para generar la final string
que se va a interpolar en el interpolated_string_expression.
Nota: Cómo se determina el formato predeterminado de un tipo en la documentación de
System.String
(§C.2) 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 el constant_expression tendrá una conversión implícita a int
. Deje que el ancho del campo sea el valor absoluto de este constant_expression y la alineación sea el signo (positivo o negativo) del valor de este constant_expression:
- Si el valor del ancho del campo es menor o igual que la longitud de la cadena con formato, la cadena con formato no se modifica.
- De lo contrario, la cadena con formato se rellena con caracteres de espacio en blanco para que su longitud sea igual al ancho del campo:
- Si la alineación es positiva, la cadena con formato está alineada a la derecha con el relleno,
- De lo contrario, está alineado a la izquierda anexando el relleno.
El significado general de un interpolated_string_expression, incluido el formato y el relleno anteriores de interpolaciones, se define mediante una conversión de la expresión a una invocación de método: si el tipo de la expresión es System.IFormattable
o System.FormattableString
ese método es System.Runtime.CompilerServices.FormattableStringFactory.Create
(§C.3) que devuelve un valor de tipo System.FormattableString
; de lo contrario, el tipo será string
y el método es string.Format
(§C.2) que devuelve un valor de tipo string
.
En ambos casos, la lista de argumentos de la llamada consta de un literal de cadena de formato con especificaciones de formato para cada interpolación y un argumento para cada expresión correspondiente a las especificaciones de formato.
El literal de cadena de formato se construye de la manera siguiente, donde N
es el número de interpolaciones en el interpolated_string_expression. El literal de cadena de formato consta de, en orden:
- Caracteres del Interpolated_Regular_String_Start o Interpolated_Verbatim_String_Start
- Caracteres del Interpolated_Regular_String_Mid o Interpolated_Verbatim_String_Mid, si existe
- A continuación, si
N ≥ 1
para cada númeroI
de0
aN-1
:- Especificación de marcador de posición:
- Carácter de llave izquierda (
{
) - Representación decimal de
I
- A continuación, si el regular_interpolation o verbatim_interpolation correspondiente tiene un interpolation_minimum_width, una coma (
,
) seguida de la representación decimal del valor del constant_expression - Caracteres del Regular_Interpolation_Format o Verbatim_Interpolation_Format, si existe, del regular_interpolation o verbatim_interpolation correspondientes.
- Un carácter de llave derecha (
}
)
- 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 existe
- Especificación de marcador de posición:
- Por último, los caracteres del Interpolated_Regular_String_End o Interpolated_Verbatim_String_End.
Los argumentos siguientes son las expresionesde las interpolaciones, si las hay, en orden.
Cuando un interpolated_string_expression contiene varias interpolaciones, las expresiones de esas interpolaciones se evalúan en orden textual de izquierda a derecha.
Ejemplo:
En este ejemplo se usan las siguientes características de especificación de formato:
- la
X
especificación de formato que da formato a enteros como hexadecimal mayúsculas, - el formato predeterminado de un
string
valor es el propio valor, - valores de alineación positivos que justifican a la derecha dentro del ancho mínimo de campo especificado,
- valores de alineación negativos que se justifican a la izquierda dentro del ancho mínimo de campo especificado,
- constantes definidas para la interpolation_minimum_width y
}}
y{{
tienen el formato{
y}
respectivamente.
Con estas premisas:
string text = "red";
int number = 14;
const int width = -4;
Después:
Expresión de cadena interpolada | Significado equivalente como string |
Valor |
---|---|---|
$"{text}" |
string.Format("{0}", text) |
"red" |
$"{{text}}" |
string.Format("{{text}}) |
"{text}" |
$"{ text , 4 }" |
string.Format("{0,4}", text) |
" red" |
$"{ text , width }" |
string.Format("{0,-4}", text) |
"red " |
$"{number:X}" |
string.Format("{0:X}", number) |
"E" |
$"{text + '?'} {number % 3}" |
string.Format("{0} {1}", text + '?', number % 3) |
"red? 2" |
$"{text + $"[{number}]"}" |
string.Format("{0}", text + string.Format("[{0}]", number)) |
"red[14]" |
$"{(number==0?"Zero":"Non-zero")}" |
string.Format("{0}", (number==0?"Zero":"Non-zero")) |
"Non-zero" |
ejemplo final
12.8.4 Nombres simples
Un simple_name consta de un identificador, seguido opcionalmente de una lista de argumentos de tipo:
simple_name
: identifier type_argument_list?
;
Un simple_name es de la forma I
o del formulario I<A₁, ..., Aₑ>
, donde I
es un identificador único y I<A₁, ..., Aₑ>
es un type_argument_list opcional. Cuando no se especifica ningún type_argument_list , considere la posibilidad e
de ser cero. El simple_name se evalúa y clasifica de la siguiente manera:
- Si
e
es cero y el simple_name aparece dentro de un espacio de declaración de variable local (§7.3) que contiene directamente una variable local, un parámetro o una constante con el nombreI
, el simple_name hace referencia a esa variable local, parámetro o constante y se clasifica como una variable o valor. - Si
e
es cero y el simple_name aparece dentro de una declaración de método genérico, pero fuera de los atributos de su method_declaration, y si esa declaración incluye un parámetro de tipo con nombreI
, el simple_name hace referencia a ese parámetro de tipo. - De lo contrario, para cada tipo
T
de instancia (§15.3.2), empezando por el tipo de instancia de la declaración de tipo envolvente inmediatamente y continuando con el tipo de instancia de cada clase o declaración de estructura envolvente (si existe):- Si
e
es cero y la declaración de incluye un parámetro deT
tipo con el nombreI
, el simple_name hace referencia a ese parámetro de tipo. - De lo contrario, si una búsqueda de miembros (§12.5) de en
T
cone
argumentos deI
tipo genera una coincidencia:- Si
T
es el tipo de instancia de la clase o estructura envolvente inmediatamente y la búsqueda identifica uno o varios métodos, el resultado es un grupo de métodos con una expresión de instancia asociada dethis
. Si se especificó una lista de argumentos de tipo, se usa para llamar a un método genérico (§12.8.10.2). - De lo contrario, si es el tipo de instancia de la clase o estructura envolvente inmediatamente, si la búsqueda identifica un miembro de instancia y, si
T
la referencia se produce dentro del bloque de un constructor de instancia, un método de instancia o un descriptor de acceso de instancia (§12.2.1), el resultado es el mismo que un acceso de miembro (§12.8.7) del formulariothis.I
. Esto solo puede ocurrir cuandoe
es cero. - De lo contrario, el resultado es el mismo que un acceso de miembro (§12.8.7) del formulario
T.I
oT.I<A₁, ..., Aₑ>
.
- Si
- Si
- De lo contrario, para cada espacio de nombres
N
, empezando por el espacio de nombres en el que se produce el simple_name , continuando con cada espacio de nombres envolvente (si existe) y finalizando con el espacio de nombres global, se evalúan los pasos siguientes hasta que se encuentra una entidad:- Si
e
es cero yI
es el nombre de un espacio de nombres enN
, entonces:- Si la ubicación en la que se produce el simple_name se incluye entre una declaración de espacio de nombres para
N
y la declaración de espacio de nombres contiene una extern_alias_directive o using_alias_directive que asocia el nombreI
a un espacio de nombres o tipo, el simple_name es ambiguo y se produce un error en tiempo de compilación. - De lo contrario, el simple_name hace referencia al espacio de nombres denominado
I
enN
.
- Si la ubicación en la que se produce el simple_name se incluye entre una declaración de espacio de nombres para
- De lo contrario, si
N
contiene un tipo accesible que tiene parámetros de nombreI
ye
tipo, a continuación:- Si
e
es cero y la ubicación en la que se produce el simple_name se incluye en una declaración de espacio de nombres paraN
y la declaración de espacio de nombres contiene un extern_alias_directive o using_alias_directive que asocia el nombreI
a un espacio de nombres o tipo, el simple_name es ambiguo y se produce un error en tiempo de compilación. - De lo contrario, el namespace_or_type_name hace referencia al tipo construido con los argumentos de tipo especificados.
- Si
- De lo contrario, si la ubicación en la que se produce el simple_name se incluye en una declaración de espacio de nombres para
N
:- Si
e
es cero y la declaración de espacio de nombres contiene un extern_alias_directive o using_alias_directive que asocia el nombreI
a un espacio de nombres o tipo importados, el simple_name hace referencia a ese espacio de nombres o tipo. - De lo contrario, si los espacios de nombres importados por los using_namespace_directives de la declaración de espacio de nombres contienen exactamente un tipo que tiene parámetros de nombre
I
ye
tipo, el simple_name hace referencia a ese tipo construido con los argumentos de tipo especificados. - De lo contrario, si los espacios de nombres importados por los using_namespace_directives de la declaración de espacio de nombres contienen más de un tipo que tiene parámetros de nombre
I
ye
tipo, el simple_name es ambiguo y se produce un error en tiempo de compilación.
- Si
Nota: Este paso completo es exactamente paralelo al paso correspondiente en el procesamiento de un namespace_or_type_name (§7.8). nota final
- Si
- De lo contrario, si
e
es cero yI
es el identificador_
, el simple_name es un descarte sencillo, que es una forma de expresión de declaración (§12.17). - De lo contrario, el simple_name no está definido y se produce un error en tiempo de compilación.
12.8.5 Expresiones entre paréntesis
Un parenthesized_expression consta de una expresión entre paréntesis.
parenthesized_expression
: '(' expression ')'
;
Una parenthesized_expression se evalúa mediante la evaluación de la expresión entre paréntesis. Si la expresión entre paréntesis denota un espacio de nombres o un tipo, se produce un error en tiempo de compilación. De lo contrario, el resultado de la parenthesized_expression es el resultado de la evaluación de la expresión contenida.
12.8.6 Expresiones de tupla
Un tuple_expression representa una tupla y consta de dos o más expresiones separadas por comas y, opcionalmente, con nombre entre paréntesis. Un deconstruction_expression es una sintaxis abreviada para una tupla que contiene expresiones de declaración con tipo implícito.
tuple_expression
: '(' tuple_element (',' tuple_element)+ ')'
| deconstruction_expression
;
tuple_element
: (identifier ':')? expression
;
deconstruction_expression
: 'var' deconstruction_tuple
;
deconstruction_tuple
: '(' deconstruction_element (',' deconstruction_element)+ ')'
;
deconstruction_element
: deconstruction_tuple
| identifier
;
Un tuple_expression se clasifica como una tupla.
Una deconstruction_expression var (e1, ..., en)
es abreviada para el tuple_expression (var e1, ..., var en)
y sigue el mismo comportamiento. Esto se aplica de forma recursiva a cualquier deconstruction_tupleanidado en el deconstruction_expression. Cada identificador anidado dentro de un deconstruction_expression introduce así una expresión de declaración (§12.17). Como resultado, un deconstruction_expression solo puede producirse en el lado izquierdo de una asignación simple.
Una expresión de tupla tiene un tipo si y solo si cada una de sus expresiones Ei
de elemento tiene un tipo Ti
. El tipo debe ser un tipo de tupla de la misma aridad que la expresión de tupla, donde cada elemento recibe lo siguiente:
- Si el elemento de tupla en la posición correspondiente tiene un nombre
Ni
, el elemento de tipo de tupla seráTi Ni
. - De lo contrario, si
Ei
tiene el formatoNi
oE.Ni
E?.Ni
, el elemento de tipo de tupla seráTi Ni
, a menos que cualquiera de las siguientes suspensiones:- Otro elemento de la expresión de tupla tiene el nombre
Ni
, o - Otro elemento de tupla sin un nombre tiene una expresión de elemento de tupla del formulario
Ni
oE.Ni
E?.Ni
o , o Ni
es del formatoItemX
, dondeX
es una secuencia de0
dígitos decimales no iniciados que podrían representar la posición de un elemento de tupla yX
no 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 será
Ti
.
Una expresión de tupla se evalúa evaluando cada una de sus expresiones de elemento en orden de izquierda a derecha.
Un valor de tupla se puede obtener de una expresión de tupla si lo convierte en un tipo de tupla (§10.2.13), reclasificándolo como un valor (§12.2.2)) o convirtiéndolo en el destino de una asignación deconstrucción (§12.21.2).
Ejemplo:
(int i, string) t1 = (i: 1, "One"); (long l, string) t2 = (l: 2, null); var t3 = (i: 3, "Three"); // (int i, string) var t4 = (i: 4, null); // Error: no type
En este ejemplo, las cuatro expresiones de tupla son válidas. Los dos
t1
primeros yt2
, no usan el tipo de la expresión de tupla, sino que 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 de2
a ylong
desdenull
astring
. La tercera expresión de tupla tiene un tipo(int i, string)
y, por tanto, se puede reclasificar como un valor de ese tipo. La declaración det4
, por otro lado, es un error: la expresión de tupla no tiene ningún tipo porque su segundo elemento no tiene ningún tipo.if ((x, y).Equals((1, 2))) { ... };
En este ejemplo se muestra que las tuplas a veces pueden provocar varias capas de paréntesis, especialmente cuando la expresión de tupla es el único argumento para una invocación de método.
ejemplo final
Acceso a miembros 12.8.7
12.8.7.1 General
Un member_access consta de un primary_expression, un predefined_type o un qualified_alias_member, seguido de un token ".
", seguido de un identificador, opcionalmente seguido de un type_argument_list.
member_access
: primary_expression '.' identifier type_argument_list?
| predefined_type '.' identifier type_argument_list?
| qualified_alias_member '.' identifier type_argument_list?
;
predefined_type
: 'bool' | 'byte' | 'char' | 'decimal' | 'double' | 'float' | 'int'
| 'long' | 'object' | 'sbyte' | 'short' | 'string' | 'uint' | 'ulong'
| 'ushort'
;
La qualified_alias_member producción se define en §14.8.
Un member_access es de la forma E.I
o del formulario E.I<A₁, ..., Aₑ>
, donde E
es un primary_expression, predefined_type o qualified_alias_member, I
es un identificador único y <A₁, ..., Aₑ>
es un type_argument_list opcional. Cuando no se especifica ningún type_argument_list , considere la posibilidad e
de ser cero.
Un member_access con un primary_expression de tipo dynamic
está enlazado dinámicamente (§12.3.3). En este caso, el compilador clasifica el acceso de miembro como acceso de propiedad de tipo dynamic
. Las reglas siguientes para determinar el significado del member_access se aplican en tiempo de ejecución mediante el tipo en tiempo de ejecución en lugar del tipo en tiempo de compilación del primary_expression. Si esta clasificación en tiempo de ejecución conduce a un grupo de métodos, el acceso de miembro será el primary_expression de un invocation_expression.
El member_access se evalúa y clasifica de la siguiente manera:
- Si
e
es cero yE
es un espacio de nombres yE
contiene un espacio de nombres anidado con el nombreI
, el resultado es ese espacio de nombres. - De lo contrario, si
E
es un espacio de nombres yE
contiene un tipo accesible que tiene parámetros de nombreI
yK
tipo, el resultado es ese tipo construido con los argumentos de tipo especificados. - Si
E
se clasifica como un tipo, siE
no es un parámetro de tipo y si una búsqueda de miembros (§12.5) de enE
conK
parámetros deI
tipo genera una coincidencia,E.I
se evalúa y clasifica de la siguiente manera:Nota: Cuando el resultado de dicha búsqueda de miembros es un grupo de métodos y
K
es cero, el grupo de métodos puede contener métodos que tienen parámetros de tipo. Esto permite que estos métodos se consideren para la inferencia de argumentos de tipo. nota final- Si
I
identifica un tipo, el resultado es ese tipo construido con cualquier argumento de tipo determinado. - Si
I
identifica uno o varios métodos, el resultado es un grupo de métodos sin ninguna expresión de instancia asociada. - Si
I
identifica una propiedad estática, el resultado es un acceso de propiedad sin ninguna expresión de instancia asociada. - Si
I
identifica un campo estático:- Si el campo es de solo lectura y la referencia se produce fuera del constructor estático de la clase o estructura en la que se declara el campo, el resultado es un valor, es decir, el valor del campo
I
estático enE
. - De lo contrario, el resultado es una variable, es decir, el campo
I
estático enE
.
- 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
- Si
I
identifica un evento estático:- Si la referencia se produce dentro de la clase o estructura en la que se declara el evento y el evento se declaró sin event_accessor_declarations (§15.8.1), se
E.I
procesa exactamente como siI
fuera un campo estático. - De lo contrario, el resultado es un acceso a eventos sin ninguna expresión de instancia asociada.
- Si la referencia se produce dentro de la clase o estructura en la que se declara el evento y el evento se declaró sin event_accessor_declarations (§15.8.1), se
- Si
I
identifica una constante, el resultado es un valor, es decir, el valor de esa constante. - Si
I
identifica un miembro de enumeración, el resultado es un valor, es decir, el valor de ese miembro de enumeración. - De lo contrario,
E.I
es una referencia de miembro no válida y se produce un error en tiempo de compilación.
- Si
- Si
E
es un acceso de propiedad, acceso de indexador, variable o valor, el tipo de que esT
y una búsqueda de miembros (§12.5) de enT
conK
argumentos deI
tipo produce una coincidencia, seE.I
evalúa y clasifica de la siguiente manera:- En primer lugar, si
E
es una propiedad o acceso de indexador, se obtiene el valor de la propiedad o el acceso del indexador (§12.2.2) y E se vuelve a clasificar como un valor. - Si
I
identifica uno o varios métodos, el resultado es un grupo de métodos con una expresión de instancia asociada deE
. - Si
I
identifica una propiedad de instancia, el resultado es un acceso de propiedad con una expresión de instancia asociada deE
y un tipo asociado que es el tipo de la propiedad. SiT
es un tipo de clase, el tipo asociado se elige de la primera declaración o invalidación de la propiedad que se encuentra al comenzar conT
y buscar en sus clases base. - Si
T
es un class_type eI
identifica un campo de instancia de ese class_type:- Si el valor de
E
esnull
, se produce unaSystem.NullReferenceException
excepción . - De lo contrario, si el campo es de solo lectura y la referencia se produce fuera de un constructor de instancia de la clase en la que se declara el campo, el resultado es un valor, es decir, el valor del campo
I
en el objeto al que haceE
referencia . - De lo contrario, el resultado es una variable, es decir, el campo
I
del objeto al que haceE
referencia .
- Si el valor de
- Si
T
es un struct_type eI
identifica un campo de instancia de ese struct_type:- Si
E
es un valor o si el campo es de solo lectura y la referencia se produce fuera de un constructor de instancia del struct en el que se declara el campo, el resultado es un valor, es decir, el valor del campoI
en la instancia de estructura dada porE
. - De lo contrario, el resultado es una variable, es decir, el campo
I
de la instancia de estructura dada porE
.
- Si
- Si
I
identifica un evento de instancia:- Si la referencia se produce dentro de la clase o estructura en la que se declara el evento y el evento se declaró sin event_accessor_declarations (§15.8.1) y la referencia no se produce como el lado izquierdo de
a +=
o-=
operador,E.I
se procesa exactamente como siI
fuera 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 se produce dentro de la clase o estructura en la que se declara el evento y el evento se declaró sin event_accessor_declarations (§15.8.1) y la referencia no se produce como el lado izquierdo de
- En primer lugar, si
- De lo contrario, se intenta procesar
E.I
como invocación de método de extensión (§12.8.10.3). Si se produce un error,E.I
es una referencia de miembro no válida y se produce un error en tiempo de enlace.
12.8.7.2 Nombres simples y nombres de tipo idénticos
En un acceso de miembro del formulario E.I
, si E
es un identificador único y si el significado de E
como un simple_name (§12.8.4) es una constante, campo, propiedad, variable local o parámetro con el mismo tipo que el significado de E
como un type_name (§7.8.1), se permiten ambos significados posibles de E
. La búsqueda de miembros de E.I
nunca es ambigua, ya que I
necesariamente será miembro del tipo E
en ambos casos. En otras palabras, la regla simplemente permite el acceso a los miembros estáticos y los tipos anidados de donde se habría producido un error en tiempo de E
compilación.
Ejemplo:
struct Color { public static readonly Color White = new Color(...); public static readonly Color Black = new Color(...); public Color Complement() => new Color(...); } class A { public «Color» Color; // Field Color of type Color void F() { Color = «Color».Black; // Refers to Color.Black static member Color = Color.Complement(); // Invokes Complement() on Color field } static void G() { «Color» c = «Color».White; // Refers to Color.White static member } }
Solo para fines expositores, dentro de la
A
clase , esas apariciones delColor
identificador que hacen referencia alColor
tipo están delimitadas por«...»
y las que hacen referencia alColor
campo no.ejemplo final
12.8.8 Acceso condicional nulo a miembros
Un null_conditional_member_access es una versión condicional de member_access (§12.8.7) y es un error de hora de enlace si el tipo de resultado es void
. Para obtener una expresión condicional nula donde el tipo de resultado puede verse void
(§12.8.11).
Un null_conditional_member_access consta de un primary_expression seguido de los dos tokens "?
" y ".
", seguidos de un identificador con un type_argument_list opcional, seguido de cero o más dependent_accesses cualquiera de los cuales puede preceedido por un null_forgiving_operator.
null_conditional_member_access
: primary_expression '?' '.' identifier type_argument_list?
(null_forgiving_operator? dependent_access)*
;
dependent_access
: '.' identifier type_argument_list? // member access
| '[' argument_list ']' // element access
| '(' argument_list? ')' // invocation
;
null_conditional_projection_initializer
: primary_expression '?' '.' identifier type_argument_list?
;
Una expresión E
null_conditional_member_access tiene el formato P?.A
. El significado de E
se determina de la siguiente manera:
Si el tipo de es un tipo de
P
valor que acepta valores NULL:Deje que
T
sea el tipo deP.Value.A
.Si
T
es un parámetro de tipo que no se sabe que es un tipo de referencia o un tipo de valor que no acepta valores NULL, se produce un error en tiempo de compilación.Si
T
es un tipo de valor que no acepta valores NULL, el tipo deE
esT?
y el significado deE
es el mismo que el significado de:((object)P == null) ? (T?)null : P.Value.A
Excepto que
P
se evalúa solo una vez.De lo contrario, el tipo de
E
esT
y el significado deE
es el mismo que el significado de:((object)P == null) ? (T)null : P.Value.A
Excepto que
P
se evalúa solo una vez.
De lo contrario:
Vamos
T
a ser el tipo de la expresiónP.A
.Si
T
es un parámetro de tipo que no se sabe que es un tipo de referencia o un tipo de valor que no acepta valores NULL, se produce un error en tiempo de compilación.Si
T
es un tipo de valor que no acepta valores NULL, el tipo deE
esT?
y el significado deE
es el mismo que el significado de:((object)P == null) ? (T?)null : P.A
Excepto que
P
se evalúa solo una vez.De lo contrario, el tipo de
E
esT
y el significado deE
es el mismo que el significado de:((object)P == null) ? (T)null : P.A
Excepto que
P
se evalúa solo una vez.
Nota: En una expresión del formulario:
P?.A₀?.A₁
después, si
P
senull
evalúa como ningunaA₀
de las evaluaciones oA₁
se evalúan. Lo mismo sucede si una expresión es una secuencia de null_conditional_member_access o null_conditional_element_access operaciones §12.8.13 .nota final
Un null_conditional_projection_initializer es una restricción de null_conditional_member_access y tiene la misma semántica. Solo se produce como inicializador de proyección en una expresión de creación de objetos anónimos (§12.8.17.7).
12.8.9 Expresiones que admiten valores NULL
12.8.9.1 General
Un valor, tipo, clasificación (§12.2) y contexto seguro (§16.4.12) es el valor, el tipo, la clasificación y el contexto seguro de su primary_expression.
null_forgiving_expression
: primary_expression null_forgiving_operator
;
null_forgiving_operator
: '!'
;
Nota: Los operadores de negación lógica de prefijos y null postfix (§12.9.4), mientras que representados por el mismo token léxico (!
), son distintos. Solo se puede invalidar este último (§15.10), se fija la definición del operador que admite valores NULL. nota final
Se trata de un error en tiempo de compilación para aplicar el operador null-forgiving más de una vez a la misma expresión, sin perjuicio de paréntesis entre paréntesis.
Ejemplo: lo siguiente no es válido:
var p = q!!; // error: applying null_forgiving_operator more than once var s = ( ( m(t) ! ) )! // error: null_forgiving_operator applied twice to m(t)
ejemplo final
El resto de esta subclausa y las siguientes subclases del mismo nivel son normativas condicionalmente.
Un compilador que realiza el análisis de estado nulo estático (§8.9.5) debe cumplir la especificación siguiente.
El operador null-forgiving es una pseudooperación en tiempo de compilación que se usa para informar al análisis de estado estático de un compilador. Tiene dos usos: para invalidar la determinación de un compilador de que una expresión puede ser nula y para invalidar un compilador que emite una advertencia relacionada con la nulabilidad.
Aplicar el operador null-forgiving a una expresión para la que el análisis de estado null estático de un compilador no genera ninguna advertencia no es un error.
12.8.9.2 Invalidar una determinación "tal vez nula"
En algunas circunstancias, el análisis de estado null estático de un compilador puede determinar que una expresión tiene el estado NULL tal vez null y emitir una advertencia de diagnóstico cuando otra información indica que la expresión no puede ser null. Al aplicar el operador null-forgiving a dicha expresión, se informa al análisis estático de estado NULL del compilador de que el estado NULL no es NULL; lo que impide la advertencia de diagnóstico y puede informar a cualquier análisis en curso.
Ejemplo: Tenga en cuenta lo siguiente:
#nullable enable public static void M() { Person? p = Find("John"); // returns Person? if (IsValid(p)) { Console.WriteLine($"Found {p!.Name}"); // p can't be null } } public static bool IsValid(Person? person) => person != null && person.Name != null;
Si
IsValid
devuelvetrue
,p
se puede desreferenciar de forma segura para tener acceso a suName
propiedad y se puede suprimir la advertencia "desreferenciación de un valor posiblemente NULL" mediante!
.ejemplo final
Ejemplo: El operador null-forgiving debe usarse con precaución, tenga en cuenta lo siguiente:
#nullable enable int B(int? x) { int y = (int)x!; // quash warning, throw at runtime if x is null return y; }
Aquí se aplica el operador null-forgiving a un tipo de valor y se anula cualquier advertencia en
x
. Sin embargo, six
estánull
en tiempo de ejecución, se producirá una excepción, yanull
que no se puede convertir aint
.ejemplo final
12.8.9.3 Invalidación de otras advertencias de análisis null
Además de invalidar quizás determinaciones nulas como antes, puede haber otras circunstancias en las que se desee invalidar la determinación de análisis de estado null estático de un compilador que una expresión requiere una o varias advertencias. Aplicar el operador null-forgiving a estas solicitudes de expresión que el compilador no emite ninguna advertencia para la expresión. En respuesta, un compilador puede optar por no emitir advertencias y también modificar su análisis posterior.
Ejemplo: Tenga en cuenta lo siguiente:
#nullable enable public static void Assign(out string? lv, string? rv) { lv = rv; } public string M(string? t) { string s; Assign(out s!, t ?? "«argument was null»"); return s; }
Los tipos de parámetros del método
Assign
,lv
yrv
, sonstring?
, conlv
ser un parámetro de salida y realiza una asignación simple.El método
M
pasa la variables
, de tipostring
, comoAssign
parámetro de salida, el compilador usó una advertencia, yas
que no es una variable que acepta valores NULL. Dado queAssign
el segundo argumento no puede ser NULL, se usa el operador null forgiving para anular la advertencia.ejemplo final
Fin del texto normativo condicionalmente.
12.8.10 Expresiones de invocación
12.8.10.1 General
Se usa un invocation_expression para invocar un método.
invocation_expression
: primary_expression '(' argument_list? ')'
;
El primary_expression puede ser un null_forgiving_expression si y solo si tiene un delegate_type.
Un invocation_expression se enlaza dinámicamente (§12.3.3) si al menos una de las siguientes suspensiones:
- El primary_expression tiene el tipo
dynamic
en tiempo de compilación . - Al menos un argumento del argument_list opcional tiene el tipo
dynamic
en tiempo de compilación .
En este caso, el compilador clasifica el invocation_expression como un valor de tipo dynamic
. Las reglas siguientes para determinar el significado de la invocation_expression se aplican en tiempo de ejecución, mediante el tipo en tiempo de ejecución en lugar del tipo en tiempo de compilación de los de la primary_expression y los argumentos que tienen el tipo dynamic
en tiempo de compilación . Si el primary_expression no tiene el tipo dynamic
en tiempo de compilación , la invocación del método se somete a una comprobación limitada en tiempo de compilación, tal como se describe en §12.6.5.
El primary_expression de un invocation_expression será un grupo de métodos o un valor de un delegate_type. Si el primary_expression es un grupo de métodos, el invocation_expression es una invocación de método (§12.8.10.2). Si el primary_expression es un valor de un delegate_type, el invocation_expression es una invocación de delegado (§12.8.10.4). Si el primary_expression no es un grupo de métodos ni un valor de un delegate_type, se produce un error en tiempo de enlace.
El argument_list opcional (§12.6.2) proporciona valores o referencias de variables para los parámetros del método.
El resultado de evaluar un invocation_expression se clasifica de la siguiente manera:
- Si el invocation_expression invoca un método returns-no-value (§15.6.1) o un delegado returns-no-value, el resultado no es nada. Una expresión que se clasifica como nada solo se permite en el contexto de un statement_expression (§13.7) o como el cuerpo de un lambda_expression (§12.19). De lo contrario, se produce un error en tiempo de enlace.
- De lo contrario, si el invocation_expression invoca un método return-by-ref (§15.6.1) o un delegado return-by-ref, el resultado es una variable con un tipo asociado del tipo de valor devuelto del método o delegado. Si la invocación es de un método de instancia y el receptor es de un tipo
T
de clase , el tipo asociado se elige de la primera declaración o invalidación del método encontrado al comenzar conT
y buscar en sus clases base. - De lo contrario, el invocation_expression invoca un método return-by-value (§15.6.1) o un delegado devuelto por valor, y el resultado es un valor, con un tipo asociado del tipo de valor devuelto del método o delegado. Si la invocación es de un método de instancia y el receptor es de un tipo
T
de clase , el tipo asociado se elige de la primera declaración o invalidación del método encontrado al comenzar conT
y buscar en sus clases base.
12.8.10.2 Invocaciones del método
Para una invocación de método, el primary_expression del invocation_expression será un grupo de métodos. El grupo de métodos identifica el método que se va a invocar o el conjunto de métodos sobrecargados desde los que elegir un método específico que se va a invocar. En este último caso, la determinación del método específico que se va a invocar se basa en el contexto proporcionado por los tipos de los argumentos del argument_list.
El procesamiento en tiempo de enlace de una invocación de método del formulario M(A)
, donde M
es un grupo de métodos (posiblemente incluido un type_argument_list) y A
es un argument_list opcional, consta de los pasos siguientes:
- El conjunto de métodos candidatos para la invocación del método se construye. Para cada método
F
asociado al grupoM
de métodos :- Si
F
no es genérico,F
es un candidato cuando:M
no tiene ninguna lista de argumentos de tipo yF
es aplicable con respecto aA
(§12.6.4.2).
- Si
F
es genérico yM
no tiene ninguna lista de argumentos de tipo,F
es un candidato cuando:- La inferencia de tipos (§12.6.3) se realiza correctamente, inferir una lista de argumentos de tipo para la llamada y
- Una vez que los argumentos de tipo inferido se sustituyen por los parámetros de tipo de método correspondientes, todos los tipos construidos de la lista de parámetros de
F
cumplen sus restricciones (§8.4.5) y la lista de parámetros deF
es aplicable con respecto aA
(§12.6.4.2)
- Si
F
es genérico eM
incluye una lista de argumentos de tipo,F
es un candidato cuando:F
tiene el mismo número de parámetros de tipo de método que se proporcionaron en la lista de argumentos de tipo y- Una vez que los argumentos de tipo se sustituyen por los parámetros de tipo de método correspondientes, todos los tipos construidos de la lista de parámetros de
F
cumplen sus restricciones (§8.4.5) y la lista de parámetros deF
es aplicable con respecto aA
(§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.F
del conjunto, dondeC
es el tipo en el que se declara el métodoF
, todos los métodos declarados en un tipo base deC
se quitan del conjunto. Además, siC
es un tipo de clase distintoobject
de , todos los métodos declarados en un tipo de interfaz se quitan del conjunto.Nota: Esta última regla solo tiene un efecto cuando el grupo de métodos era el resultado de una búsqueda de miembros en un parámetro de tipo que tiene una clase base efectiva distinta
object
de y un conjunto de interfaz efectivo no vacío. nota final - Si el conjunto resultante de métodos candidatos está vacío, se abandona el procesamiento posterior a lo largo de los pasos siguientes y, en su lugar, se intenta procesar la invocación como invocación de método de extensión (§12.8.10.3). Si se produce un error, no existen métodos aplicables y se produce un error en tiempo de enlace.
- El mejor método del conjunto de métodos candidatos se identifica mediante las reglas de resolución de sobrecarga de §12.6.4. Si no se puede identificar un único método mejor, la invocación del método es ambigua y se produce un error en tiempo de enlace. Al realizar la resolución de sobrecarga, los parámetros de un método genérico se consideran después de sustituir los argumentos de tipo (proporcionados o inferidos) para los parámetros de tipo de método correspondientes.
Una vez que se ha seleccionado y validado un método en tiempo de enlace mediante los pasos anteriores, la invocación en tiempo de ejecución real se procesa según las reglas de invocación de miembro de función descritas en §12.6.6.
Nota: El efecto intuitivo de las reglas de resolución descritas anteriormente es el siguiente: Para buscar el método concreto invocado por una invocación de método, comience con el tipo indicado por la invocación del método y continúe con la cadena de herencia hasta que se encuentre al menos una declaración de método accesible, accesible y sin invalidación. A continuación, realice la inferencia de tipos y la resolución de sobrecargas en el conjunto de métodos aplicables, accesibles y no invalidados declarados en ese tipo e invoque el método seleccionado de este modo. Si no se encontró ningún método, intente procesar la invocación como una invocación de método de extensión. nota final
12.8.10.3 Invocaciones de método de extensión
En una invocación de método (§12.6.6.2) de uno de los formularios
«expr» . «identifier» ( )
«expr» . «identifier» ( «args» )
«expr» . «identifier» < «typeargs» > ( )
«expr» . «identifier» < «typeargs» > ( «args» )
si el procesamiento normal de la invocación no encuentra ningún método aplicable, se intenta procesar la construcción como una invocación de método de extensión. Si «expr» o cualquiera de los «args» tiene el tipo dynamic
en tiempo de compilación, los métodos de extensión no se aplicarán.
El objetivo es encontrar el mejor type_nameC
, de modo que se pueda realizar la invocación del método estático correspondiente:
C . «identifier» ( «expr» )
C . «identifier» ( «expr» , «args» )
C . «identifier» < «typeargs» > ( «expr» )
C . «identifier» < «typeargs» > ( «expr» , «args» )
Un método Cᵢ.Mₑ
de extensión es apto si:
Cᵢ
es una clase no genérica y no anidada- El nombre de
Mₑ
es el identificador Mₑ
es accesible y aplicable cuando se aplica a los argumentos como un método estático, como se muestra anteriormente.- Existe una conversión implícita de identidad, referencia o conversión boxing de expr al tipo del primer parámetro de
Mₑ
.
La búsqueda de C
las ganancias es la siguiente:
- A partir de la declaración de espacio de nombres más cercana, continuando con cada declaración de espacio de nombres envolvente y finalizando con la unidad de compilación contenedora, se realizan intentos sucesivos para buscar un conjunto candidato de métodos de extensión:
- Si el espacio de nombres o la unidad de compilación especificados contiene directamente declaraciones de tipos no genéricos con métodos de extensión aptos
Cᵢ
Mₑ
, el conjunto de esos métodos de extensión es el conjunto candidato. - Si los espacios de nombres importados mediante directivas de espacio de nombres en el espacio de nombres o la unidad de compilación especificados contienen directamente declaraciones de tipo no genéricos con métodos
Mₑ
de extensión aptosCᵢ
, el conjunto de esos métodos de extensión es el conjunto candidato.
- Si el espacio de nombres o la unidad de compilación especificados contiene directamente declaraciones de tipos no genéricos con métodos de extensión aptos
- Si no se encuentra ningún conjunto de candidatos en ninguna declaración de espacio de nombres o unidad de compilación envolvente, se produce un error en tiempo de compilación.
- De lo contrario, la resolución de sobrecarga se aplica al conjunto candidato como se describe en §12.6.4. Si no se encuentra ningún método mejor, se produce un error en tiempo de compilación.
C
es el tipo en el que el mejor método se declara como un método de extensión.
Con C
como destino, la llamada al método se procesa como invocación de método estático (§12.6.6).
Nota: A diferencia de una invocación de método de instancia, no se produce ninguna excepción cuando expr se evalúa como una referencia nula. En su lugar, este
null
valor se pasa al método de extensión, ya que sería a través de una invocación de método estático normal. Es necesario que la implementación del método de extensión decida cómo responder a dicha llamada. nota final
Las reglas anteriores significan que los métodos de instancia tienen prioridad sobre los métodos de extensión, que los métodos de extensión disponibles en las declaraciones de espacio de nombres internos tienen prioridad sobre los métodos de extensión disponibles en declaraciones de espacio de nombres externos y que los métodos de extensión declarados directamente en un espacio de nombres tienen prioridad sobre los métodos de extensión importados en ese mismo espacio de nombres con una directiva de espacio de nombres mediante .
Ejemplo:
public static class E { public static void F(this object obj, int i) { } public static void F(this object obj, string s) { } } class A { } class B { public void F(int i) { } } class C { public void F(object obj) { } } class X { static void Test(A a, B b, C c) { a.F(1); // E.F(object, int) a.F("hello"); // E.F(object, string) b.F(1); // B.F(int) b.F("hello"); // E.F(object, string) c.F(1); // C.F(object) c.F("hello"); // C.F(object) } }
En el ejemplo,
B
el método tiene prioridad sobre el primer método de extensión yC
el método de tiene prioridad sobre ambos métodos de extensión.public static class C { public static void F(this int i) => Console.WriteLine($"C.F({i})"); public static void G(this int i) => Console.WriteLine($"C.G({i})"); public static void H(this int i) => Console.WriteLine($"C.H({i})"); } namespace N1 { public static class D { public static void F(this int i) => Console.WriteLine($"D.F({i})"); public static void G(this int i) => Console.WriteLine($"D.G({i})"); } } namespace N2 { using N1; public static class E { public static void F(this int i) => Console.WriteLine($"E.F({i})"); } class Test { static void Main(string[] args) { 1.F(); 2.G(); 3.H(); } } }
La salida de este ejemplo es:
E.F(1) D.G(2) C.H(3)
D.G
tiene precendece sobreC.G
y tiene prioridad sobre yD.F
C.F
.E.F
ejemplo final
12.8.10.4 Invocaciones de delegado
Para una invocación de delegado, el primary_expression del invocation_expression será un valor de un delegate_type. Además, considerando la delegate_type ser miembro de función con la misma lista de parámetros que el delegate_type, el delegate_type será aplicable (§12.6.4.2) con respecto a la argument_list del invocation_expression.
El procesamiento en tiempo de ejecución de una invocación de delegado del formulario D(A)
, donde D
es un primary_expression de un delegate_type y A
es un argument_list opcional, consta de los pasos siguientes:
D
se evalúa. Si esta evaluación provoca una excepción, no se ejecutan pasos adicionales.- La lista
A
de argumentos se evalúa. Si esta evaluación provoca una excepción, no se ejecutan pasos adicionales. - Se comprueba que el valor de
D
es válido. Si el valor deD
esnull
, se produce unaSystem.NullReferenceException
excepción y no se ejecutan pasos adicionales. - De lo contrario,
D
es una referencia a una instancia de delegado. Las invocaciones de miembro de función (§12.6.6) se realizan en cada una de las entidades invocables de la lista de invocación del delegado. Para las entidades invocables que constan de una instancia y un método de instancia, la instancia de la invocación es la instancia contenida en la entidad invocable.
Consulte §20.6 para obtener más información sobre varias listas de invocación sin parámetros.
12.8.11 Expresión de invocación condicional nula
Un null_conditional_invocation_expression es sintácticamente un null_conditional_member_access (§12.8.8) o null_conditional_element_access (§12.8.13) donde el dependent_access final es una expresión de invocación (§12.8.10).
Un null_conditional_invocation_expression se produce en el contexto de un statement_expression (§13.7), anonymous_function_body (§12.19.1) o method_body (§15.6.1).
A diferencia del null_conditional_member_access o null_conditional_element_access equivalente sintácticamente, un null_conditional_invocation_expression puede clasificarse como nada.
null_conditional_invocation_expression
: null_conditional_member_access null_forgiving_operator? '(' argument_list? ')'
| null_conditional_element_access null_forgiving_operator? '(' argument_list? ')'
;
El null_forgiving_operator opcional puede incluirse si el null_conditional_member_access o null_conditional_element_access solo tiene un delegate_type.
Una expresión null_conditional_invocation_expression tiene el formato P?A
; donde A
es el resto del null_conditional_member_access o null_conditional_element_access equivalente sintácticamente, A
comenzará por .
o [
. E
Supongamos PA
la concatenación de P
y A
.
Cuando E
se produce como un statement_expression el significado de E
es el mismo que el significado de la instrucción :
if ((object)P != null) PA
excepto que P
se evalúa una sola vez.
Cuando E
se produce como un anonymous_function_body o method_body el significado de depende de E
su clasificación:
Si
E
se clasifica como nada, su significado es el mismo que el significado del bloque:{ if ((object)P != null) PA; }
excepto que
P
se evalúa una sola vez.De lo contrario, el significado de
E
es el mismo que el significado del bloque:{ return E; }
y, a su vez, el significado de este bloque depende de si
E
es sintácticamente equivalente a un null_conditional_member_access (§12.8.8) o null_conditional_element_access (§12.8.13).
Acceso a elementos 12.8.12
12.8.12.1 General
Un element_access consta de un primary_no_array_creation_expression, seguido de un token "[
", seguido de un argument_list, seguido de un token "]
". El argument_list consta de uno o varios argumentos, separados por comas.
element_access
: primary_no_array_creation_expression '[' argument_list ']'
;
No se permite que la argument_list de un element_access contenga out
argumentos o ref
.
Un element_access se enlaza dinámicamente (§12.3.3) si al menos una de las siguientes suspensiones:
- El primary_no_array_creation_expression tiene el tipo
dynamic
en tiempo de compilación . - Al menos una expresión del argument_list tiene un tipo
dynamic
en tiempo de compilación y el primary_no_array_creation_expression no tiene un tipo de matriz.
En este caso, el compilador clasifica el element_access como un valor de tipo dynamic
. Las reglas siguientes para determinar el significado de la element_access se aplican en tiempo de ejecución, utilizando el tipo en tiempo de ejecución en lugar del tipo en tiempo de compilación de los de la primary_no_array_creation_expression y argument_list expresiones que tienen el tipo dynamic
en tiempo de compilación . Si el primary_no_array_creation_expression no tiene el tipo dynamic
en tiempo de compilación , el acceso al elemento se somete a una comprobación limitada en tiempo de compilación, tal como se describe en §12.6.5.
Si el primary_no_array_creation_expression de un element_access es un valor de un array_type, el element_access es un acceso de matriz (§12.8.12.2). De lo contrario, el primary_no_array_creation_expression será una variable o un valor de una clase, estructura o tipo de interfaz que tenga uno o varios miembros del indexador, en cuyo caso el element_access es un acceso de indexador (§12.8.12.3).
Acceso a la matriz 12.8.12.2
Para el acceso a una matriz, el primary_no_array_creation_expression del element_access será un valor de un array_type. Además, no se permite que la argument_list de acceso a una matriz contenga argumentos con nombre. El número de expresiones de la argument_list será el mismo que el rango de la array_type, y cada expresión será de tipo int
, uint
, long
o ulong,
o se podrá convertir implícitamente en uno o varios de estos tipos.
El resultado de evaluar el acceso a una matriz es una variable del tipo de elemento de la matriz, es decir, el elemento de matriz seleccionado por los valores de las expresiones de la argument_list.
El procesamiento en tiempo de ejecución de un acceso de matriz del formulario P[A]
, donde P
es un primary_no_array_creation_expression de un array_type y A
es un argument_list, consta de los pasos siguientes:
P
se evalúa. Si esta evaluación provoca una excepción, no se ejecutan pasos adicionales.- Las expresiones de índice del argument_list se evalúan en orden, de izquierda a derecha. Después de la evaluación de cada expresión de índice, se realiza una conversión implícita (§10.2) a uno de los siguientes tipos:
int
,uint
,long
,ulong
. El primer tipo de esta lista para el que existe una conversión implícita se elige. Por ejemplo, si la expresión de índice es de tiposhort
, se realiza una conversión implícita aint
, ya que las conversiones implícitas deshort
haciaint
y desdeshort
along
son posibles. Si la evaluación de una expresión de índice o la conversión implícita subsiguiente produce una excepción, no se evalúan más expresiones de índice y no se ejecutan más pasos. - Se comprueba que el valor de
P
es válido. Si el valor deP
esnull
, se produce unaSystem.NullReferenceException
excepción y no se ejecutan pasos adicionales. - El valor de cada expresión del argument_list se comprueba con los límites reales de cada dimensión de la instancia de matriz a la que hace
P
referencia . Si uno o varios valores están fuera del intervalo, se produce unaSystem.IndexOutOfRangeException
excepción y no se ejecutan más pasos. - La ubicación del elemento de matriz proporcionado por las expresiones de índice se calcula y esta ubicación se convierte en el resultado del acceso a la matriz.
Acceso al indexador 12.8.12.3
Para un acceso de indexador, el primary_no_array_creation_expression del element_access será una variable o un valor de una clase, estructura o tipo de interfaz, y este tipo implementará uno o varios indizadores que sean aplicables con respecto al argument_list de la element_access.
El procesamiento en tiempo de enlace de un acceso de indizador del formulario P[A]
, donde P
es un primary_no_array_creation_expression de una clase, estructura o tipo T
de interfaz , y A
es un argument_list, consta de los pasos siguientes:
- El conjunto de indexadores proporcionados por
T
se construye. El conjunto consta de todos los indexadores declarados enT
o un tipo base de que no son declaraciones deT
invalidación y son accesibles en el contexto actual (§7.5). - El conjunto se reduce a los indexadores aplicables y no ocultos por otros indexadores. Las reglas siguientes se aplican a cada indexador
S.I
del conjunto, dondeS
es el tipo en el que se declara el indexadorI
:- Si
I
no es aplicable con respecto aA
(§12.6.4.2),I
se quita del conjunto. - Si
I
es aplicable con respecto aA
(§12.6.4.2), todos los indexadores declarados en un tipo base deS
se quitan del conjunto. - Si
I
es aplicable con respecto aA
(§12.6.4.2) yS
es un tipo de clase distintoobject
de , todos los indexadores declarados en una interfaz se quitan del conjunto.
- Si
- Si el conjunto resultante de indexadores candidatos está vacío, no existen indizadores aplicables y se produce un error en tiempo de enlace.
- El mejor indexador del conjunto de indexadores candidatos se identifica mediante las reglas de resolución de sobrecarga de §12.6.4. Si no se puede identificar un único mejor indexador, el acceso al indexador es ambiguo y se produce un error en tiempo de enlace.
- Las expresiones de índice del argument_list se evalúan en orden, de izquierda a derecha. El resultado del procesamiento del acceso al indexador es una expresión clasificada como acceso de indexador. La expresión de acceso del indexador hace referencia al indexador determinado en el paso anterior y tiene una expresión de instancia asociada de
P
y una lista de argumentos asociada deA
y un tipo asociado que es el tipo del indexador. SiT
es un tipo de clase, el tipo asociado se elige de la primera declaración o invalidación del indexador que se encuentra al comenzar conT
y buscar en sus clases base.
Dependiendo del contexto en el que se use, un acceso de indexador provoca la invocación del descriptor de acceso get o del descriptor de acceso set del indexador. Si el acceso del indexador es el destino de una asignación, se invoca el descriptor de acceso set para asignar un nuevo valor (§12.21.2). En todos los demás casos, se invoca el descriptor de acceso get para obtener el valor actual (§12.2.2).
12.8.13 Acceso a elementos condicionales NULL
Un null_conditional_element_access consta de un primary_no_array_creation_expression seguido de los dos tokens "?
" y "[
", seguidos de un argument_list, seguido de un token "]
", seguido de cero o más dependent_accesses cualquiera de los cuales puede ir precedido por un null_forgiving_operator.
null_conditional_element_access
: primary_no_array_creation_expression '?' '[' argument_list ']'
(null_forgiving_operator? dependent_access)*
;
Un null_conditional_element_access es una versión condicional de element_access (§12.8.12) y es un error de tiempo de enlace si el tipo de resultado es void
. Para obtener una expresión condicional nula donde el tipo de resultado puede verse void
(§12.8.11).
Una expresión E
null_conditional_element_access tiene el formato P?[A]B
; donde B
son los dependent_accesses, si los hay. El significado de E
se determina de la siguiente manera:
Si el tipo de es un tipo de
P
valor que acepta valores NULL:Vamos
T
a ser el tipo de la expresiónP.Value[A]B
.Si
T
es un parámetro de tipo que no se sabe que es un tipo de referencia o un tipo de valor que no acepta valores NULL, se produce un error en tiempo de compilación.Si
T
es un tipo de valor que no acepta valores NULL, el tipo deE
esT?
y el significado deE
es el mismo que el significado de:((object)P == null) ? (T?)null : P.Value[A]B
Excepto que
P
se evalúa solo una vez.De lo contrario, el tipo de
E
esT
y el significado deE
es el mismo que el significado de:((object)P == null) ? null : P.Value[A]B
Excepto que
P
se evalúa solo una vez.
De lo contrario:
Vamos
T
a ser el tipo de la expresiónP[A]B
.Si
T
es un parámetro de tipo que no se sabe que es un tipo de referencia o un tipo de valor que no acepta valores NULL, se produce un error en tiempo de compilación.Si
T
es un tipo de valor que no acepta valores NULL, el tipo deE
esT?
y el significado deE
es el mismo que el significado de:((object)P == null) ? (T?)null : P[A]B
Excepto que
P
se evalúa solo una vez.De lo contrario, el tipo de
E
esT
y el significado deE
es el mismo que el significado de:((object)P == null) ? null : P[A]B
Excepto que
P
se evalúa solo una vez.
Nota: En una expresión del formulario:
P?[A₀]?[A₁]
si
P
senull
evalúa como ningunaA₀
de las evaluaciones oA₁
se evalúan. Lo mismo sucede si una expresión es una secuencia de null_conditional_element_access o null_conditional_member_access operaciones §12.8.8 .nota final
12.8.14 Este acceso
Un this_access consta de la palabra clave this
.
this_access
: 'this'
;
Solo se permite un this_access en el bloque de un constructor de instancia, un método de instancia, un descriptor de acceso de instancia (§12.2.1) o un finalizador. Tiene uno de los significados siguientes:
- Cuando
this
se usa en un primary_expression dentro de un constructor de instancia de una clase, se clasifica como un valor. El tipo del valor es el tipo de instancia (§15.3.2) de la clase en la que se produce el uso y el valor es una referencia al objeto que se está construyendo. - Cuando
this
se usa en un primary_expression dentro de un método de instancia o descriptor de acceso de instancia de una clase, se clasifica como un valor. El tipo del valor es el tipo de instancia (§15.3.2) de la clase en la que se produce el uso y el valor es una referencia al objeto para el que se invocó el método o descriptor de acceso. - Cuando
this
se usa en un primary_expression dentro de un constructor de instancia de un struct, se clasifica como una variable. El tipo de la variable es el tipo de instancia (§15.3.2) del struct en el que se produce el uso y la variable representa la estructura que se va a construir.- Si la declaración del constructor no tiene inicializador de constructor, la
this
variable se comporta exactamente igual que un parámetro de salida del tipo de estructura. En concreto, esto significa que la variable se asignará definitivamente en todas las rutas de acceso de ejecución del constructor de instancia. - De lo contrario, la
this
variable se comporta exactamente igual que unref
parámetro del tipo de estructura. En concreto, esto significa que la variable se considera asignada inicialmente.
- Si la declaración del constructor no tiene inicializador de constructor, la
- Cuando
this
se usa en un primary_expression dentro de un método de instancia o descriptor de acceso de instancia de un struct, se clasifica como una variable. El tipo de la variable es el tipo de instancia (§15.3.2) del struct en el que se produce el uso.- Si el método o descriptor de acceso no es un iterador (§15.14) o una función asincrónica (§15.15), la
this
variable representa la estructura para la que se invocó el método o descriptor de acceso.- Si la estructura es ,
readonly struct
lathis
variable se comporta exactamente igual que un parámetro de entrada del tipo de estructura. - De lo contrario, la
this
variable se comporta exactamente igual que unref
parámetro del tipo de estructura.
- Si la estructura es ,
- Si el método o descriptor de acceso es un iterador o una función asincrónica, la
this
variable representa una copia de la estructura para la que se invocó el método o descriptor de acceso, y se comporta exactamente igual que un parámetro de valor del tipo de estructura.
- Si el método o descriptor de acceso no es un iterador (§15.14) o una función asincrónica (§15.15), la
El uso de this
en un primary_expression en un contexto distinto de los enumerados anteriormente es un error en tiempo de compilación. En concreto, no es posible hacer referencia a this
en un método estático, un descriptor de acceso de propiedad estática o en un variable_initializer de una declaración de campo.
Acceso base 12.8.15
Un base_access consta de la base de palabras clave seguida de un token ".
" y un identificador y una type_argument_list opcional o un argument_list entre corchetes:
base_access
: 'base' '.' identifier type_argument_list?
| 'base' '[' argument_list ']'
;
Un base_access se usa para acceder a los miembros de clase base ocultos por miembros con nombre similar en la clase o estructura actual. Solo se permite un base_access en el cuerpo de un constructor de instancia, un método de instancia, un descriptor de acceso de instancia (§12.2.1) o un finalizador. Cuando base.I
se produce en una clase o estructura, indicaré un miembro de la clase base de esa clase o estructura. Del mismo modo, cuando base[E]
se produce en una clase, un indexador aplicable existirá en la clase base.
En tiempo de enlace, base_access expresiones del formulario base.I
y base[E]
se evalúan exactamente como si se escribieran ((B)this).I
y ((B)this)[E]
, donde B
es la clase base de la clase o estructura en la que se produce la construcción. Por lo tanto, base.I
y corresponden a this.I
y base[E]
this[E]
, excepto this
se ve como una instancia de la clase base.
Cuando un base_access hace referencia a un miembro de función virtual (un método, una propiedad o un indexador), se cambia la determinación del miembro de función que se va a invocar en tiempo de ejecución (§12.6.6). El miembro de función invocado se determina mediante la búsqueda de la implementación más derivada (§15.6.4) del miembro de función con respecto a B
(en lugar de con respecto al tipo en tiempo de ejecución de this
, como sería habitual en un acceso no base). Por lo tanto, dentro de una invalidación de un miembro de función virtual, se puede usar un base_access para invocar la implementación heredada del miembro de función. Si el miembro de función al que hace referencia un base_access es abstracto, se produce un error en tiempo de enlace.
Nota: A diferencia de
this
,base
no es una expresión en sí misma. Es una palabra clave que solo se usa en el contexto de un base_access o un constructor_initializer (§15.11.2). nota final
12.8.16 Operadores de incremento y decremento postfijo
post_increment_expression
: primary_expression '++'
;
post_decrement_expression
: primary_expression '--'
;
El operando de una operación de incremento o disminución de postfijo será una expresión clasificada como una variable, un acceso a propiedades o un acceso de indizador. El resultado de la operación es un valor del mismo tipo que el operando.
Si el primary_expression tiene el tipo dynamic
en tiempo de compilación, el operador se enlaza dinámicamente (§12.3.3), el post_increment_expression o post_decrement_expression tiene el tipo dynamic
en tiempo de compilación y las siguientes reglas se aplican en tiempo de ejecución mediante el tipo de tiempo de ejecución del primary_expression.
Si el operando de una operación de incremento o disminución de postfijo es un acceso de propiedad o indexador, la propiedad o el indexador tendrán un descriptor de acceso get y set. Si no es así, se produce un error en tiempo de enlace.
La resolución de sobrecarga del operador unario (§12.4.4) se aplica para seleccionar una implementación específica del operador. Existen operadores predefinidos ++
y --
para los siguientes tipos: sbyte
, byte
, ushort
int
uint
long
short
, char
float
ulong
double
, decimal
y cualquier tipo de enumeración. Los operadores predefinidos ++
devuelven el valor generado agregando 1
al operando y los operadores predefinidos --
devuelven el valor generado restando 1
del operando. En un contexto comprobado, si el resultado de esta suma o resta está fuera del intervalo del tipo de resultado y el tipo de resultado es un tipo entero o tipo de enumeración, se produce una System.OverflowException
excepción .
Habrá una conversión implícita del tipo de valor devuelto del operador unario seleccionado al tipo del primary_expression; de lo contrario, se produce un error en tiempo de compilación.
El procesamiento en tiempo de ejecución de una operación de incremento o disminución de postfijo del formulario x++
o x--
consta de los pasos siguientes:
- Si
x
se clasifica como una variable:x
se evalúa para generar la variable.- El valor de
x
se guarda. - El valor guardado de
x
se convierte en el tipo de operando del operador seleccionado y el operador se invoca con este valor como argumento. - El valor devuelto por el operador se convierte en el tipo de
x
y se almacena en la ubicación dada por la evaluación anterior dex
. - El valor guardado de
x
se convierte en el resultado de la operación.
- Si
x
se clasifica como acceso de propiedad o indexador:- La expresión de instancia (si
x
nostatic
es ) y la lista de argumentos (six
es un acceso de indexador) asociadax
a se evalúan y los resultados se usan en las invocaciones de descriptor de acceso get y set posteriores. - Se invoca el descriptor de acceso get de
x
y se guarda el valor devuelto. - El valor guardado de
x
se convierte en el tipo de operando del operador seleccionado y el operador se invoca con este valor como argumento. - El valor devuelto por el operador se convierte en el tipo de
x
y el descriptor de acceso set dex
se invoca con este valor como argumento value. - El valor guardado de
x
se 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.6). El resultado de o x--
es el valor de x++
antes de x
la operación, mientras que el resultado de ++x
o --x
es el valor de después de x
la operación. En cualquier caso, x
sí mismo tiene el mismo valor después de la operación.
Se puede invocar una implementación de operador ++
o operador --
mediante notación postfijo o prefijo. No es posible tener implementaciones de operador independientes para las dos notaciones.
12.8.17 El nuevo operador
12.8.17.1 General
El new
operador se usa para crear nuevas instancias de tipos.
Hay tres formas de expresiones nuevas:
- Las expresiones de creación de objetos y las expresiones de creación de objetos anónimos se usan para crear nuevas instancias de tipos de clase y tipos de valor.
- Las expresiones de creación de matrices se usan para crear nuevas instancias de tipos de matriz.
- Las expresiones de creación de delegados se usan para obtener instancias de tipos delegados.
El new
operador implica la creación de una instancia de un tipo, pero no implica necesariamente la asignación de memoria. En concreto, las instancias de tipos de valor no requieren memoria adicional más allá de las variables en las que residen y no se produce ninguna asignación cuando new
se usa para crear instancias de tipos de valor.
Nota: Las expresiones de creación del delegado no siempre crean nuevas instancias. Cuando la expresión se procesa de la misma manera que una conversión de grupo de métodos (§10.8) o una conversión de función anónima (§10.7), esto puede dar lugar a que se reutilice una instancia de delegado existente. nota final
12.8.17.2 Expresiones de creación de objetos
Se usa un object_creation_expression para crear una nueva instancia de un class_type o un value_type.
object_creation_expression
: 'new' type '(' argument_list? ')' object_or_collection_initializer?
| 'new' type object_or_collection_initializer
;
object_or_collection_initializer
: object_initializer
| collection_initializer
;
El tipo de un object_creation_expression será un class_type, un value_type o un type_parameter. El tipo no puede ser un tuple_type ni una class_type abstracta o estática.
El argument_list opcional (§12.6.2) solo se permite si el tipo es un class_type o un struct_type.
Una expresión de creación de objetos puede omitir la lista de argumentos del constructor y incluir paréntesis proporcionado que incluye un inicializador de objeto o inicializador de colección. Omitir la lista de argumentos del constructor y incluir paréntesis equivale a especificar una lista de argumentos vacía.
El procesamiento de una expresión de creación de objetos que incluye un inicializador de objeto o inicializador de colección consiste en procesar primero el constructor de instancia y, a continuación, procesar las inicializaciones de miembro o elemento especificadas por el inicializador de objeto (§12.8.17.3) o inicializador de colección (§12.8.17.4).
Si alguno de los argumentos del argument_list opcional tiene el tipo dynamic
en tiempo de compilación, el object_creation_expression se enlaza dinámicamente (§12.3.3) y las siguientes reglas se aplican en tiempo de ejecución mediante el tipo en tiempo de ejecución de esos argumentos del argument_list que tienen el tipo dynamic
en tiempo de compilación . Sin embargo, la creación del objeto se somete a una comprobación limitada en tiempo de compilación, tal como se describe en §12.6.5.
El procesamiento en tiempo de enlace de un object_creation_expression del formulario nuevo T(A)
, donde T
es un class_type, o un value_type, y A
es un argument_list opcional, consta de los pasos siguientes:
- Si
T
es un value_type yA
no está presente:- El object_creation_expression es una invocación de constructor predeterminada. El resultado de la object_creation_expression es un valor de tipo
T
, es decir, el valor predeterminado deT
como se define en §8.3.3.
- El object_creation_expression es una invocación de constructor predeterminada. El resultado de la object_creation_expression es un valor de tipo
- De lo contrario, si
T
es un type_parameter yA
no está presente:- Si no se ha especificado ninguna restricción de tipo de valor o restricción de constructor (§15.2.5) para
T
, se produce un error en tiempo de enlace. - El resultado del object_creation_expression es un valor del tipo en tiempo de ejecución al que se ha enlazado el parámetro type, es decir, el resultado de invocar el constructor predeterminado de ese tipo. El tipo en tiempo de ejecución puede ser un tipo de referencia o un tipo de valor.
- Si no se ha especificado ninguna restricción de tipo de valor o restricción de constructor (§15.2.5) para
- De lo contrario, si
T
es un class_type o un struct_type:- Si
T
es un class_type abstracto o estático, se produce un error en tiempo de compilación. - El constructor de instancia que se va a invocar se determina mediante las reglas de resolución de sobrecarga de §12.6.4. El conjunto de constructores de instancias candidatas consta de todos los constructores de instancias accesibles declarados en
T
, que son aplicables con respecto a A (§12.6.4.2). Si el conjunto de constructores de instancias candidatas está vacío o si no se puede identificar un único constructor de instancia mejor, se produce un error en tiempo de enlace. - El resultado del object_creation_expression es un valor de tipo
T
, es decir, el valor generado invocando el constructor de instancia determinado en el paso anterior. - De lo contrario, el object_creation_expression no es válido y se produce un error en tiempo de enlace.
- Si
Incluso si el object_creation_expression está enlazado dinámicamente, el tipo en tiempo de compilación sigue siendo T
.
El procesamiento en tiempo de ejecución de un object_creation_expression del formulario nuevo T(A)
, donde T
es class_type o un struct_type y A
es un argument_list opcional, consta de los pasos siguientes:
- Si
T
es un class_type:- Se asigna una nueva instancia de clase
T
. Si no hay suficiente memoria disponible para asignar la nueva instancia, se produce unaSystem.OutOfMemoryException
excepción y no se ejecutan más pasos. - Todos los campos de la nueva instancia se inicializan en sus valores predeterminados (§9.3).
- El constructor de instancia se invoca según las reglas de invocación de miembro de función (§12.6.6). Una referencia a la instancia recién asignada se pasa automáticamente al constructor de instancia y se puede acceder a ella desde dentro de ese constructor.
- Se asigna una nueva instancia de clase
- Si
T
es un struct_type:- Se crea una instancia de tipo
T
asignando una variable local temporal. Dado que se requiere un constructor de instancia de un struct_type para asignar definitivamente un valor a cada campo de la instancia que se va a crear, no es necesaria ninguna inicialización de la variable temporal. - El constructor de instancia se invoca según las reglas de invocación de miembro de función (§12.6.6). Una referencia a la instancia recién asignada se pasa automáticamente al constructor de instancia y se puede acceder a ella desde dentro de ese constructor.
- Se crea una instancia de tipo
12.8.17.3 Inicializadores de objeto
Un inicializador de objeto especifica valores para cero o más campos, propiedades o elementos indexados de un objeto.
object_initializer
: '{' member_initializer_list? '}'
| '{' member_initializer_list ',' '}'
;
member_initializer_list
: member_initializer (',' member_initializer)*
;
member_initializer
: initializer_target '=' initializer_value
;
initializer_target
: identifier
| '[' argument_list ']'
;
initializer_value
: expression
| object_or_collection_initializer
;
Un inicializador de objeto consta de una secuencia de inicializadores de miembros, entre {
tokens y }
separados por comas. Cada member_initializer designará un destino para la inicialización. Un identificador asignará un nombre a un campo o una propiedad accesibles del objeto que se va a inicializar, mientras que un argument_list entre corchetes especificará argumentos para un indexador accesible en el objeto que se va a inicializar. Se trata de un error para que un inicializador de objeto incluya más de un inicializador de miembro para el mismo campo o propiedad.
Nota: Aunque no se permite que un inicializador de objeto establezca el mismo campo o propiedad más de una vez, no hay restricciones para los indexadores. Un inicializador de objeto puede contener varios destinos de inicializador que hacen referencia a indizadores e incluso puede usar los mismos argumentos del indexador varias veces. nota final
Cada initializer_target va seguido de un signo igual y una expresión, un inicializador de objeto o un inicializador de colección. No es posible que las expresiones del inicializador de objeto hagan referencia al objeto recién creado que se está inicializando.
Inicializador de miembro que especifica una expresión después de que el signo igual se procese de la misma manera que una asignación (§12.21.2) al destino.
Inicializador de miembro que especifica un inicializador de objeto después del signo igual es un inicializador de objeto anidado, es decir, una inicialización de un objeto incrustado. En lugar de asignar un nuevo valor al campo o la propiedad, las asignaciones del inicializador de objetos anidados se tratan como asignaciones a los miembros del campo o propiedad. Los inicializadores de objeto anidados no se pueden aplicar a propiedades con un tipo de valor ni a campos de solo lectura con un tipo de valor.
Inicializador de miembro que especifica un inicializador de colección después del signo igual es una inicialización de una colección incrustada. En lugar de asignar una nueva colección al campo de destino, la propiedad o el indexador, los elementos especificados en el inicializador se agregan a la colección a la que hace referencia el destino. El destino será de un tipo de colección que cumpla los requisitos especificados en §12.8.17.4.
Cuando un destino de inicializador hace referencia a un indexador, los argumentos del indexador siempre se evaluarán exactamente una vez. Por lo tanto, incluso si los argumentos terminan nunca siendo utilizados (por ejemplo, debido a un inicializador anidado vacío), se evalúan para sus efectos secundarios.
Ejemplo: La siguiente clase representa un punto con dos coordenadas:
public class Point { public int X { get; set; } public int Y { get; set; } }
Se puede crear e inicializar una instancia de
Point
como se indica a continuación:Point a = new Point { X = 0, Y = 1 };
Esto tiene el mismo efecto que
Point __a = new Point(); __a.X = 0; __a.Y = 1; Point a = __a;
donde
__a
es una variable temporal invisible e inaccesible.La siguiente clase muestra un rectángulo creado a partir de dos puntos y la creación e inicialización de una
Rectangle
instancia:public class Rectangle { public Point P1 { get; set; } public Point P2 { get; set; } }
Se puede crear e inicializar una instancia de
Rectangle
como se indica a continuación:Rectangle r = new Rectangle { P1 = new Point { X = 0, Y = 1 }, P2 = new Point { X = 2, Y = 3 } };
Esto tiene el mismo efecto que
Rectangle __r = new Rectangle(); Point __p1 = new Point(); __p1.X = 0; __p1.Y = 1; __r.P1 = __p1; Point __p2 = new Point(); __p2.X = 2; __p2.Y = 3; __r.P2 = __p2; Rectangle r = __r;
donde
__r
,__p1
y__p2
son variables temporales que de lo contrario son invisibles e inaccesibles.Si
Rectangle
el constructor asigna las dos instancias incrustadas, se pueden usar para inicializar las instancias incrustadasPoint
Point
en lugar de asignar nuevas instancias:public class Rectangle { public Point P1 { get; } = new Point(); public Point P2 { get; } = new Point(); }
La siguiente construcción se puede usar para inicializar las instancias incrustadas
Point
en lugar de asignar nuevas instancias:Rectangle r = new Rectangle { P1 = { X = 0, Y = 1 }, P2 = { X = 2, Y = 3 } };
Esto tiene el mismo efecto que
Rectangle __r = new Rectangle(); __r.P1.X = 0; __r.P1.Y = 1; __r.P2.X = 2; __r.P2.Y = 3; Rectangle r = __r;
ejemplo final
12.8.17.4 Inicializadores de colección
Un inicializador de colección especifica los elementos de una colección.
collection_initializer
: '{' element_initializer_list '}'
| '{' element_initializer_list ',' '}'
;
element_initializer_list
: element_initializer (',' element_initializer)*
;
element_initializer
: non_assignment_expression
| '{' expression_list '}'
;
expression_list
: expression
| expression_list ',' expression
;
Un inicializador de colección consta de una secuencia de inicializadores de elementos, entre {
tokens y }
separados por comas. Cada inicializador de elemento especifica un elemento que se va a agregar al objeto de colección que se va a inicializar y consta de una lista de expresiones entre {
tokens y }
separados por comas. Un inicializador de elemento de expresión única se puede escribir sin llaves, pero no puede ser una expresión de asignación, para evitar ambigüedad con inicializadores de miembro. La non_assignment_expression producción se define en §12.22.
Ejemplo: a continuación se muestra un ejemplo de una expresión de creación de objetos que incluye un inicializador de colección:
List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
ejemplo final
El objeto de colección al que se aplica un inicializador de colección debe ser de un tipo que implementa System.Collections.IEnumerable
o se produce un error en tiempo de compilación. Para cada elemento especificado en orden de izquierda a derecha, se aplica la búsqueda de miembros normal para buscar un miembro denominado Add
. Si el resultado de la búsqueda de miembros no es un grupo de métodos, se produce un error en tiempo de compilación. De lo contrario, la resolución de sobrecarga se aplica con la lista de expresiones del inicializador de elementos como la lista de argumentos y el inicializador de colección invoca el método resultante. Por lo tanto, el objeto de colección contendrá una instancia o método de extensión aplicable con el nombre Add
de cada inicializador de elemento.
Ejemplo: a continuación se muestra una clase que representa un contacto con un nombre y una lista de números de teléfono, y la creación e inicialización de un
List<Contact>
:public class Contact { public string Name { get; set; } public List<string> PhoneNumbers { get; } = new List<string>(); } class A { static void M() { var contacts = new List<Contact> { new Contact { Name = "Chris Smith", PhoneNumbers = { "206-555-0101", "425-882-8080" } }, new Contact { Name = "Bob Harris", PhoneNumbers = { "650-555-0199" } } }; } }
que tiene el mismo efecto que
var __clist = new List<Contact>(); Contact __c1 = new Contact(); __c1.Name = "Chris Smith"; __c1.PhoneNumbers.Add("206-555-0101"); __c1.PhoneNumbers.Add("425-882-8080"); __clist.Add(__c1); Contact __c2 = new Contact(); __c2.Name = "Bob Harris"; __c2.PhoneNumbers.Add("650-555-0199"); __clist.Add(__c2); var contacts = __clist;
donde
__clist
,__c1
y__c2
son variables temporales que de lo contrario son invisibles e inaccesibles.ejemplo final
12.8.17.5 Expresiones de creación de matrices
Se usa un array_creation_expression para crear una nueva instancia de un array_type.
array_creation_expression
: 'new' non_array_type '[' expression_list ']' rank_specifier*
array_initializer?
| 'new' array_type array_initializer
| 'new' rank_specifier array_initializer
;
Una expresión de creación de matriz del primer formulario asigna una instancia de matriz del tipo que resulta de eliminar cada una de las expresiones individuales de la lista de expresiones.
Ejemplo: la expresión
new int[10,20]
de creación de matriz genera una instancia de matriz de tipoint[,]
y la expresión de creación de matriz nuevaint[10][,]
genera una instancia de matriz de tipoint[][,]
. ejemplo final
Cada expresión de la lista de expresiones debe ser de tipo int
, uint
, long
o ulong
, o implícitamente convertible a uno o varios de estos tipos. El valor de cada expresión determina la longitud de la dimensión correspondiente en la instancia de matriz recién asignada. Dado que la longitud de una dimensión de matriz debe ser no negativa, es un error en tiempo de compilación tener una expresión constante con un valor negativo, en la lista de expresiones.
Excepto en un contexto no seguro (§23.2), no se especifica el diseño de las matrices.
Si una expresión de creación de matriz del primer formulario incluye un inicializador de matriz, cada expresión de la lista de expresiones será una constante y las longitudes de rango y dimensión especificadas por la lista de expresiones coincidirán con las del inicializador de matriz.
En una expresión de creación de matriz del segundo o tercer formulario, el rango del tipo de matriz o especificador de rango coincidirá con el del inicializador de matriz. Las longitudes de dimensión individuales se deducen del número de elementos de cada uno de los niveles de anidamiento correspondientes del inicializador de matriz. Por lo tanto, la expresión del inicializador en la siguiente declaración
var a = new int[,] {{0, 1}, {2, 3}, {4, 5}};
corresponde exactamente a
var a = new int[3, 2] {{0, 1}, {2, 3}, {4, 5}};
Una expresión de creación de matriz del tercer formulario se conoce como expresión de creación de matriz con tipo implícito. Es similar al segundo formulario, excepto que el tipo de elemento de la matriz no se proporciona explícitamente, pero se determina como el mejor tipo común (§12.6.3.15) del conjunto de expresiones en el inicializador de matriz. Para una matriz multidimensional, es decir, una donde el rank_specifier contiene al menos una coma, este conjunto consta de todas las expresionesque se encuentran en array_initializeranidados.
Los inicializadores de matriz se describen más adelante en §17.7.
El resultado de evaluar una expresión de creación de matriz se clasifica como un valor, es decir, una referencia a la instancia de matriz recién asignada. El procesamiento en tiempo de ejecución de una expresión de creación de matriz consta de los pasos siguientes:
- Las expresiones de longitud de dimensión del expression_list se evalúan en orden, de izquierda a derecha. Después de la evaluación de cada expresión, se realiza una conversión implícita (§10.2) a uno de los siguientes tipos:
int
,uint
,long
,ulong
. El primer tipo de esta lista para el que existe una conversión implícita se elige. Si la evaluación de una expresión o la posterior conversión implícita provoca una excepción, no se evalúan más expresiones y no se ejecutan más pasos. - Los valores calculados para las longitudes de dimensión se validan, como se indica a continuación: Si uno o varios de los valores son menores que cero, se produce un
System.OverflowException
y no se ejecutan más pasos. - Se asigna una instancia de matriz con las longitudes de dimensión especificadas. Si no hay suficiente memoria disponible para asignar la nueva instancia, se produce una
System.OutOfMemoryException
excepción y no se ejecutan más pasos. - Todos los elementos de la nueva instancia de matriz se inicializan en sus valores predeterminados (§9.3).
- Si la expresión de creación de matriz contiene un inicializador de matriz, cada expresión del inicializador de matriz se evalúa y se asigna a su elemento de matriz correspondiente. Las evaluaciones y asignaciones se realizan en el orden en que las expresiones se escriben en el inicializador de matriz; es decir, los elementos se inicializan en orden de índice creciente, con la dimensión más a la derecha aumentando primero. Si la evaluación de una expresión determinada o la asignación posterior al elemento de matriz correspondiente provoca una excepción, no se inicializan elementos adicionales (y los elementos restantes tendrán sus valores predeterminados).
Una expresión de creación de matriz permite crear instancias de una matriz con elementos de un tipo de matriz, pero los elementos de dicha matriz se inicializarán manualmente.
Ejemplo: La instrucción
int[][] a = new int[100][];
crea una matriz unidimensional con 100 elementos de tipo
int[]
. El valor inicial de cada elemento esnull
. No es posible que la misma expresión de creación de matrices cree instancias también de las sub arrays y la instrucción .int[][] a = new int[100][5]; // Error
produce un error en tiempo de compilación. La creación de instancias de las sub arrays se puede realizar manualmente, como en
int[][] a = new int[100][]; for (int i = 0; i < 100; i++) { a[i] = new int[5]; }
ejemplo final
Nota: Cuando una matriz de matrices tiene una forma "rectangular", es decir, cuando las sub arrays son todas de la misma longitud, es más eficaz usar una matriz multidimensional. En el ejemplo anterior, la creación de instancias de la matriz de matrices crea 101 objetos: una matriz externa y 100 sub matrices. Por el contrario,
int[,] a = new int[100, 5];
crea solo un solo objeto, una matriz bidimensional y realiza la asignación en una sola instrucción.
nota final
Ejemplo: a continuación se muestran ejemplos de expresiones de creación de matrices con tipo implícito:
var a = new[] { 1, 10, 100, 1000 }; // int[] var b = new[] { 1, 1.5, 2, 2.5 }; // double[] var c = new[,] { { "hello", null }, { "world", "!" } }; // string[,] var d = new[] { 1, "one", 2, "two" }; // Error
La última expresión produce un error en tiempo de compilación porque ni
int
sestring
puede convertir implícitamente en el otro, por lo que no hay ningún tipo más común. En este caso, se debe usar una expresión de creación de matriz con tipo explícito, por ejemplo, especificando el tipo que se va a serobject[]
. Como alternativa, uno de los elementos se puede convertir en un tipo base común, que luego se convertirá en el tipo de elemento inferido.ejemplo final
Las expresiones de creación de matrices con tipo implícito se pueden combinar con inicializadores de objetos anónimos (§12.8.17.7) para crear estructuras de datos con tipo anónimo.
Ejemplo:
var contacts = new[] { new { Name = "Chris Smith", PhoneNumbers = new[] { "206-555-0101", "425-882-8080" } }, new { Name = "Bob Harris", PhoneNumbers = new[] { "650-555-0199" } } };
ejemplo final
12.8.17.6 Expresiones de creación de delegados
Se usa un delegate_creation_expression para obtener una instancia de un delegate_type.
delegate_creation_expression
: 'new' delegate_type '(' expression ')'
;
El argumento de una expresión de creación de delegados debe ser un grupo de métodos, una función anónima o un valor del tipo dynamic
en tiempo de compilación o un delegate_type. Si el argumento es un grupo de métodos, identifica el método y, para un método de instancia, el objeto para el que se va a crear un delegado. Si el argumento es una función anónima, define directamente los parámetros y el cuerpo del método del destino delegado. Si el argumento es un valor, identifica una instancia de delegado de la que se va a crear una copia.
Si la expresión tiene el tipo dynamic
en tiempo de compilación , el delegate_creation_expression se enlaza dinámicamente (§12.8.17.6) y las reglas siguientes se aplican en tiempo de ejecución mediante el tipo en tiempo de ejecución de la expresión. De lo contrario, las reglas se aplican en tiempo de compilación.
El procesamiento en tiempo de enlace de un delegate_creation_expression del formulario nuevo D(E)
, donde D
es un delegate_type y E
es una expresión, consta de los pasos siguientes:
Si
E
es un grupo de métodos, la expresión de creación del delegado se procesa de la misma manera que una conversión de grupo de métodos (§10.8) deE
aD
.Si
E
es una función anónima, la expresión de creación del delegado se procesa de la misma manera que una conversión de función anónima (§10.7) deE
aD
.Si
E
es un valor,E
será compatible (§20.2) conD
y 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 un delegate_creation_expression del formulario nuevo D(E)
, donde D
es un delegate_type y E
es una expresión, consta de los pasos siguientes:
- Si
E
es un grupo de métodos, la expresión de creación del delegado se evalúa como una conversión de grupo de métodos (§10.8) deE
aD
. - Si
E
es una función anónima, la creación del delegado se evalúa como una conversión de función anónima deE
aD
(§10.7). - Si
E
es un valor de un delegate_type:E
se evalúa. Si esta evaluación provoca una excepción, no se ejecutan pasos adicionales.- Si el valor de
E
esnull
, se produce unaSystem.NullReferenceException
excepción y no se ejecutan pasos adicionales. - Se asigna una nueva instancia del tipo
D
de delegado. Si no hay suficiente memoria disponible para asignar la nueva instancia, se produce unaSystem.OutOfMemoryException
excepción y no se ejecutan más pasos. - La nueva instancia de delegado se inicializa con una lista de invocación de entrada única que invoca
E
.
La lista de invocación de un delegado se determina cuando se crea una instancia del delegado y, a continuación, permanece constante durante toda la duración del delegado. En otras palabras, no es posible cambiar las entidades invocables de destino de un delegado una vez que se haya creado.
Nota: Recuerde que cuando se combinan dos delegados o se quita uno de otro, se produce un nuevo delegado; no ha cambiado su contenido. nota final
No es posible crear un delegado que haga referencia a una propiedad, indexador, operador definido por el usuario, constructor de instancia, finalizador o constructor estático.
Ejemplo: Como se ha descrito anteriormente, cuando se crea un delegado a partir de un grupo de métodos, la lista de parámetros y el tipo de valor devuelto del delegado determinan cuáles de los métodos sobrecargados se van a seleccionar. En el ejemplo
delegate double DoubleFunc(double x); class A { DoubleFunc f = new DoubleFunc(Square); static float Square(float x) => x * x; static double Square(double x) => x * x; }
el
A.f
campo se inicializa con un delegado que hace referencia al segundoSquare
método porque ese método coincide exactamente con la lista de parámetros y el tipo de valor devuelto deDoubleFunc
. Si el segundoSquare
método no estuviera presente, se habría producido un error en tiempo de compilación.ejemplo final
12.8.17.7 Expresiones de creación de objetos anónimos
Un anonymous_object_creation_expression se usa para crear un objeto de un tipo anónimo.
anonymous_object_creation_expression
: 'new' anonymous_object_initializer
;
anonymous_object_initializer
: '{' member_declarator_list? '}'
| '{' member_declarator_list ',' '}'
;
member_declarator_list
: member_declarator (',' member_declarator)*
;
member_declarator
: simple_name
| member_access
| null_conditional_projection_initializer
| base_access
| identifier '=' expression
;
Un inicializador de objeto anónimo declara un tipo anónimo y devuelve una instancia de ese tipo. Un tipo anónimo es un tipo de clase sin nombre que hereda directamente de object
. Los miembros de un tipo anónimo son una secuencia de propiedades de solo lectura inferidas del inicializador de objetos anónimos que se usa para crear una instancia del tipo. En concreto, un inicializador de objeto anónimo del formulario
new {
p₁ =
e₁ ,
p Pudin =
e Expulsado ,
… pv =
ev }
declara un tipo anónimo del formulario
class __Anonymous1
{
private readonly «T1» «f1»;
private readonly «T2» «f2»;
...
private readonly «Tn» «fn»;
public __Anonymous1(«T1» «a1», «T2» «a2»,..., «Tn» «an»)
{
«f1» = «a1»;
«f2» = «a2»;
...
«fn» = «an»;
}
public «T1» «p1» { get { return «f1»; } }
public «T2» «p2» { get { return «f2»; } }
...
public «Tn» «pn» { get { return «fn»; } }
public override bool Equals(object __o) { ... }
public override int GetHashCode() { ... }
}
donde cada «Tx» es el tipo de la expresión correspondiente «ex». La expresión utilizada en un member_declarator tendrá un tipo . Por lo tanto, es un error en tiempo de compilación para que una expresión de un member_declarator sea null
o una función anónima.
El compilador genera automáticamente los nombres de un tipo anónimo y del parámetro en su Equals
método y no se puede hacer referencia a él en el texto del programa.
Dentro del mismo programa, dos inicializadores de objetos anónimos que especifican una secuencia de propiedades de los mismos nombres y tipos en tiempo de compilación en el mismo orden producirán instancias del mismo tipo anónimo.
Ejemplo: en el ejemplo
var p1 = new { Name = "Lawnmower", Price = 495.00 }; var p2 = new { Name = "Shovel", Price = 26.95 }; p1 = p2;
Se permite la asignación en la última línea porque
p1
yp2
son del mismo tipo anónimo.ejemplo final
Los Equals
métodos y GetHashcode
de los tipos anónimos invalidan los métodos heredados de object
y se definen en términos de y de las Equals
GetHashcode
propiedades, de modo que dos instancias del mismo tipo anónimo sean iguales si y solo si todas sus propiedades son iguales.
Un declarador de miembro se puede abreviar a un nombre simple (§12.8.4), un acceso de miembro (§12.8.7), un inicializador de proyección condicional NULL §12.8.8 o un acceso base (§12.8.15). Esto se denomina inicializador de proyección y es abreviado para una declaración de y asignación a una propiedad con el mismo nombre. En concreto, declaradores de miembros de los formularios
«identifier»
, «expr» . «identifier»
y «expr» ? . «identifier»
son exactamente equivalentes a lo siguiente, respectivamente:
«identifer» = «identifier»
, «identifier» = «expr» . «identifier»
y «identifier» = «expr» ? . «identifier»
Por lo tanto, en un inicializador de proyección, el identificador selecciona el valor y el campo o la propiedad a los que se asigna el valor. Intuitivamente, un inicializador de proyección proyecta no solo un valor, sino también el nombre del valor.
12.8.18 Operador typeof
El typeof
operador se usa para obtener el System.Type
objeto de un tipo.
typeof_expression
: 'typeof' '(' type ')'
| 'typeof' '(' unbound_type_name ')'
| 'typeof' '(' 'void' ')'
;
unbound_type_name
: identifier generic_dimension_specifier?
| identifier '::' identifier generic_dimension_specifier?
| unbound_type_name '.' identifier generic_dimension_specifier?
;
generic_dimension_specifier
: '<' comma* '>'
;
comma
: ','
;
La primera forma de typeof_expression consta de una typeof
palabra clave seguida de un tipo entre paréntesis. El resultado de una expresión de este formulario es el System.Type
objeto del tipo indicado. Solo hay un System.Type
objeto para cualquier tipo determinado. Esto significa que para un tipo T
, typeof(T) == typeof(T)
siempre es true. El tipo no puede ser dynamic
.
La segunda forma de typeof_expression consta de una typeof
palabra clave seguida de un unbound_type_name entre paréntesis.
Nota: Un unbound_type_name es muy similar a un type_name (§7.8), excepto que un unbound_type_name contiene generic_dimension_specifiers donde un type_name contiene type_argument_lists. nota final
Cuando el operando de un typeof_expression es una secuencia de tokens que satisface las gramáticas de unbound_type_name y type_name, es decir, cuando no contiene ni un generic_dimension_specifier ni un type_argument_list, la secuencia de tokens se considera una type_name. El significado de un unbound_type_name se determina de la siguiente manera:
- Convierta la secuencia de tokens en un type_name reemplazando cada generic_dimension_specifier por un type_argument_list que tenga el mismo número de comas y la palabra clave
object
que cada type_argument. - Evalúe el type_name resultante, al tiempo que omite todas las restricciones de parámetros de tipo.
- El unbound_type_name se resuelve en el tipo genérico sin enlazar asociado al tipo construido resultante (§8.4).
Se trata de un error para que el nombre de tipo sea un tipo de referencia que acepta valores NULL.
El resultado del typeof_expression es el System.Type
objeto del tipo genérico sin enlazar resultante.
La tercera forma de typeof_expression consta de una typeof
palabra clave seguida de una palabra clave entre paréntesis void
. El resultado de una expresión de este formulario es el System.Type
objeto que representa la ausencia de un tipo. El objeto de tipo devuelto por typeof(void)
es distinto del objeto de tipo devuelto para cualquier tipo.
Nota: Este objeto especial
System.Type
es útil en las bibliotecas de clases que permiten la reflexión en los métodos del lenguaje, donde esos métodos desean tener una manera de representar el tipo de valor devuelto de cualquier método, incluidosvoid
los métodos, con una instancia deSystem.Type
. nota final
El typeof
operador se puede usar en un parámetro de tipo. Se trata de un error en tiempo de compilación si se sabe que el nombre del tipo es un tipo de referencia que acepta valores NULL. El resultado es el System.Type
objeto del tipo en tiempo de ejecución enlazado al parámetro type. Si el tipo en tiempo de ejecución es un tipo de referencia que acepta valores NULL, el resultado es el tipo de referencia que no acepta valores NULL correspondiente. El typeof
operador también se puede usar en un tipo construido o en un tipo genérico no enlazado (§8.4.4). El System.Type
objeto de un tipo genérico sin enlazar no es el mismo que el System.Type
objeto del tipo de instancia (§15.3.2). El tipo de instancia siempre es un tipo construido cerrado en tiempo de ejecución, por lo que su System.Type
objeto depende de los argumentos de tipo en tiempo de ejecución en uso. El tipo genérico no enlazado, por otro lado, no tiene argumentos de tipo y produce el mismo System.Type
objeto independientemente de los argumentos de tipo en tiempo de ejecución.
Ejemplo: El ejemplo
class X<T> { public static void PrintTypes() { Type[] t = { typeof(int), typeof(System.Int32), typeof(string), typeof(double[]), typeof(void), typeof(T), typeof(X<T>), typeof(X<X<T>>), typeof(X<>) }; for (int i = 0; i < t.Length; i++) { Console.WriteLine(t[i]); } } } class Test { static void Main() { X<int>.PrintTypes(); } }
genera la salida siguiente:
System.Int32 System.Int32 System.String System.Double[] System.Void System.Int32 X`1[System.Int32] X`1[X`1[System.Int32]] X`1[T]
Tenga en cuenta que
int
ySystem.Int32
son el mismo tipo. El resultado detypeof(X<>)
no depende del argumento type, pero el resultado detypeof(X<T>)
sí.ejemplo final
12.8.19 Operador sizeof
El sizeof
operador devuelve el número de bytes de 8 bits ocupados por una variable de un tipo determinado. El tipo especificado como operando para sizeof será un unmanaged_type (§8.8).
sizeof_expression
: 'sizeof' '(' unmanaged_type ')'
;
Para determinados tipos predefinidos, el sizeof
operador produce un valor constante int
como se muestra en la tabla siguiente:
Expression | Resultado |
---|---|
sizeof(sbyte) |
1 |
sizeof(byte) |
1 |
sizeof(short) |
2 |
sizeof(ushort) |
2 |
sizeof(int) |
4 |
sizeof(uint) |
4 |
sizeof(long) |
8 |
sizeof(ulong) |
8 |
sizeof(char) |
2 |
sizeof(float) |
4 |
sizeof(double) |
8 |
sizeof(bool) |
1 |
sizeof(decimal) |
16 |
Para un tipo T
de enumeración , el resultado de la expresión sizeof(T)
es un valor constante igual al tamaño de su tipo subyacente, como se ha indicado anteriormente. Para todos los demás tipos de operando, el sizeof
operador se especifica en §23.6.9.
12.8.20 Operadores activados y sin comprobar
Los checked
operadores y unchecked
se usan para controlar el contexto de comprobación de desbordamiento para operaciones y conversiones aritméticas de tipo entero.
checked_expression
: 'checked' '(' expression ')'
;
unchecked_expression
: 'unchecked' '(' expression ')'
;
El checked
operador evalúa la expresión contenida en un contexto comprobado y el unchecked
operador evalúa la expresión contenida en un contexto no comprobado. Un checked_expression o unchecked_expression corresponde exactamente a un parenthesized_expression (§12.8.5), excepto que la expresión contenida se evalúa en el contexto de comprobación de desbordamiento dado.
El contexto de comprobación de desbordamiento también se puede controlar a través de las checked
instrucciones y unchecked
(§13.12).
Las siguientes operaciones se ven afectadas por el contexto de comprobación de desbordamiento establecido por los operadores y instrucciones comprobados y no comprobados:
- Los operadores predefinidos
++
y--
(§12.8.16 y §12.9.6), cuando el operando es de un tipo entero o de enumeración. - Operador unario predefinido
-
(§12.9.3), cuando el operando es de un tipo entero. - Los operadores binarios predefinidos
+
,-
,*
y/
(§12.10), cuando ambos operandos son de tipos enteros o de enumeración. - Conversiones numéricas explícitas (§10.3.2) de un tipo entero o de enumeración a otro tipo entero o de enumeración, o de
float
odouble
a un tipo entero o de enumeración.
Cuando una de las operaciones anteriores produce un resultado demasiado grande para representar en el tipo de destino, el contexto en el que se realiza la operación controla el comportamiento resultante:
- En un
checked
contexto, si la operación es una expresión constante (§12.23), se produce un error en tiempo de compilación. De lo contrario, cuando se realiza la operación en tiempo de ejecución, se produce unaSystem.OverflowException
excepción . - En un
unchecked
contexto, el resultado se trunca descartando los bits de orden alto que no caben en el tipo de destino.
En el caso de expresiones que no son constantes (§12.23) (expresiones que se evalúan en tiempo de ejecución) que no están incluidas en ninguna checked
instrucción o unchecked
operador, el contexto de comprobación de desbordamiento predeterminado está desactivado, a menos que los factores externos (como modificadores del compilador y la configuración del entorno de ejecución) llamen a la evaluación comprobada.
Para las expresiones constantes (§12.23) (expresiones que se pueden evaluar completamente en tiempo de compilación), siempre se comprueba el contexto de comprobación de desbordamiento predeterminado. A menos que una expresión constante se coloque explícitamente en un unchecked
contexto, los desbordamientos que se producen durante la evaluación en tiempo de compilación de la expresión siempre provocan errores en tiempo de compilación.
El cuerpo de una función anónima no se ve afectado por checked
contextos o unchecked
en los que se produce la función anónima.
Ejemplo: en el código siguiente
class Test { static readonly int x = 1000000; static readonly int y = 1000000; static int F() => checked(x * y); // Throws OverflowException static int G() => unchecked(x * y); // Returns -727379968 static int H() => x * y; // Depends on default }
no se notifican errores en tiempo de compilación, ya que ninguna de las expresiones se puede evaluar en tiempo de compilación. En tiempo de ejecución, el
F
método produce unSystem.OverflowException
y elG
método devuelve –727379968 (los 32 bits inferiores del resultado fuera del intervalo). El comportamiento delH
método depende del contexto de comprobación de desbordamiento predeterminado para la compilación, pero es el mismoF
que o el mismo queG
.ejemplo final
Ejemplo: en el código siguiente
class Test { const int x = 1000000; const int y = 1000000; static int F() => checked(x * y); // Compile-time error, overflow static int G() => unchecked(x * y); // Returns -727379968 static int H() => x * y; // Compile-time error, overflow }
los desbordamientos que se producen al evaluar las expresiones constantes en
F
yH
hacen que se notifiquen errores en tiempo de compilación porque las expresiones se evalúan en unchecked
contexto. También se produce un desbordamiento al evaluar la expresión constante enG
, pero dado que la evaluación tiene lugar en ununchecked
contexto, no se notifica el desbordamiento.ejemplo final
Los checked
operadores y unchecked
solo afectan al contexto de comprobación de desbordamiento para las operaciones que están textualmente contenidas en los tokens "(
" y ")
". Los operadores no tienen ningún efecto en los miembros de función que se invocan como resultado de evaluar la expresión contenida.
Ejemplo: en el código siguiente
class Test { static int Multiply(int x, int y) => x * y; static int F() => checked(Multiply(1000000, 1000000)); }
El uso de en F no afecta a la evaluación de
x * y
enMultiply
, por lo quex * y
se evalúa en el contexto de comprobación dechecked
desbordamiento predeterminado.ejemplo final
El unchecked
operador es cómodo al escribir constantes de los tipos enteros firmados en notación hexadecimal.
Ejemplo:
class Test { public const int AllBits = unchecked((int)0xFFFFFFFF); public const int HighBit = unchecked((int)0x80000000); }
Las dos constantes hexadecimales anteriores son de tipo
uint
. Dado que las constantes están fuera delint
intervalo, sin elunchecked
operador , las conversiones paraint
generarían errores en tiempo de compilación.ejemplo final
Nota: Los
checked
operadores yunchecked
las instrucciones permiten a los programadores controlar determinados aspectos de algunos cálculos numéricos. Sin embargo, el comportamiento de algunos operadores numéricos depende de los tipos de datos de sus operandos. Por ejemplo, la multiplicación de dos decimales siempre da como resultado una excepción al desbordamiento incluso dentro de una construcción desactivada explícitamente. De forma similar, la multiplicación de dos floats nunca produce una excepción en el desbordamiento incluso dentro de una construcción activada explícitamente. Además, otros operadores nunca se ven afectados por el modo de comprobación, ya sea predeterminado o explícito. nota final
12.8.21 Expresiones de valor predeterminado
Se usa una expresión de valor predeterminada para obtener el valor predeterminado (§9.3) de un tipo.
default_value_expression
: explictly_typed_default
| default_literal
;
explictly_typed_default
: 'default' '(' type ')'
;
default_literal
: 'default'
;
Un default_literal representa un valor predeterminado (§9.3). No tiene un tipo, pero se puede convertir a cualquier tipo a través de una conversión literal predeterminada (§10.2.16).
El resultado de un default_value_expression es el valor predeterminado (§9.3) del tipo explícito en un explictly_typed_default o el tipo de destino de la conversión de un default_value_expression.
Un default_value_expression es una expresión constante (§12.23) si el tipo es uno de los siguientes:
- un tipo de referencia
- un parámetro de tipo que se sabe que es un tipo de referencia (§8.2);
- uno de los siguientes tipos de valor:
sbyte
,byte
,ushort
long
ulong
char
float
uint
short
int
,double
, , , obool,
decimal
- cualquier tipo de enumeración.
Asignación de pila 12.8.22
Una expresión de asignación de pila asigna un bloque de memoria de la pila de ejecución. La pila de ejecución es un área de memoria donde se almacenan las variables locales. La pila de ejecución no forma parte del montón administrado. La memoria usada para el almacenamiento de variables locales se recupera automáticamente cuando la función actual devuelve.
Las reglas de contexto seguro para una expresión de asignación de pila se describen en §16.4.12.7.
stackalloc_expression
: 'stackalloc' unmanaged_type '[' expression ']'
| 'stackalloc' unmanaged_type? '[' constant_expression? ']'
stackalloc_initializer
;
stackalloc_initializer
: '{' stackalloc_initializer_element_list '}'
;
stackalloc_initializer_element_list
: stackalloc_element_initializer (',' stackalloc_element_initializer)* ','?
;
stackalloc_element_initializer
: expression
;
Solo se permite un stackalloc_expression en dos contextos:
- Expresión de inicialización, ,
E
de un local_variable_declaration (§13.6.2); y - Expresión de operando derecho,
E
, de una asignación simple (§12.21.2) que se produce como un expression_statement (§13.7)
En ambos contextos, el stackalloc_expression solo se permite que se produzca como:
- El conjunto de
E
; o - Los operandos segundo o tercero de un conditional_expression (§12.18), que es en sí mismo el conjunto de
E
.
El unmanaged_type (§8.8) indica el tipo de los elementos que se almacenarán en la ubicación recién asignada y la expresión indica el número de estos elementos. En conjunto, especifican el tamaño de asignación necesario. El tipo de expresión se podrá convertir implícitamente en el tipo int
.
Dado que el tamaño de una asignación de pila no puede ser negativo, se trata de un error en tiempo de compilación para especificar el número de elementos como un constant_expression que se evalúa como un valor negativo.
En tiempo de ejecución si el número de elementos que se van a asignar es un valor negativo, el comportamiento no está definido. Si es cero, no se realiza ninguna asignación y el valor devuelto es definido por la implementación. Si no hay suficiente memoria disponible para asignar los elementos, se produce una System.StackOverflowException
excepción .
Cuando hay un stackalloc_initializer presente:
- Si se omite unmanaged_type , se deduce que sigue las reglas del mejor tipo común (§12.6.3.15) para el conjunto de stackalloc_element_initializers.
- Si constant_expression se omite, se deduce que es el número de stackalloc_element_initializers.
- Si constant_expression está presente, será igual al número de stackalloc_element_initializers.
Cada stackalloc_element_initializer tendrá una conversión implícita a unmanaged_type (§10.2). El stackalloc_element_initializerinicializar elementos en la memoria asignada en orden creciente, empezando por el elemento en el índice cero. En ausencia de un stackalloc_initializer, el contenido de la memoria recién asignada no está definido.
El resultado de un stackalloc_expression es una instancia de tipo Span<T>
, donde T
es el unmanaged_type:
Span<T>
(§C.3) es un tipo de estructura ref (§16.2.3), que presenta un bloque de memoria, aquí el bloque asignado por el stackalloc_expression, como una colección indexable de elementos con tipo (T
).- La propiedad del
Length
resultado devuelve el número de elementos asignados. - El indexador del resultado (§15.9) devuelve un variable_reference (§9.5) a un elemento del bloque asignado y se comprueba el intervalo.
Nota: Al producirse en código no seguro, el resultado de un stackalloc_expression puede ser de un tipo diferente, consulte (§23.9). nota final
No se permiten inicializadores de asignación de pila en catch
bloques o finally
(§13.11).
Nota: No hay ninguna manera de liberar explícitamente memoria asignada mediante
stackalloc
. nota final
Todos los bloques de memoria asignados a la pila creados durante la ejecución de un miembro de función se descartan automáticamente cuando ese miembro de función devuelve.
Excepto para el stackalloc
operador, C# no proporciona construcciones predefinidas para administrar la memoria no recopilada sin elementos no utilizados. Normalmente, estos servicios se proporcionan mediante la compatibilidad con bibliotecas de clases o se importan directamente desde el sistema operativo subyacente.
Ejemplo:
// Memory uninitialized Span<int> span1 = stackalloc int[3]; // Memory initialized Span<int> span2 = stackalloc int[3] { -10, -15, -30 }; // Type int is inferred Span<int> span3 = stackalloc[] { 11, 12, 13 }; // Error; result is int*, not allowed in a safe context var span4 = stackalloc[] { 11, 12, 13 }; // Error; no conversion from Span<int> to Span<long> Span<long> span5 = stackalloc[] { 11, 12, 13 }; // Converts 11 and 13, and returns Span<long> Span<long> span6 = stackalloc[] { 11, 12L, 13 }; // Converts all and returns Span<long> Span<long> span7 = stackalloc long[] { 11, 12, 13 }; // Implicit conversion of Span<T> ReadOnlySpan<int> span8 = stackalloc int[] { 10, 22, 30 }; // Implicit conversion of Span<T> Widget<double> span9 = stackalloc double[] { 1.2, 5.6 }; public class Widget<T> { public static implicit operator Widget<T>(Span<double> sp) { return null; } }
En el caso de
span8
,stackalloc
da como resultado unSpan<int>
, que un operadorReadOnlySpan<int>
implícito convierte en . Del mismo modo, paraspan9
, el resultadoSpan<double>
se convierte en el tipoWidget<double>
definido por el usuario mediante la conversión, como se muestra. ejemplo final
12.8.23 El operador nameof
Un nameof_expression se usa para obtener el nombre de una entidad de programa como una cadena constante.
nameof_expression
: 'nameof' '(' named_entity ')'
;
named_entity
: named_entity_target ('.' identifier type_argument_list?)*
;
named_entity_target
: simple_name
| 'this'
| 'base'
| predefined_type
| qualified_alias_member
;
Dado que nameof
no es una palabra clave, un nameof_expression siempre es ambiguo sintácticamente con una invocación del nombre nameof
simple . Por motivos de compatibilidad, si una búsqueda de nombres (§12.8.4) del nombre nameof
se realiza correctamente, la expresión se trata como una invocation_expression , independientemente de si la invocación es válida. De lo contrario, es un nameof_expression.
Las búsquedas de acceso a miembros y nombres simples se realizan en el named_entity en tiempo de compilación, siguiendo las reglas descritas en §12.8.4 y §12.8.7. Sin embargo, cuando la búsqueda descrita en §12.8.4 y §12.8.7 da como resultado un error porque se encontró un miembro de instancia en un contexto estático, un nameof_expression no produce este error.
Se trata de un error en tiempo de compilación para que un named_entity designar un grupo de métodos tenga un type_argument_list. Es un error en tiempo de compilación para que un named_entity_target tenga el tipo dynamic
.
Un nameof_expression es una expresión constante de tipo 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 del named_entity antes del type_argument_list final opcional, transformado de la siguiente manera:
- Se quita el prefijo "
@
", si se usa. - Cada unicode_escape_sequence se transforma en su carácter Unicode correspondiente.
- Se quita cualquier formatting_characters .
Estas son las mismas transformaciones aplicadas en §6.4.3 al probar la igualdad entre identificadores.
Ejemplo: a continuación se muestran los resultados de varias
nameof
expresiones, suponiendo que un tipoList<T>
genérico declarado en elSystem.Collections.Generic
espacio de nombres:using TestAlias = System.String; class Program { static void Main() { var point = (x: 3, y: 4); string n1 = nameof(System); // "System" string n2 = nameof(System.Collections.Generic); // "Generic" string n3 = nameof(point); // "point" string n4 = nameof(point.x); // "x" string n5 = nameof(Program); // "Program" string n6 = nameof(System.Int32); // "Int32" string n7 = nameof(TestAlias); // "TestAlias" string n8 = nameof(List<int>); // "List" string n9 = nameof(Program.InstanceMethod); // "InstanceMethod" string n10 = nameof(Program.GenericMethod); // "GenericMethod" string n11 = nameof(Program.NestedClass); // "NestedClass" // Invalid // string x1 = nameof(List<>); // Empty type argument list // string x2 = nameof(List<T>); // T is not in scope // string x3 = nameof(GenericMethod<>); // Empty type argument list // string x4 = nameof(GenericMethod<T>); // T is not in scope // string x5 = nameof(int); // Keywords not permitted // Type arguments not permitted for method group // string x6 = nameof(GenericMethod<Program>); } void InstanceMethod() { } void GenericMethod<T>() { string n1 = nameof(List<T>); // "List" string n2 = nameof(T); // "T" } class NestedClass { } }
Las partes potencialmente sorprendentes de este ejemplo son la resolución de
nameof(System.Collections.Generic)
solo "Genérico" en lugar del espacio de nombres completo, y denameof(TestAlias)
a "TestAlias" en lugar de "String". ejemplo final
12.8.24 Expresiones de método anónimo
Un anonymous_method_expression es una de las dos maneras de definir una función anónima. Estos se describen más adelante en §12.19.
12.9 Operadores unarios
12.9.1 General
Los +
operadores , -
, !
(negación lógica §12.9.4 solo), ~
, ++
, --
cast y await
se denominan operadores unarios.
Nota: El operador postfix null-forgiving (§12.8.9), ,
!
debido a su naturaleza de tiempo de compilación y no sobrecargable, se excluye de la lista anterior. nota final
unary_expression
: primary_expression
| '+' unary_expression
| '-' unary_expression
| logical_negation_operator unary_expression
| '~' unary_expression
| pre_increment_expression
| pre_decrement_expression
| cast_expression
| await_expression
| pointer_indirection_expression // unsafe code support
| addressof_expression // unsafe code support
;
pointer_indirection_expression (§23.6.2) y addressof_expression (§23.6.5) solo están disponibles en código no seguro (§23).
Si el operando de un unary_expression tiene el tipo dynamic
en tiempo de compilación , se enlaza dinámicamente (§12.3.3). En este caso, el tipo en tiempo de compilación del unary_expression es dynamic
y la resolución descrita a continuación tendrá lugar en tiempo de ejecución mediante el tipo en tiempo de ejecución del operando.
12.9.2 Operador Unary plus
Para una operación con el formato +x
, la resolución de sobrecarga del operador unario (§12.4.4) se aplica para seleccionar una implementación específica del operador. El operando se convierte en el tipo de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador. Los operadores unarios más predefinidos son:
int operator +(int x);
uint operator +(uint x);
long operator +(long x);
ulong operator +(ulong x);
float operator +(float x);
double operator +(double x);
decimal operator +(decimal x);
Para cada uno de estos operadores, el resultado es simplemente el valor del operando.
Las formas levantadas (§12.4.8) de los operadores unarios predefinidos sin levantar más definidos anteriormente también están predefinidos.
12.9.3 Operador menos unario
Para una operación con el formato –x
, la resolución de sobrecarga del operador unario (§12.4.4) se aplica para seleccionar una implementación específica del operador. El operando se convierte en el tipo de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador. Los operadores unarios menos predefinidos son:
Negación de enteros:
int operator –(int x); long operator –(long x);
El resultado se calcula restando
X
de cero. Si el valor deX
es el valor representable más pequeño del tipo de operando (-2³¹ paraint
o -2⁶³ paralong
), la negación matemática deX
no se puede representar dentro del tipo de operando. Si esto ocurre dentro de unchecked
contexto, se produce unaSystem.OverflowException
excepción ; si se produce dentro de ununchecked
contexto, el resultado es el valor del operando y no se notifica el desbordamiento.Si el operando del operador de negación es de tipo
uint
, se convierte en el tipolong
y el tipo del resultado eslong
. Una excepción es la regla que permite escribir elint
valor−2147483648
(−2³¹) como un literal entero decimal (§6.4.5.3).Si el operando del operador de negación es de tipo
ulong
, se produce un error en tiempo de compilación. Una excepción es la regla que permite escribir ellong
valor−9223372036854775808
(−2⁶³) como un literal entero decimal (§6.4.5.3)Negación de punto flotante:
float operator –(float x); double operator –(double x);
El resultado es el valor de
X
con su signo invertido. Six
esNaN
, el resultado tambiénNaN
es .Negación decimal:
decimal operator –(decimal x);
El resultado se calcula restando
X
de cero. La negación decimal es equivalente a usar el operador unario menos del tipoSystem.Decimal
.
Las formas levantadas (§12.4.8) de los operadores unarios predefinidos sin levantar definidos anteriormente también están predefinidos.
Operador de negación lógica 12.9.4
Para una operación con el formato !x
, la resolución de sobrecarga del operador unario (§12.4.4) se aplica para seleccionar una implementación específica del operador. El operando se convierte en el tipo de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador. Solo existe un operador de negación lógica predefinido:
bool operator !(bool x);
Este operador calcula la negación lógica del operando: si el operando es true
, el resultado es false
. Si el operando es false
, el resultado es true
.
Las formas levantadas (§12.4.8) del operador de negación lógica predefinido no elevado definido anteriormente también están predefinidos.
Nota: Los operadores de negación lógica de prefijo y postfijo nulo (§12.8.9), mientras que representados por el mismo token léxico (!
), son distintos. nota final
Operador de complemento bit a bit 12.9.5
Para una operación con el formato ~x
, la resolución de sobrecarga del operador unario (§12.4.4) se aplica para seleccionar una implementación específica del operador. El operando se convierte en el tipo de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador. Los operadores de complemento bit a bit predefinidos son:
int operator ~(int x);
uint operator ~(uint x);
long operator ~(long x);
ulong operator ~(ulong x);
Para cada uno de estos operadores, el resultado de la operación es el complemento bit a bit de x
.
Cada tipo E
de enumeración proporciona implícitamente el siguiente operador de complemento bit a bit:
E operator ~(E x);
El resultado de evaluar ~x
, donde X
es una expresión de un tipo E
de enumeración con un tipo U
subyacente, es exactamente el mismo que evaluar (E)(~(U)x)
, excepto que la conversión a E
siempre se realiza como si estuviera en un unchecked
contexto (§12.8.20).
Las formas levantadas (§12.4.8) de los operadores predefinidos de complemento bit a bit predefinidos definidos anteriormente también están predefinidos.
12.9.6 Operadores de incremento y decremento de prefijo
pre_increment_expression
: '++' unary_expression
;
pre_decrement_expression
: '--' unary_expression
;
El operando de una operación de incremento o disminución de prefijo será una expresión clasificada como una variable, un acceso a propiedades o un acceso de indizador. El resultado de la operación es un valor del mismo tipo que el operando.
Si el operando de una operación de incremento o decremento de prefijo es un acceso de propiedad o indexador, la propiedad o el indexador tendrán un descriptor de acceso get y set. Si no es así, se produce un error en tiempo de enlace.
La resolución de sobrecarga del operador unario (§12.4.4) se aplica para seleccionar una implementación específica del operador. Existen operadores predefinidos ++
y --
para los siguientes tipos: sbyte
, byte
, ushort
int
uint
long
short
, char
float
ulong
double
, decimal
y cualquier tipo de enumeración. Los operadores predefinidos ++
devuelven el valor generado agregando 1
al operando y los operadores predefinidos --
devuelven el valor generado restando 1
del operando. En un checked
contexto, si el resultado de esta suma o resta está fuera del intervalo del tipo de resultado y el tipo de resultado es un tipo entero o tipo de enumeración, se produce una System.OverflowException
excepción .
Habrá una conversión implícita del tipo de valor devuelto del operador unario seleccionado al tipo del unary_expression; de lo contrario, se produce un error en tiempo de compilación.
El procesamiento en tiempo de ejecución de una operación de incremento o disminución de prefijo del formulario ++x
o --x
consta de los pasos siguientes:
- Si
x
se clasifica como una variable:x
se evalúa para generar la variable.- El valor de
x
se convierte en el tipo de operando del operador seleccionado y el operador se invoca con este valor como argumento. - El valor devuelto por el operador se convierte en el tipo de
x
. El valor resultante se almacena en la ubicación dada por la evaluación dex
. - y se convierte en el resultado de la operación.
- Si
x
se clasifica como acceso de propiedad o indexador:- La expresión de instancia (si
x
nostatic
es ) y la lista de argumentos (six
es un acceso de indexador) asociadax
a se evalúan y los resultados se usan en las invocaciones de descriptor de acceso get y set posteriores. - Se invoca el descriptor de acceso get de
x
. - El valor devuelto por el descriptor de acceso get se convierte en el tipo de operando del operador seleccionado y el operador se invoca con este valor como argumento.
- El valor devuelto por el operador se convierte en el tipo de
x
. El descriptor de acceso set dex
se invoca con este valor como argumento value. - 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 postfijo (§12.8.16). El resultado de o x--
es el valor de x++
antes de x
la operación, mientras que el resultado de ++x
o --x
es el valor de después de x
la operación. En cualquier caso, x
sí mismo tiene el mismo valor después de la operación.
Se puede invocar una implementación de operador ++
o operador --
mediante notación postfijo o prefijo. No es posible tener implementaciones de operador independientes para las dos notaciones.
También se predefinin las formas levantadas (§12.4.8) de los operadores predefinidos de incremento y decremento predefinidos definidos anteriormente.
12.9.7 Expresiones cast
Un cast_expression se usa para convertir explícitamente una expresión en un tipo determinado.
cast_expression
: '(' type ')' unary_expression
;
Un cast_expression del formulario (T)E
, donde T
es un tipo y E
es un unary_expression, realiza una conversión explícita (§10.3) del valor de E
al tipo T
. Si no existe ninguna conversión explícita de E
a T
, se produce un error en tiempo de enlace. De lo contrario, el resultado es el valor generado por la conversión explícita. El resultado siempre se clasifica como un valor, incluso si E
denota una variable.
La gramática de una cast_expression conduce a ciertas ambigüedades sintácticas.
Ejemplo: La expresión
(x)–y
puede interpretarse como un cast_expression (una conversión de–y
al tipox
) o como un additive_expression combinado con un parenthesized_expression (que calcula el valorx – y
). ejemplo final
Para resolver cast_expression ambigüedades, existe la siguiente regla: una secuencia de uno o varios tokens (§6.4) entre paréntesis se considera el inicio de un cast_expression solo si se cumple al menos uno de los siguientes elementos:
- La secuencia de tokens es la gramática correcta para un tipo, pero no para una expresión.
- La secuencia de tokens es la gramática correcta para un tipo y el token inmediatamente después de los paréntesis de cierre es el token "
~
", el token "!
", el token "(
", un identificador (§6.4.3), un literal (§6.4.5) o cualquier palabra clave (§6.4.4) exceptoas
yis
.
El término "gramática correcta" anterior significa únicamente que la secuencia de tokens se ajustará a la producción gramatical concreta. En concreto, no tiene en cuenta el significado real de ningún identificador constituyente.
Ejemplo: Si
x
yy
son identificadores, esx.y
la gramática correcta para un tipo, incluso six.y
realmente no denota un tipo. ejemplo final
Nota: A partir de la regla de desambiguación, sigue que, si
x
yy
son identificadores,(x)y
,(x)(y)
y(x)(-y)
son cast_expressions, pero(x)-y
no es, incluso six
identifica un tipo. Sin embargo, six
es una palabra clave que identifica un tipo predefinido (comoint
), los cuatro formularios se cast_expressions (porque tal palabra clave no pudo ser posiblemente una expresión por sí misma). nota final
12.9.8 Expresiones await
12.9.8.1 General
El await
operador se usa para suspender la evaluación de la función asincrónica envolvente hasta que se haya completado la operación asincrónica representada por el operando.
await_expression
: 'await' unary_expression
;
Solo se permite un await_expression en el cuerpo de una función asincrónica (§15.15). Dentro de la función asincrónica más cercana, no se producirá un await_expression en estos lugares:
- Dentro de una función anónima anidada (no asincrónica)
- Dentro del bloque de un lock_statement
- En una conversión de función anónima a un tipo de árbol de expresión (§10.7.3)
- En un contexto no seguro
Nota: Una await_expression no puede producirse en la mayoría de los lugares dentro de un query_expression, ya que se transforman sintácticamente para usar expresiones lambda no asincrónicas. nota final
Dentro de una función asincrónica, await
no se usará como available_identifier aunque se pueda usar el identificador @await
textual. Por lo tanto, no hay ambigüedad sintáctica entre await_expressions y varias expresiones que implican identificadores. Fuera de las funciones asincrónicas, await
actúa como un identificador normal.
El operando de un await_expression se denomina tarea. Representa una operación asincrónica que puede o no completarse en el momento en que se evalúa el await_expression . El propósito del await
operador es suspender la ejecución de la función asincrónica envolvente hasta que se complete la tarea esperada y, a continuación, obtener su resultado.
12.9.8.2 Expresiones awaitables
La tarea de un await_expression es necesaria para que se pueda esperar. Se puede esperar una expresión t
si una de las siguientes suspensiones:
t
es de tipo en tiempo de compilacióndynamic
t
tiene una instancia o método de extensión accesible llamadoGetAwaiter
sin parámetros y ningún parámetro de tipo, y un tipoA
de valor devuelto para el que se mantienen todas las siguientes operaciones:A
implementa la interfazSystem.Runtime.CompilerServices.INotifyCompletion
(en adelante conocida comoINotifyCompletion
por motivos de brevedad).A
tiene una propiedadIsCompleted
de instancia accesible y legible de tipobool
A
tiene un métodoGetResult
de instancia accesible sin parámetros y ningún parámetro de tipo
El propósito del GetAwaiter
método es obtener un awaiter para la tarea. El tipo A
se denomina tipo awaiter para la expresión await.
El propósito de la IsCompleted
propiedad es determinar si la tarea ya está completa. Si es así, no es necesario suspender la evaluación.
El propósito del INotifyCompletion.OnCompleted
método es registrar una "continuación" en la tarea; es decir, un delegado (de tipo System.Action
) que se invocará una vez completada la tarea.
El propósito del GetResult
método es obtener el resultado de la tarea una vez completado. Este resultado puede ser una finalización correcta, posiblemente con un valor de resultado, o puede ser una excepción producida por el GetResult
método .
12.9.8.3 Clasificación de expresiones await
La expresión await t
se clasifica de la misma manera que la expresión (t).GetAwaiter().GetResult()
. Por lo tanto, si el tipo de valor devuelto de GetResult
es void
, el await_expression se clasifica como nada. Si tiene un tipo devoid
valor no devuelto , el await_expression se clasifica como un valor de tipo T
.T
12.9.8.4 Evaluación en tiempo de ejecución de expresiones await
En tiempo de ejecución, la expresión await t
se evalúa de la siguiente manera:
- Un awaiter
a
se obtiene mediante la evaluación de la expresión(t).GetAwaiter()
. - Se
bool
b
obtiene mediante la evaluación de la expresión(a).IsCompleted
. - Si
b
esfalse
entonces la evaluación depende de sia
implementa la interfazSystem.Runtime.CompilerServices.ICriticalNotifyCompletion
(en adelante conocida comoICriticalNotifyCompletion
por brevedad). Esta comprobación se realiza en tiempo de enlace; Es decir, en tiempo de ejecución sia
tiene el tipodynamic
en tiempo de compilación y en tiempo de compilación. Anoter
el delegado de reanudación (§15.15):- Si
a
no implementaICriticalNotifyCompletion
, la expresión((a) as INotifyCompletion).OnCompleted(r)
se evalúa. - Si
a
implementaICriticalNotifyCompletion
, la expresión((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r)
se evalúa. - La evaluación se suspende y el control se devuelve al autor de la llamada actual de la función asincrónica.
- Si
- Inmediatamente después de (si
b
eratrue
), o después de la invocación posterior del delegado de reanudación (sib
esfalse
), se evalúa la expresión(a).GetResult()
. Si devuelve un valor, ese valor es el resultado del await_expression. De lo contrario, el resultado no es nada.
La implementación de un awaiter de los métodos INotifyCompletion.OnCompleted
de interfaz y ICriticalNotifyCompletion.UnsafeOnCompleted
debe hacer que el delegado r
se invoque como máximo una vez. De lo contrario, el comportamiento de la función asincrónica envolvente no está definido.
12.10 Operadores aritméticos
12.10.1 General
Los *
operadores , /
, %
, +
y -
se denominan operadores aritméticos.
multiplicative_expression
: unary_expression
| multiplicative_expression '*' unary_expression
| multiplicative_expression '/' unary_expression
| multiplicative_expression '%' unary_expression
;
additive_expression
: multiplicative_expression
| additive_expression '+' multiplicative_expression
| additive_expression '-' multiplicative_expression
;
Si un operando de un operador aritmético tiene el tipo dynamic
en tiempo de compilación , la expresión se enlaza dinámicamente (§12.3.3). En este caso, el tipo en tiempo de compilación de la expresión es dynamic
y la resolución descrita a continuación tendrá lugar en tiempo de ejecución mediante el tipo en tiempo de ejecución de esos operandos que tienen el tipo dynamic
de tiempo de compilación .
Operador de multiplicación 12.10.2
Para una operación con el formato x * y
, la resolución de sobrecarga del operador binario (§12.4.5) se aplica para seleccionar una implementación específica del operador. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador.
A continuación se enumeran los operadores de multiplicación predefinidos. Los operadores calculan todo el producto de x
y y
.
Multiplicación de enteros:
int operator *(int x, int y); uint operator *(uint x, uint y); long operator *(long x, long y); ulong operator *(ulong x, ulong y);
En un
checked
contexto, si el producto está fuera del intervalo del tipo de resultado, se produce unaSystem.OverflowException
excepción . En ununchecked
contexto, los desbordamientos no se notifican y se descartan los bits significativos de orden alto fuera del intervalo del tipo de resultado.Multiplicación de punto flotante:
float operator *(float x, float y); double operator *(double x, double y);
El producto se calcula según las reglas de aritmética IEC 60559. En la tabla siguiente se enumeran los resultados de todas las combinaciones posibles de valores finitos distintos de cero, ceros, infinities y NaNs. En la tabla,
x
ey
son valores finitos positivos.z
es el resultado dex * y
, redondeado al valor representable más cercano. Si la magnitud del resultado es demasiado grande para el tipo de destino,z
es infinito. Debido al redondeo,z
puede ser cero aunque ni seax
y
cero.+y
-y
+0
-0
+∞
-∞
NaN
+x
+z
-z
+0
-0
+∞
-∞
NaN
-x
-z
+z
-0
+0
-∞
+∞
NaN
+0
+0
-0
+0
-0
NaN
NaN
NaN
-0
-0
+0
-0
+0
NaN
NaN
NaN
+∞
+∞
-∞
NaN
NaN
+∞
-∞
NaN
-∞
-∞
+∞
NaN
NaN
-∞
+∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
(Excepto si se han indicado lo contrario, en las tablas de punto flotante de §12.10.2–§12.10.6 , el uso de "
+
" significa que el valor es positivo; el uso de "" significa que-
el valor es negativo; y la falta de un signo significa que el valor puede ser positivo o negativo o no tiene signo (NaN).Multiplicación decimal:
decimal operator *(decimal x, decimal y);
Si la magnitud del valor resultante es demasiado grande para representar en formato decimal, se produce una
System.OverflowException
excepción . Debido al redondeo, el resultado puede ser cero aunque ninguno de los operandos sea cero. La escala del resultado, antes de cualquier redondeo, es la suma de las escalas de los dos operandos. La multiplicación decimal equivale a usar el operador de multiplicación de tipoSystem.Decimal
.
Las formas levantadas (§12.4.8) de los operadores de multiplicación predefinidos sin levantar definidos anteriormente también están predefinidos.
Operador de división 12.10.3
Para una operación con el formato x / y
, la resolución de sobrecarga del operador binario (§12.4.5) se aplica para seleccionar una implementación específica del operador. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador.
Los operadores de división predefinidos se enumeran a continuación. Todos los operadores calculan el cociente de x
y y
.
División de enteros:
int operator /(int x, int y); uint operator /(uint x, uint y); long operator /(long x, long y); ulong operator /(ulong x, ulong y);
Si el valor del operando derecho es cero, se produce una
System.DivideByZeroException
excepción .La división redondea el resultado hacia cero. Por lo tanto, el valor absoluto del resultado es el entero más grande posible que es menor o igual que el valor absoluto del cociente de los dos operandos. El resultado es cero o positivo cuando los dos operandos tienen el mismo signo y cero o negativo cuando los dos operandos tienen signos opuestos.
Si el operando izquierdo es el menor que se puede representar
int
olong
el valor y el operando derecho es–1
, se produce un desbordamiento. En unchecked
contexto, esto haceSystem.ArithmeticException
que se produzca una (o una subclase de ahí). En ununchecked
contexto, se define la implementación en cuanto a siSystem.ArithmeticException
se produce una (o una subclase) o el desbordamiento no se notifica con el valor resultante que es el del operando izquierdo.División de punto flotante:
float operator /(float x, float y); double operator /(double x, double y);
El cociente se calcula según las reglas de aritmética IEC 60559. En la tabla siguiente se enumeran los resultados de todas las combinaciones posibles de valores finitos distintos de cero, ceros, infinities y NaNs. En la tabla,
x
ey
son valores finitos positivos.z
es el resultado dex / y
, redondeado al valor representable más cercano.+y
-y
+0
-0
+∞
-∞
NaN
+x
+z
-z
+∞
-∞
+0
-0
NaN
-x
-z
+z
-∞
+∞
-0
+0
NaN
+0
+0
-0
NaN
NaN
+0
-0
NaN
-0
-0
+0
NaN
NaN
-0
+0
NaN
+∞
+∞
-∞
+∞
-∞
NaN
NaN
NaN
-∞
-∞
+∞
-∞
+∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
División decimal:
decimal operator /(decimal x, decimal y);
Si el valor del operando derecho es cero, se produce una
System.DivideByZeroException
excepción . Si la magnitud del valor resultante es demasiado grande para representar en formato decimal, se produce unaSystem.OverflowException
excepción . Debido al redondeo, el resultado puede ser cero aunque el primer operando no sea cero. La escala del resultado, antes de cualquier redondeo, es la escala más cercana a la escala preferida que conservará un resultado igual al resultado exacto. La escala preferida es la escala dex
menos la escala dey
.La división decimal es equivalente a usar el operador de división de tipo
System.Decimal
.
Las formas levantadas (§12.4.8) de los operadores de división predefinidos sin levantar definidos anteriormente también están predefinidos.
Operador de resto 12.10.4
Para una operación con el formato x % y
, la resolución de sobrecarga del operador binario (§12.4.5) se aplica para seleccionar una implementación específica del operador. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador.
Los operadores de resto predefinidos se enumeran a continuación. Todos los operadores calculan el resto de la división entre x
y y
.
Resto entero:
int operator %(int x, int y); uint operator %(uint x, uint y); long operator %(long x, long y); ulong operator %(ulong x, ulong y);
El resultado de
x % y
es el valor generado porx – (x / y) * y
. Siy
es cero, se produce unaSystem.DivideByZeroException
excepción .Si el operando izquierdo es el menor
int
olong
el valor y el operando derecho es–1
, se produce unaSystem.OverflowException
excepción si y solo six / y
se produciría una excepción.Resto de punto flotante:
float operator %(float x, float y); double operator %(double x, double y);
En la tabla siguiente se enumeran los resultados de todas las combinaciones posibles de valores finitos distintos de cero, ceros, infinities y NaNs. En la tabla,
x
ey
son valores finitos positivos.z
es el resultado dex % y
y se calcula comox – n * y
, donde n es el entero más grande posible que es menor o igual quex / y
. Este método de computación del resto es análogo al usado para operandos enteros, pero difiere de la definición iec 60559 (en la quen
es el entero más cercano ax / y
).+y
-y
+0
-0
+∞
-∞
NaN
+x
+z
+z
NaN
NaN
+x
+x
NaN
-x
-z
-z
NaN
NaN
-x
-x
NaN
+0
+0
+0
NaN
NaN
+0
+0
NaN
-0
-0
-0
NaN
NaN
-0
-0
NaN
+∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
-∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
Resto decimal:
decimal operator %(decimal x, decimal y);
Si el valor del operando derecho es cero, se produce una
System.DivideByZeroException
excepción . Se define la implementación cuandoSystem.ArithmeticException
se produce una (o una subclase de ahí). Una implementación conforme no producirá una excepción enx % y
ningún caso enx / y
el que no produzca una excepción. La escala del resultado, antes de cualquier redondeo, es mayor de las escalas de los dos operandos y el signo del resultado, si no es cero, es igual que el dex
.El resto decimal es equivalente al uso del operador de resto de tipo
System.Decimal
.Nota: Estas reglas garantizan que para todos los tipos, el resultado nunca tiene el signo opuesto del operando izquierdo. nota final
Las formas levantadas (§12.4.8) de los operadores de resto predefinidos sin levantar definidos anteriormente también están predefinidos.
Operador de suma 12.10.5
Para una operación con el formato x + y
, la resolución de sobrecarga del operador binario (§12.4.5) se aplica para seleccionar una implementación específica del operador. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador.
A continuación se enumeran los operadores de suma predefinidos. Para los tipos numéricos y de enumeración, los operadores de suma predefinidos calculan la suma de los dos operandos. Cuando uno o ambos operandos son de tipo string
, los operadores de suma predefinidos concatenan la representación de cadena de los operandos.
Suma de enteros:
int operator +(int x, int y); uint operator +(uint x, uint y); long operator +(long x, long y); ulong operator +(ulong x, ulong y
En un
checked
contexto, si la suma está fuera del intervalo del tipo de resultado, se produce unaSystem.OverflowException
excepción . En ununchecked
contexto, los desbordamientos no se notifican y se descartan los bits significativos de orden alto fuera del intervalo del tipo de resultado.Adición de punto flotante:
float operator +(float x, float y); double operator +(double x, double y);
La suma se calcula según las reglas de aritmética IEC 60559. En la tabla siguiente se enumeran los resultados de todas las combinaciones posibles de valores finitos distintos de cero, ceros, infinities y NaNs. En la tabla,
x
ey
son valores finitos distintos de cero yz
es el resultado dex + y
. Six
ey
tienen la misma magnitud pero signos opuestos,z
es cero positivo. Six + y
es demasiado grande para representar en el tipo de destino,z
es un infinito con el mismo signo quex + y
.y
+0
-0
+∞
-∞
NaN
x
z
x
x
+∞
-∞
NaN
+0
y
+0
+0
+∞
–∞
NaN
-0
y
+0
-0
+∞
-∞
NaN
+∞
+∞
+∞
+∞
+∞
NaN
NaN
-∞
-∞
-∞
-∞
NaN
-∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
Suma decimal:
decimal operator +(decimal x, decimal y);
Si la magnitud del valor resultante es demasiado grande para representar en formato decimal, se produce una
System.OverflowException
excepción . La escala del resultado, antes de cualquier redondeo, es mayor de las escalas de los dos operandos.La suma decimal es equivalente al uso del operador de suma de tipo
System.Decimal
.Adición de enumeración. Cada tipo de enumeración proporciona implícitamente los siguientes operadores predefinidos, donde
E
es el tipo de enumeración yU
es 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 concatenación de cadenas esnull
, se sustituye una cadena vacía. De lo contrario, cualquierstring
operando no se convierte en su representación de cadena invocando el método virtualToString
heredado del tipoobject
. SiToString
devuelvenull
, se sustituye una cadena vacía.Ejemplo:
class Test { static void Main() { string s = null; Console.WriteLine("s = >" + s + "<"); // Displays s = >< int i = 1; Console.WriteLine("i = " + i); // Displays i = 1 float f = 1.2300E+15F; Console.WriteLine("f = " + f); // Displays f = 1.23E+15 decimal d = 2.900m; Console.WriteLine("d = " + d); // Displays d = 2.900 } }
La salida que se muestra en los comentarios es el resultado típico en un sistema en inglés de EE. UU. La salida precisa puede depender de la configuración regional del entorno de ejecución. El propio operador de concatenación de cadenas se comporta de la misma manera en cada caso, pero los métodos a los que se llama implícitamente durante la
ToString
ejecución podrían verse afectados por la configuración regional.ejemplo final
El resultado del operador de concatenación de cadena es un
string
que consta de los caracteres del operando izquierdo seguido de los caracteres del operando derecho. El operador de concatenación de cadenas nunca devuelve unnull
valor. Se puede producir unaSystem.OutOfMemoryException
excepción si no hay suficiente memoria disponible para asignar la cadena resultante.Combinación de delegados. Cada tipo de delegado proporciona implícitamente el siguiente operador predefinido, donde
D
es el tipo de delegado:D operator +(D x, D y);
Si el primer operando es
null
, el resultado de la operación es el valor del segundo operando (aunque tambiénnull
sea ). De lo contrario, si el segundo operando esnull
, el resultado de la operación es el valor del primer operando. De lo contrario, el resultado de la operación es una nueva instancia de delegado cuya lista de invocación consta de los elementos de la lista de invocación del primer operando, seguido de los elementos de la lista de invocación del segundo operando. Es decir, la lista de invocación del delegado resultante es la concatenación de las listas de invocación de los dos operandos.Nota: Para obtener ejemplos de combinación de delegados, consulte §12.10.6 y §20.6. Puesto
System.Delegate
que no es un tipo delegado, el operador + no está definido para él. nota final
Las formas levantadas (§12.4.8) de los operadores de suma predefinidos sin levantar definidos anteriormente también están predefinidos.
Operador de resta 12.10.6
Para una operación con el formato x – y
, la resolución de sobrecarga del operador binario (§12.4.5) se aplica para seleccionar una implementación específica del operador. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador.
A continuación se enumeran los operadores de resta predefinidos. Todos los operadores restan y
de x
.
Resta de enteros:
int operator –(int x, int y); uint operator –(uint x, uint y); long operator –(long x, long y); ulong operator –(ulong x, ulong y
En un
checked
contexto, si la diferencia está fuera del intervalo del tipo de resultado, se produce unaSystem.OverflowException
excepción . En ununchecked
contexto, los desbordamientos no se notifican y se descartan los bits significativos de orden alto fuera del intervalo del tipo de resultado.Resta de punto flotante:
float operator –(float x, float y); double operator –(double x, double y);
La diferencia se calcula según las reglas de aritmética IEC 60559. En la tabla siguiente se enumeran los resultados de todas las combinaciones posibles de valores finitos distintos de cero, ceros, infinities y NaNs. En la tabla,
x
ey
son valores finitos distintos de cero yz
es el resultado dex – y
. Six
ey
son iguales,z
es cero positivo. Six – y
es demasiado grande para representar en el tipo de destino,z
es un infinito con el mismo signo quex – y
.y
+0
-0
+∞
-∞
NaN
x
z
x
x
-∞
+∞
NaN
+0
-y
+0
+0
-∞
+∞
NaN
-0
-y
-0
+0
-∞
+∞
NaN
+∞
+∞
+∞
+∞
NaN
+∞
NaN
-∞
-∞
-∞
-∞
-∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
(En la tabla anterior, las
-y
entradas indican la negación dey
, no que el valor es negativo).Resta decimal:
decimal operator –(decimal x, decimal y);
Si la magnitud del valor resultante es demasiado grande para representar en formato decimal, se produce una
System.OverflowException
excepción . La escala del resultado, antes de cualquier redondeo, es mayor de las escalas de los dos operandos.La resta decimal es equivalente a usar el operador de resta de tipo
System.Decimal
.Resta de enumeración. Cada tipo de enumeración proporciona implícitamente el siguiente operador predefinido, donde
E
es el tipo de enumeración yU
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 dex
yy
, y el tipo del resultado es el tipo subyacente de la enumeración.E operator –(E x, U y);
Este operador se evalúa exactamente como
(E)((U)x – y)
. En otras palabras, el operador resta un valor del tipo subyacente de la enumeración, lo que produce un valor de la enumeración.Eliminación del delegado. Cada tipo de delegado proporciona implícitamente el siguiente operador predefinido, donde
D
es el tipo de delegado:D operator –(D x, D y);
La semántica es la siguiente:
- Si el primer operando es
null
, el resultado de la operación esnull
. - De lo contrario, si el segundo operando es
null
, el resultado de la operación es el valor del primer operando. - De lo contrario, ambos operandos representan listas de invocación no vacías (§20.2).
- Si las listas comparan igual, según lo determinado por el operador de igualdad de delegado (§12.12.9), el resultado de la operación es
null
. - De lo contrario, el resultado de la operación es una nueva lista de invocación que consta de la lista del primer operando con las entradas del segundo operando quitadas de ella, siempre que la lista del segundo operando sea una sublist del primero. (Para determinar la igualdad de la sublist, las entradas correspondientes se comparan con el operador de igualdad del delegado). Si la segunda lista del operando coincide con varias sublists de entradas contiguas en la lista del primer operando, se quita la última sublist coincidente de entradas contiguas.
- De lo contrario, el resultado de la operación es el valor del operando izquierdo.
- Si las listas comparan igual, según lo determinado por el operador de igualdad de delegado (§12.12.9), el resultado de la operación es
Ninguna de las listas de operandos (si las hay) cambia en el proceso.
Ejemplo:
delegate void D(int x); class C { public static void M1(int i) { ... } public static void M2(int i) { ... } } class Test { static void Main() { D cd1 = new D(C.M1); D cd2 = new D(C.M2); D list = null; list = null - cd1; // null list = (cd1 + cd2 + cd2 + cd1) - null; // M1 + M2 + M2 + M1 list = (cd1 + cd2 + cd2 + cd1) - cd1; // M1 + M2 + M2 list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2); // M2 + M1 list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd2); // M1 + M1 list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd1); // M1 + M2 list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd1); // M1 + M2 + M2 + M1 list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2 + cd2 + cd1); // null } }
ejemplo final
- Si el primer operando es
Las formas levantadas (§12.4.8) de los operadores de resta predefinidos no eliminados definidos anteriormente también están predefinidos.
Operadores de desplazamiento 12.11
Los <<
operadores y >>
se usan para realizar operaciones de desplazamiento de bits.
shift_expression
: additive_expression
| shift_expression '<<' additive_expression
| shift_expression right_shift additive_expression
;
Si un operando de un shift_expression tiene el tipo dynamic
en tiempo de compilación , la expresión se enlaza dinámicamente (§12.3.3). En este caso, el tipo en tiempo de compilación de la expresión es dynamic
y la resolución descrita a continuación tendrá lugar en tiempo de ejecución mediante el tipo en tiempo de ejecución de esos operandos que tienen el tipo dynamic
de tiempo de compilación .
Para una operación del formulario x << count
o x >> count
, la resolución de sobrecarga del operador binario (§12.4.5) se aplica para seleccionar una implementación de operador específica. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador.
Al declarar un operador de desplazamiento sobrecargado, el tipo del primer operando siempre será la clase o estructura que contiene la declaración del operador, y el tipo del segundo operando siempre int
será .
Los operadores de desplazamiento predefinidos se enumeran a continuación.
Desplazamiento a la izquierda:
int operator <<(int x, int count); uint operator <<(uint x, int count); long operator <<(long x, int count); ulong operator <<(ulong x, int count);
El
<<
operador cambia de izquierda por un número de bits calculadosx
como se describe a continuación.Los bits de orden superior fuera del intervalo del tipo de resultado de
x
se descartan, los bits restantes se desplazan a la izquierda y las posiciones de bits vacías de orden bajo se establecen en cero.Desplazamiento a la derecha:
int operator >>(int x, int count); uint operator >>(uint x, int count); long operator >>(long x, int count); ulong operator >>(ulong x, int count);
El
>>
operador cambia a la derecha por un número de bits calculadosx
como se describe a continuación.Cuando
x
es de tipoint
olong
, los bits de orden bajo dex
se descartan, los bits restantes se desplazan hacia la derecha y las posiciones de bits vacías de orden alto se establecen en cero six
no es negativo y se establecen en uno six
es negativo.Cuando
x
es de tipouint
oulong
, los bits de orden bajo dex
se descartan, los bits restantes se desplazan hacia la derecha y las posiciones de bits vacías de orden alto se establecen en cero.
Para los operadores predefinidos, el número de bits que se van a desplazar se calcula de la siguiente manera:
- Cuando el tipo de
x
esint
ouint
, el recuento de desplazamientos recibe cinco bits de orden bajo decount
. En otras palabras, el recuento de turnos se calcula desdecount & 0x1F
. - Cuando el tipo de
x
eslong
oulong
, el recuento de desplazamientos recibe seis bits de orden bajo decount
. En otras palabras, el recuento de turnos se calcula desdecount & 0x3F
.
Si el recuento de desplazamientos resultante es cero, los operadores de desplazamiento simplemente devuelven el valor de x
.
Las operaciones de desplazamiento nunca producen desbordamientos y generan los mismos resultados en contextos checked y unchecked.
Cuando el operando izquierdo del >>
operador es de un tipo entero firmado, el operador realiza un desplazamiento aritmético a la derecha, donde el valor del bit más significativo (el bit de signo) del operando se propaga a las posiciones de bits vacías de orden alto. Cuando el operando izquierdo del >>
operador es de un tipo entero sin signo, el operador realiza un desplazamiento lógico a la derecha, donde las posiciones de bits vacías de orden alto siempre se establecen en cero. Para realizar la operación opuesta de que se deduce del tipo de operando, se pueden usar conversiones explícitas.
Ejemplo: si
x
es una variable de tipoint
, la operaciónunchecked ((int)((uint)x >> y))
realiza un desplazamiento lógico a la derecha dex
. ejemplo final
Las formas levantadas (§12.4.8) de los operadores de desplazamiento predefinidos sin levantar definidos anteriormente también están predefinidos.
12.12 Operadores relacionales y de pruebas de tipos
12.12.1 General
Los ==
operadores , !=
, <
, <=
>
, >=
, , is
y as
se denominan operadores relacionales y de prueba de tipos.
relational_expression
: shift_expression
| relational_expression '<' shift_expression
| relational_expression '>' shift_expression
| relational_expression '<=' shift_expression
| relational_expression '>=' shift_expression
| relational_expression 'is' type
| relational_expression 'is' pattern
| relational_expression 'as' type
;
equality_expression
: relational_expression
| equality_expression '==' relational_expression
| equality_expression '!=' relational_expression
;
Nota: La búsqueda del operando derecho del
is
operador debe probar primero como un tipo y, a continuación, como una expresión que puede abarcar varios tokens. En el caso de que el operando sea una expreesión, la expresión de patrón debe tener prioridad al menos tan alta como shift_expression. nota final
El is
operador se describe en §12.12.12 y el as
operador se describe en §12.12.13.
Los ==
operadores , !=
, <
, >
y >=
<=
son operadores de comparación.
Si se usa un default_literal (§12.8.21) como operando de un <
operador , >
, <=
o >=
, se produce un error en tiempo de compilación.
Si se usa un default_literal como operandos de un ==
operador o !=
, se produce un error en tiempo de compilación. Si se usa un default_literal como operando izquierdo del is
operador o as
, se produce un error en tiempo de compilación.
Si un operando de un operador de comparación tiene el tipo dynamic
en tiempo de compilación , la expresión se enlaza dinámicamente (§12.3.3). En este caso, el tipo en tiempo de compilación de la expresión es dynamic
y la resolución descrita a continuación tendrá lugar en tiempo de ejecución mediante el tipo en tiempo de ejecución de esos operandos que tienen el tipo dynamic
en tiempo de compilación .
Para una operación del formulario x «op» y
, donde «op» es un operador de comparación, se aplica la resolución de sobrecarga (§12.4.5) para seleccionar una implementación de operador específica. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador. Si ambos operandos de un equality_expression son el null
literal, no se realiza la resolución de sobrecarga y la expresión se evalúa como un valor constante de true
o false
según si el operador es ==
o !=
.
Los operadores de comparación predefinidos se describen en las subclases siguientes. Todos los operadores de comparación predefinidos devuelven un resultado de tipo bool, como se describe en la tabla siguiente.
Operación | Resultado |
---|---|
x == y |
true si x es igual a y , de lo contrario, false |
x != y |
true si x no es igual a y , de lo contrario, false |
x < y |
true si x es menor que y , false en caso contrario |
x > y |
true si x es mayor que y , false en caso contrario |
x <= y |
true si x es menor o igual que y , false en caso contrario |
x >= y |
true si x es mayor o igual que y , false en caso contrario |
12.12.2 Operadores de comparación enteros
Los operadores de comparación de enteros predefinidos son:
bool operator ==(int x, int y);
bool operator ==(uint x, uint y);
bool operator ==(long x, long y);
bool operator ==(ulong x, ulong y);
bool operator !=(int x, int y);
bool operator !=(uint x, uint y);
bool operator !=(long x, long y);
bool operator !=(ulong x, ulong y);
bool operator <(int x, int y);
bool operator <(uint x, uint y);
bool operator <(long x, long y);
bool operator <(ulong x, ulong y);
bool operator >(int x, int y);
bool operator >(uint x, uint y);
bool operator >(long x, long y);
bool operator >(ulong x, ulong y);
bool operator <=(int x, int y);
bool operator <=(uint x, uint y);
bool operator <=(long x, long y);
bool operator <=(ulong x, ulong y);
bool operator >=(int x, int y);
bool operator >=(uint x, uint y);
bool operator >=(long x, long y);
bool operator >=(ulong x, ulong y);
Cada uno de estos operadores compara los valores numéricos de los dos operandos enteros y devuelve un bool
valor que indica si la relación concreta es true
o false
.
Las formas levantadas (§12.4.8) de los operadores de comparación de enteros predefinidos no eliminados definidos anteriormente también están predefinidos.
12.12.3 Operadores de comparación de punto flotante
Los operadores de comparación de punto flotante predefinidos son:
bool operator ==(float x, float y);
bool operator ==(double x, double y);
bool operator !=(float x, float y);
bool operator !=(double x, double y);
bool operator <(float x, float y);
bool operator <(double x, double y);
bool operator >(float x, float y);
bool operator >(double x, double y);
bool operator <=(float x, float y);
bool operator <=(double x, double y);
bool operator >=(float x, float y);
bool operator >=(double x, double y);
Los operadores comparan los operandos según las reglas del estándar IEC 60559:
Si cualquiera de los operandos es NaN, el resultado es false
para todos los operadores excepto !=
, para los que el resultado es true
. Para los dos operandos, x != y
siempre produce el mismo resultado que !(x == y)
. Sin embargo, cuando uno o ambos operandos son NaN, los <
operadores , >
, <=
y >=
no producen los mismos resultados que la negación lógica del operador opuesto.
Ejemplo: si cualquiera de
x
yy
es NaN, entoncesx < y
esfalse
, pero!(x >= y)
estrue
. ejemplo final
Cuando ninguno de los operandos es NaN, los operadores comparan los valores de los dos operandos de punto flotante con respecto a la ordenación.
–∞ < –max < ... < –min < –0.0 == +0.0 < +min < ... < +max < +∞
donde min
y max
son los valores finitos positivos más pequeños y mayores que se pueden representar en el formato de punto flotante determinado. Los efectos importantes de este orden son:
- Los ceros negativos y positivos se consideran iguales.
- Un infinito negativo se considera menor que todos los demás valores, pero igual a otro infinito negativo.
- Un infinito positivo se considera mayor que todos los demás valores, pero igual a otro infinito positivo.
Las formas levantadas (§12.4.8) de los operadores de comparación predefinidos de punto flotante predefinidos definidos anteriormente también están predefinidos.
12.12.4 Operadores de comparación decimales
Los operadores de comparación decimal predefinidos son:
bool operator ==(decimal x, decimal y);
bool operator !=(decimal x, decimal y);
bool operator <(decimal x, decimal y);
bool operator >(decimal x, decimal y);
bool operator <=(decimal x, decimal y);
bool operator >=(decimal x, decimal y);
Cada uno de estos operadores compara los valores numéricos de los dos operandos decimales y devuelve un bool
valor que indica si la relación concreta es true
o false
. Cada comparación decimal equivale a usar el operador relacional o de igualdad correspondiente de tipo System.Decimal
.
Las formas levantadas (§12.4.8) de los operadores de comparación decimal predefinidos no eliminados definidos anteriormente también están predefinidos.
12.12.5 Operadores de igualdad booleanos
Los operadores de igualdad booleanos predefinidos son:
bool operator ==(bool x, bool y);
bool operator !=(bool x, bool y);
El resultado de ==
es true
si y x
y
son true
o si ambos x
y y
son false
. De lo contrario, el resultado es false
.
El resultado de !=
es false
si y x
y
son true
o si ambos x
y y
son false
. De lo contrario, el resultado es true
. Cuando los operandos son de tipo bool
, el !=
operador genera el mismo resultado que el ^
operador .
Las formas levantadas (§12.4.8) de los operadores de igualdad booleanos predefinidos no eliminados definidos anteriormente también están predefinidos.
12.12.6 Operadores de comparación de enumeración
Cada tipo de enumeración proporciona implícitamente los siguientes operadores de comparación predefinidos
bool operator ==(E x, E y);
bool operator !=(E x, E y);
bool operator <(E x, E y);
bool operator >(E x, E y);
bool operator <=(E x, E y);
bool operator >=(E x, E y);
El resultado de evaluar x «op» y
, donde x e y son expresiones de un tipo E
de enumeración con un tipo U
subyacente , y «op» es uno de los operadores de comparación, es exactamente igual que evaluar ((U)x) «op» ((U)y)
. En otras palabras, los operadores de comparación de tipos de enumeración simplemente comparan los valores enteros subyacentes de los dos operandos.
Las formas levantadas (§12.4.8) de los operadores de comparación de enumeración predefinidos no eliminados definidos anteriormente también están predefinidos.
12.12.7 Operadores de igualdad de tipos de referencia
Cada tipo de C
clase proporciona implícitamente los siguientes operadores de igualdad de tipos de referencia predefinidos:
bool operator ==(C x, C y);
bool operator !=(C x, C y);
a menos que existan operadores de igualdad predefinidos para C
(por ejemplo, cuando C
es string
o System.Delegate
).
Los operadores devuelven el resultado de comparar las dos referencias para la igualdad o la no igualdad. operator ==
devuelve true
si y solo si x
y y
hacen referencia a la misma instancia o son ambas null
, mientras que operator !=
devuelve true
si y solo si operator ==
con los mismos operandos devolverían false
.
Además de las reglas de aplicabilidad normales (§12.6.4.2), los operadores predefinidos de igualdad de tipos de referencia requieren uno de los siguientes para poder aplicarse:
- Ambos operandos son un valor de un tipo conocido como un reference_type o el literal
null
. Además, existe una conversión de referencia explícita o de identidad (§10.3.5) de cualquiera de los operandos al tipo del otro operando. - Un operando es el literal
null
y el otro operando es un valor de tipoT
dondeT
es un type_parameter que no se sabe que es un tipo de valor y no tiene la restricción de tipo de valor.- Si en tiempo de ejecución
T
es un tipo de valor que no acepta valores NULL, el resultado de==
esfalse
y el resultado de!=
estrue
. - Si en tiempo de ejecución
T
es un tipo de valor que acepta valores NULL, el resultado se calcula a partir de laHasValue
propiedad del operando, como se describe en (§12.12.10). - Si en tiempo de ejecución
T
es un tipo de referencia, el resultado estrue
si el operando esnull
yfalse
, de lo contrario, .
- Si en tiempo de ejecución
A menos que se cumpla una de estas condiciones, se produce un error en tiempo de enlace.
Nota: Las implicaciones importantes de estas reglas son:
- Se trata de un error en tiempo de enlace para usar los operadores predefinidos de igualdad de tipos de referencia para comparar dos referencias que se sabe que son diferentes en tiempo de enlace. Por ejemplo, si los tipos de tiempo de enlace de los operandos son dos tipos de clase y, si ninguno deriva del otro, sería imposible que los dos operandos hagan referencia al mismo objeto. Por lo tanto, la operación se considera un error en tiempo de enlace.
- Los operadores predefinidos de igualdad de tipos de referencia no permiten comparar operandos de tipo de valor (excepto cuando los parámetros de tipo se comparan con
null
, que se controla especialmente).- Los operandos de operadores predefinidos de igualdad de tipos de referencia nunca se colocan en conversión boxeada. No tendría sentido realizar estas operaciones boxing, ya que las referencias a las instancias boxing recién asignadas difieren necesariamente de todas las demás referencias.
Para una operación del formulario
x == y
ox != y
, si existe algún usuario definidooperator ==
poroperator !=
el usuario o existe, las reglas de resolución de sobrecarga del operador (§12.4.5) seleccionarán ese operador en lugar del operador de igualdad de tipos de referencia predefinido. Siempre es posible seleccionar el operador de igualdad de tipos de referencia predefinido mediante la conversión explícita de uno o ambos operandos al tipoobject
.nota final
Ejemplo: en el ejemplo siguiente se comprueba si un argumento de un tipo de parámetro de tipo sin restricciones es
null
.class C<T> { void F(T x) { if (x == null) { throw new ArgumentNullException(); } ... } }
La
x == null
construcción se permite aunqueT
podría representar un tipo de valor que no acepta valores NULL y el resultado se define simplemente para serfalse
cuandoT
es un tipo de valor que no acepta valores NULL.ejemplo final
Para una operación del formulario x == y
o x != y
, si procede operator ==
o operator !=
existe, las reglas de resolución de sobrecarga del operador (§12.4.5) seleccionarán ese operador en lugar del operador de igualdad de tipos de referencia predefinido.
Nota: Siempre es posible seleccionar el operador de igualdad de tipos de referencia predefinido mediante la conversión explícita de ambos operandos al tipo
object
. nota final
Ejemplo: El ejemplo
class Test { static void Main() { string s = "Test"; string t = string.Copy(s); Console.WriteLine(s == t); Console.WriteLine((object)s == t); Console.WriteLine(s == (object)t); Console.WriteLine((object)s == (object)t); } }
genera el resultado
True False False False
Las
s
variables yt
hacen referencia a dos instancias de cadena distintas que contienen los mismos caracteres. La primera comparación generaTrue
porque el operador de igualdad de cadenas predefinido (§12.12.8) se selecciona cuando ambos operandos son de tipostring
. El resto de comparaciones de todas las salidasFalse
porque la sobrecarga deoperator ==
en elstring
tipo no es aplicable cuando ninguno de los operandos tiene un tipo de tiempo de enlace deobject
.Tenga en cuenta que la técnica anterior no es significativa para los tipos de valor. En el ejemplo
class Test { static void Main() { int i = 123; int j = 123; Console.WriteLine((object)i == (object)j); } }
False
salidas porque las conversiones crean referencias a dos instancias independientes de valores boxedint
.ejemplo final
12.12.8 Operadores de igualdad de cadenas
Los operadores de igualdad de cadenas predefinidos son:
bool operator ==(string x, string y);
bool operator !=(string x, string y);
Dos string
valores se consideran iguales cuando se cumple uno de los siguientes valores:
- Ambos valores son
null
. - Ambos valores no son
null
referencias a instancias de cadena que tienen longitudes idénticas y caracteres idénticos en cada posición de carácter.
Los operadores de igualdad de cadenas comparan valores de cadena en lugar de referencias de cadena. Cuando dos instancias de cadena independientes contienen exactamente la misma secuencia de caracteres, los valores de las cadenas son iguales, pero las referencias son diferentes.
Nota: Como se describe en §12.12.7, los operadores de igualdad de tipos de referencia se pueden usar para comparar referencias de cadena en lugar de valores de cadena. nota final
12.12.9 Operadores de igualdad de delegados
Los operadores de igualdad de delegados predefinidos son:
bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y);
Dos instancias de delegado se consideran iguales a las siguientes:
- Si cualquiera de las instancias de delegado es
null
, son iguales si y solo si ambos sonnull
. - Si los delegados tienen un tipo de tiempo de ejecución diferente, nunca son iguales.
- Si ambas instancias del delegado tienen una lista de invocación (§20.2), esas instancias son iguales si y solo si sus listas de invocación tienen la misma longitud y cada entrada de la lista de invocación de una es igual (como se define a continuación) a la entrada correspondiente, en orden, en la lista de invocación del otro.
Las siguientes reglas rigen la igualdad de entradas de lista de invocación:
- Si dos entradas de lista de invocación hacen referencia al mismo método estático, las entradas son iguales.
- Si dos entradas de lista de invocación hacen referencia al mismo método no estático en el mismo objeto de destino (según lo definido por los operadores de igualdad de referencia), las entradas son iguales.
- Las entradas de la lista de invocación generadas a partir de la evaluación de funciones anónimas semánticamente idénticas (§12.19) con el mismo conjunto (posiblemente vacío) de instancias de variables externas capturadas pueden ser iguales (pero no necesarios).
Si la resolución de sobrecarga del operador se resuelve en cualquier operador de igualdad de delegados y los tipos de tiempo de enlace de ambos operandos son tipos delegados, como se describe en §20 en lugar System.Delegate
de , y no hay ninguna conversión de identidad entre los tipos de operando de tipo de enlace, se produce un error en tiempo de enlace.
Nota: Esta regla impide las comparaciones que nunca pueden considerar
null
valores no iguales debido a que son referencias a instancias de diferentes tipos de delegados. nota final
12.12.10 Operadores de igualdad entre tipos de valor que aceptan valores NULL y el literal NULL
Los ==
operadores y !=
permiten que un operando sea un valor de un tipo de valor que acepta valores NULL y el otro sea el literal, incluso si no existe ningún operador predefinido o definido por el null
usuario (en forma no levantada o levantada) para la operación.
Para una operación de uno de los formularios
x == null null == x x != null null != x
donde x
es una expresión de un tipo de valor que acepta valores NULL, si la resolución de sobrecarga del operador (§12.4.5) no encuentra un operador aplicable, el resultado se calcula en su lugar desde la HasValue
propiedad de x
. En concreto, las dos primeras formas se traducen en !x.HasValue
y las dos últimas se traducen en x.HasValue
.
12.12.11 Operadores de igualdad de tupla
Los operadores de igualdad de tupla se aplican en pares a los elementos de los operandos de tupla en orden léxico.
Si cada operando x
y de un ==
operador o !=
se clasifica como una tupla o como un valor con un tipo de tupla (§8.3.11), el operador es un operador de igualdad y
de tupla.
Si un operando e
se clasifica como una tupla, los elementos e1...en
serán los resultados de evaluar las expresiones de elemento de la expresión de tupla. De lo contrario, si e
es un valor de un tipo de tupla, los elementos serán t.Item1...t.Itemn
donde t
es el resultado de evaluar e
.
Los operandos x
y y
de un operador de igualdad de tupla tendrán la misma aridad o se producirá un error en tiempo de compilación. Para cada par de elementos xi
y yi
, se aplicará el mismo operador de igualdad y producirá un resultado de tipo bool
, dynamic
, un tipo que tenga una conversión implícita a bool
o un tipo que defina los true
operadores y false
.
El operador x == y
de igualdad de tupla se evalúa de la siguiente manera:
- Se evalúa el operando
x
izquierdo. - Se evalúa el operando
y
del lado derecho. - Para cada par de elementos
xi
yyi
en orden léxico:- El operador
xi == yi
se evalúa y se obtiene un resultado de tipobool
de la siguiente manera:- Si la comparación produjo un
bool
resultado, ese es el resultado. - De lo contrario, si la comparación produjo un
dynamic
, el operadorfalse
se invoca dinámicamente en él y el valor resultantebool
se niega con el operador de negación lógica (!
). - De lo contrario, si el tipo de la comparación tiene una conversión implícita a
bool
, esa conversión se aplica. - De lo contrario, si el tipo de la comparación tiene un operador
false
, ese operador se invoca y el valor resultantebool
se niega con el operador de negación lógica (!
).
- Si la comparación produjo un
- Si el resultado
bool
esfalse
, no se produce ninguna evaluación adicional y el resultado del operador de igualdad de tupla esfalse
.
- El operador
- Si todas las comparaciones de elementos produjeron
true
, el resultado del operador de igualdad de tupla estrue
.
El operador x != y
de igualdad de tupla se evalúa de la siguiente manera:
- Se evalúa el operando
x
izquierdo. - Se evalúa el operando
y
del lado derecho. - Para cada par de elementos
xi
yyi
en orden léxico:- El operador
xi != yi
se evalúa y se obtiene un resultado de tipobool
de la siguiente manera:- Si la comparación produjo un
bool
resultado, ese es el resultado. - De lo contrario, si la comparación produjo un
dynamic
, el operadortrue
se invoca dinámicamente en él y el valor resultantebool
es el resultado. - De lo contrario, si el tipo de la comparación tiene una conversión implícita a
bool
, esa conversión se aplica. - De lo contrario, si el tipo de la comparación tiene un operador
true
, ese operador se invoca y el valor resultantebool
es el resultado.
- Si la comparación produjo un
- Si el resultado
bool
estrue
, no se produce ninguna evaluación adicional y el resultado del operador de igualdad de tupla estrue
.
- El operador
- Si todas las comparaciones de elementos produjeron
false
, el resultado del operador de igualdad de tupla esfalse
.
12.12.12 El operador is
Hay dos formas del is
operador. Uno es el operador is-type, que tiene un tipo en el lado derecho. El otro es el operador is-pattern, que tiene un patrón en el lado derecho.
12.12.12.1 Operador is-type
El operador is-type se usa para comprobar si el tipo en tiempo de ejecución de un objeto es compatible con un tipo determinado. La comprobación se realiza en tiempo de ejecución. El resultado de la operación E is T
, donde E
es una expresión y T
es un tipo distinto dynamic
de , es un valor booleano que indica si E
no es null y se puede convertir correctamente al tipo T
mediante una conversión de referencia, una conversión boxing, una conversión de conversión unboxing, una conversión de ajuste o una conversión de desencapsulado.
La operación se evalúa de la siguiente manera:
- Si
E
es una función anónima o un grupo de métodos, se produce un error en tiempo de compilación. - Si
E
es elnull
literal o si el valor deE
esnull
, el resultado esfalse
. - De lo contrario:
- Deje que
R
sea el tipo de tiempo de ejecución deE
. - Vamos
D
a derivar deR
lo siguiente: - Si
R
es un tipo de valor que acepta valores NULL,D
es el tipo subyacente deR
. - En caso contrario,
D
esR
. - El resultado depende
D
de yT
de la manera siguiente: - Si
T
es un tipo de referencia, el resultado estrue
si:- Existe una conversión de identidad entre
D
yT
, D
es un tipo de referencia y una conversión de referencia implícita deD
aT
existe o- Cualquiera de las dos:
D
es un tipo de valor y una conversión boxing deD
aT
existe.
O bien:D
es un tipo de valor yT
es un tipo de interfaz implementado porD
.
- Existe una conversión de identidad entre
- Si
T
es un tipo de valor que acepta valores NULL, el resultado estrue
siD
es el tipo subyacente deT
. - Si
T
es un tipo de valor que no acepta valores NULL, el resultado estrue
siD
yT
son del mismo tipo. - De lo contrario, el resultado es
false
.
El operador no considera las conversiones definidas por el is
usuario.
Nota: A medida que el
is
operador se evalúa en tiempo de ejecución, se han sustituido todos los argumentos de tipo y no hay tipos abiertos (§8.4.3) que se deben tener en cuenta. nota final
Nota: El
is
operador se puede entender en términos de tipos y conversiones en tiempo de compilación como se indica a continuación, dondeC
es el tipo en tiempo de compilación deE
:
- Si el tipo en tiempo de compilación de es el mismo
T
quee
, o si una conversión de referencia implícita (§10.2.8), la conversión boxing (§10.2.9), la conversión de ajuste (§10.6) o una conversión explícita de desencapsulado (§10.6) existe desde el tipo en tiempo de compilación deE
aT
:
- Si
C
es de un tipo de valor que no acepta valores NULL, el resultado de la operación estrue
.- De lo contrario, el resultado de la operación es equivalente a evaluar
E != null
.- De lo contrario, si existe una conversión de referencia explícita (§10.3.5) o una conversión de unboxing (§10.3.7) desde
C
aT
o siC
oT
es un tipo abierto (§8.4.3), se realizarán comprobaciones en tiempo de ejecución como anteriores.- De lo contrario, no es posible realizar ninguna referencia, conversión boxing, encapsulado o desencapsulado de
E
al tipoT
y el resultado de la operación esfalse
. Un compilador puede implementar optimizaciones basadas en el tipo de tiempo de compilación.nota final
12.12.12.2 El operador is-pattern
El operador is-pattern se usa para comprobar si el valor calculado por una expresión coincide con un patrón determinado (§11). La comprobación se realiza en tiempo de ejecución. El resultado del operador is-pattern es true si el valor coincide con el patrón; de lo contrario, es false.
Para una expresión del formulario E is P
, donde E
es una expresión relacional de tipo T
y P
es un patrón, es un error en tiempo de compilación si alguno de los siguientes elementos contiene:
E
no designa un valor o no tiene un tipo.- El patrón
P
no es aplicable (§11.2) al tipoT
.
12.12.13 Operador as
El as
operador se usa para convertir explícitamente un valor en un tipo de referencia determinado o un tipo de valor que acepta valores NULL. A diferencia de una expresión de conversión (§12.9.7), el as
operador nunca produce una excepción. En su lugar, si la conversión indicada no es posible, el valor resultante es null
.
En una operación del formulario E as T
, E
debe ser una expresión y T
debe ser un tipo de referencia, un parámetro de tipo conocido como un tipo de referencia o un tipo de valor que acepta valores NULL. Además, al menos uno de los siguientes será true o, de lo contrario, se produce un error en tiempo de compilación:
- Existe una conversión de identidad (§10.2.2), nullable implícita (§10.2.6), referencia implícita (§10.2.8), conversión boxing (§10.2.9), nullable explícita (§10.3.4), referencia explícita (§10.3.5) o ajuste (§8.3.12) de
E
aT
. - El tipo de
E
oT
es un tipo abierto. E
es elnull
literal.
Si el tipo en tiempo de compilación de E
no dynamic
es , la operación E as T
genera el mismo resultado que
E is T ? (T)(E) : (T)null
salvo que E
solo se evalúa una vez. Se puede esperar que el compilador optimice E as T
para realizar como máximo una comprobación de tipos en tiempo de ejecución en lugar de las dos comprobaciones de tipos en tiempo de ejecución implícitas por la expansión anterior.
Si el tipo de tiempo de compilación de E
es , a diferencia del operador de conversión, el as
operador de conversión no está enlazado dinámicamente (§12.3.3dynamic
). Por lo tanto, la expansión en este caso es:
E is T ? (T)(object)(E) : (T)null
Tenga en cuenta que algunas conversiones, como las conversiones definidas por el usuario, no son posibles con el as
operador y, en su lugar, deben realizarse mediante expresiones de conversión.
Ejemplo: en el ejemplo
class X { public string F(object o) { return o as string; // OK, string is a reference type } public T G<T>(object o) where T : Attribute { return o as T; // Ok, T has a class constraint } public U H<U>(object o) { return o as U; // Error, U is unconstrained } }
Se sabe que el parámetro
T
de tipo de es un tipo deG
referencia, ya que tiene la restricción de clase . El parámetroU
type deH
no es sin embargo; por lo tanto, no se permite el uso delas
operador enH
.ejemplo final
12.13 Operadores lógicos
12.13.1 General
Los &,
^
operadores , y |
se denominan operadores lógicos.
and_expression
: equality_expression
| and_expression '&' equality_expression
;
exclusive_or_expression
: and_expression
| exclusive_or_expression '^' and_expression
;
inclusive_or_expression
: exclusive_or_expression
| inclusive_or_expression '|' exclusive_or_expression
;
Si un operando de un operador lógico tiene el tipo dynamic
en tiempo de compilación , la expresión se enlaza dinámicamente (§12.3.3). En este caso, el tipo en tiempo de compilación de la expresión es dynamic
y la resolución descrita a continuación tendrá lugar en tiempo de ejecución mediante el tipo en tiempo de ejecución de esos operandos que tienen el tipo dynamic
en tiempo de compilación .
Para una operación del formulario x «op» y
, donde «op» es uno de los operadores lógicos, se aplica la resolución de sobrecarga (§12.4.5) para seleccionar una implementación de operador específica. Los operandos se convierten en los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de valor devuelto del operador.
Los operadores lógicos predefinidos se describen en las subclases siguientes.
12.13.2 Operadores lógicos enteros
Los operadores lógicos enteros predefinidos son:
int operator &(int x, int y);
uint operator &(uint x, uint y);
long operator &(long x, long y);
ulong operator &(ulong x, ulong y);
int operator |(int x, int y);
uint operator |(uint x, uint y);
long operator |(long x, long y);
ulong operator |(ulong x, ulong y);
int operator ^(int x, int y);
uint operator ^(uint x, uint y);
long operator ^(long x, long y);
ulong operator ^(ulong x, ulong y);
El &
operador calcula el AND lógico bit a bit de los dos operandos, el |
operador calcula el OR lógico bit a bit de los dos operandos, y el ^
operador calcula el or exclusivo lógico bit a bit de los dos operandos. No se pueden desbordar estas operaciones.
Las formas levantadas (§12.4.8) de los operadores lógicos enteros predefinidos sin levantar definidos anteriormente también están predefinidos.
12.13.3 Operadores lógicos de enumeración
Cada tipo E
de enumeración proporciona implícitamente los siguientes operadores lógicos predefinidos:
E operator &(E x, E y);
E operator |(E x, E y);
E operator ^(E x, E y);
El resultado de evaluar x «op» y
, donde x
y y
son expresiones de un tipo E
de enumeración con un tipo U
subyacente , y «op» es uno de los operadores lógicos, es exactamente igual que evaluar (E)((U)x «op» (U)y)
. En otras palabras, los operadores lógicos de tipo de enumeración simplemente realizan la operación lógica en el tipo subyacente de los dos operandos.
Las formas levantadas (§12.4.8) de los operadores lógicos de enumeración predefinidos no eliminados definidos anteriormente también están predefinidos.
12.13.4 Operadores lógicos booleanos
Los operadores lógicos booleanos predefinidos son:
bool operator &(bool x, bool y);
bool operator |(bool x, bool y);
bool operator ^(bool x, bool y);
El resultado de x & y
es true
si tanto x
como y
son true
. De lo contrario, el resultado es false
.
El resultado de x | y
es true
si o x
y
es true
. De lo contrario, el resultado es false
.
El resultado de x ^ y
es true
si x
es y y
es false
true
, o x
es false
y y
es true
. De lo contrario, el resultado es false
. Cuando los operandos son de tipo bool
, el ^
operador calcula el mismo resultado que el !=
operador .
12.13.5 Boolean y | Operadores
El tipo bool?
booleano que acepta valores NULL puede representar tres valores, true
, false
y null
.
Al igual que con los otros operadores binarios, las formas levantadas de los operadores &
lógicos y |
(§12.13.4) también se definen previamente:
bool? operator &(bool? x, bool? y);
bool? operator |(bool? x, bool? y);
La semántica de los operadores elevados &
y |
se define en la tabla siguiente:
x |
y |
x & y |
x \| y |
---|---|---|---|
true |
true |
true |
true |
true |
false |
false |
true |
true |
null |
null |
true |
false |
true |
false |
true |
false |
false |
false |
false |
false |
null |
false |
null |
null |
true |
null |
true |
null |
false |
false |
null |
null |
null |
null |
null |
Nota: El
bool?
tipo es conceptualmente similar al tipo de tres valores que se usa para expresiones booleanas en SQL. La tabla anterior sigue la misma semántica que SQL, mientras que aplicar las reglas de §12.4.8 a los&
operadores y|
no. Las reglas de §12.4.8 ya proporcionan semántica similar a SQL para el operador elevado^
. nota final
12.14 Operadores lógicos condicionales
12.14.1 General
Los operadores &&
y ||
se denominan operadores lógicos condicionales. También se denominan operadores lógicos de cortocircuito.
conditional_and_expression
: inclusive_or_expression
| conditional_and_expression '&&' inclusive_or_expression
;
conditional_or_expression
: conditional_and_expression
| conditional_or_expression '||' conditional_and_expression
;
Los &&
operadores y ||
son versiones condicionales de los &
operadores y |
:
- La operación
x && y
corresponde a la operaciónx & y
, excepto quey
solo se evalúa six
nofalse
es . - La operación
x || y
corresponde a la operaciónx | y
, excepto quey
solo se evalúa six
notrue
es .
Nota: La razón por la que el cortocircuito usa las condiciones "not true" y "not false" es permitir que los operadores condicionales definidos por el usuario definan cuándo se aplica el cortocircuito. Los tipos definidos por el usuario podrían estar en un estado donde
operator true
devuelvefalse
yoperator false
devuelvefalse
. En esos casos, ni&&
tampoco||
cortocircuito. nota final
Si un operando de un operador lógico condicional tiene el tipo dynamic
en tiempo de compilación , la expresión se enlaza dinámicamente (§12.3.3). En este caso, el tipo en tiempo de compilación de la expresión es dynamic
y la resolución descrita a continuación tendrá lugar en tiempo de ejecución mediante el tipo en tiempo de ejecución de esos operandos que tienen el tipo dynamic
en tiempo de compilación .
Una operación del formulario x && y
o x || y
se procesa aplicando la resolución de sobrecarga (§12.4.5) como si la operación se escribiera x & y
o x | y
. A continuación,
- Si la resolución de sobrecarga no encuentra un único operador mejor o si la resolución de sobrecarga selecciona uno de los operadores lógicos enteros predefinidos o operadores lógicos booleanos que aceptan valores NULL (§12.13.5), se produce un error en tiempo de enlace.
- De lo contrario, si el operador seleccionado es uno de los operadores lógicos booleanos predefinidos (§12.13.4), la operación se procesa como se describe en §12.14.2.
- De lo contrario, el operador seleccionado es un operador definido por el usuario y la operación se procesa como se describe en §12.14.3.
No es posible sobrecargar directamente los operadores lógicos condicionales. Sin embargo, dado que los operadores lógicos condicionales se evalúan en términos de los operadores lógicos normales, las sobrecargas de los operadores lógicos normales son, con ciertas restricciones, también consideradas sobrecargas de los operadores lógicos condicionales. Esto se describe más adelante en §12.14.3.
12.14.2 Operadores lógicos condicionales booleanos
Cuando los operandos de &&
o ||
son de tipo bool
, o cuando los operandos son de tipos que no definen un objeto aplicable operator &
o operator |
, pero definen conversiones implícitas en bool
, la operación se procesa de la siguiente manera:
- La operación
x && y
se evalúa comox ? y : false
. En otras palabras,x
primero se evalúa y se convierte en el tipobool
. A continuación, six
estrue
,y
se evalúa y se convierte en el tipobool
y se convierte en el resultado de la operación. De lo contrario, el resultado de la operación esfalse
. - La operación
x || y
se evalúa comox ? true : y
. En otras palabras,x
primero se evalúa y se convierte en el tipobool
. A continuación, six
estrue
, el resultado de la operación estrue
. De lo contrario,y
se evalúa y se convierte en el tipobool
y se convierte en el resultado de la operación.
12.14.3 Operadores lógicos condicionales definidos por el usuario
Cuando los operandos de &&
o son de tipos que declaran un usuario definido por operator &
el usuario aplicable o operator |
, ambos de los siguientes serán true, donde T
es el tipo en el que se declara el operador ||
seleccionado:
- El tipo de valor devuelto y el tipo de cada parámetro del operador seleccionado serán
T
. Es decir, el operador calculará el AND lógico o el OR lógico de dos operandos de tipoT
y devolverá un resultado de tipoT
. T
contendrá declaraciones deoperator true
yoperator false
.
Se produce un error en tiempo de enlace si no se cumple alguno de estos requisitos. De lo contrario, la &&
operación o ||
se evalúa combinando el operador definido operator true
por el usuario o operator false
con el operador definido por el usuario seleccionado:
- La operación
x && y
se evalúa comoT.false(x) ? x : T.&(x, y)
, dondeT.false(x)
es una invocación deloperator false
declarado enT
yT.&(x, y)
es una invocación del objeto seleccionadooperator &
. En otras palabras,x
se evalúa primero yoperator false
se invoca en el resultado para determinar six
es definitivamente false. A continuación, six
definitivamente es false, el resultado de la operación es el valor calculado previamente parax
. De lo contrario,y
se evalúa y se invoca al seleccionadooperator &
en el valor calculado previamente parax
y el valor calculado para generary
el resultado de la operación. - La operación
x || y
se evalúa comoT.true(x) ? x : T.|(x, y)
, dondeT.true(x)
es una invocación deloperator true
declarado enT
yT.|(x, y)
es una invocación del objeto seleccionadooperator |
. En otras palabras,x
primero se evalúa yoperator true
se invoca en el resultado para determinar six
es definitivamente true. A continuación, six
definitivamente es true, el resultado de la operación es el valor calculado previamente parax
. De lo contrario,y
se evalúa y se invoca al seleccionadooperator |
en el valor calculado previamente parax
y el valor calculado para generary
el resultado de la operación.
En cualquiera de estas operaciones, la expresión dada por x
solo se evalúa una vez y la expresión dada por y
no se evalúa o evalúa exactamente una vez.
12.15 Operador de fusión null
El ??
operador se denomina operador de fusión null.
null_coalescing_expression
: conditional_or_expression
| conditional_or_expression '??' null_coalescing_expression
| throw_expression
;
En una expresión de fusión nula del formato a ?? b
, si a
nonull
es , el resultado es a
; de lo contrario, el resultado es b
. La operación solo se evalúa b
si a
es null
.
El operador de fusión null es asociativo a la derecha, lo que significa que las operaciones se agrupan de derecha a izquierda.
Ejemplo: Una expresión del formulario
a ?? b ?? c
se evalúa como .?? (b ?? c)
En términos generales, una expresión del formularioE1 ?? E2 ?? ... ?? EN
devuelve el primero de los operandos que nonull
son , onull
si 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 (siempre que a
tenga un tipo), B
es el tipo de b
(siempre que b
tenga un tipo) y A₀
es el tipo subyacente de A
si A
es un tipo de a
valor que acepta valores NULL o A
de otro modo. En concreto, a ?? b
se procesa de la siguiente manera:
- Si
A
existe y no es un tipo de valor que acepta valores NULL o un tipo de referencia, se produce un error en tiempo de compilación. - De lo contrario, si
A
existe yb
es una expresión dinámica, el tipo de resultado esdynamic
. En tiempo de ejecución,a
se evalúa primero. Sia
nonull
es ,a
se convierte endynamic
y se convierte en el resultado. De lo contrario,b
se evalúa y se convierte en el resultado. - De lo contrario, si
A
existe y es un tipo de valor que acepta valores NULL y existe una conversión implícita deb
aA₀
, el tipo de resultado esA₀
. En tiempo de ejecución,a
se evalúa primero. Sia
nonull
es ,a
se desencapsula en el tipoA₀
y se convierte en el resultado. De lo contrario,b
se evalúa y se convierte en el tipoA₀
y se convierte en el resultado. - De lo contrario, si
A
existe y existe una conversión implícita deb
aA
, el tipo de resultado esA
. En tiempo de ejecución, se evalúa por primera vez. Si un objeto no es NULL, se convierte en el resultado. De lo contrario,b
se evalúa y se convierte en el tipoA
y se convierte en el resultado. - De lo contrario, si
A
existe y es un tipo de valor que acepta valores NULL,b
tiene un tipoB
y existe una conversión implícita deA₀
aB
, el tipo de resultado esB
. En tiempo de ejecución,a
se evalúa primero. Sia
no es ,a
se desencapsula al tipoA₀
y se convierte en el tipoB
y se conviertenull
en el resultado. De lo contrario,b
se evalúa y se convierte en el resultado. - De lo contrario, si
b
tiene un tipoB
y existe una conversión implícita dea
aB
, el tipo de resultado esB
. En tiempo de ejecución,a
se evalúa primero. Sia
no es ,a
se convierte en el tipoB
y se conviertenull
en el resultado. De lo contrario,b
se evalúa y se convierte en el resultado.
De lo contrario, a
y b
son incompatibles y a
se produce un error en tiempo de compilación.
12.16 Operador de expresión throw
throw_expression
: 'throw' null_coalescing_expression
;
Un throw_expression produce el valor generado mediante la evaluación del null_coalescing_expression. La expresión se convertirá implícitamente en System.Exception
y el resultado de evaluar la expresión se convierte en System.Exception
antes de iniciarse. El comportamiento en tiempo de ejecución de la evaluación de una expresión throw es el mismo que se especifica para una instrucción throw (§13.10.6).
Un throw_expression no tiene ningún tipo. Un throw_expression se puede convertir en cada tipo mediante una conversión de inicio implícita.
Una expresión throw solo se producirá en los siguientes contextos sintácticos:
- Como segundo o tercer operando de un operador condicional ternario (
?:
). - Como segundo operando de un operador de fusión null (
??
). - Como cuerpo de una expresión lambda o miembro.
12.17 Expresiones de declaración
Una expresión de declaración declara una variable local.
declaration_expression
: local_variable_type identifier
;
local_variable_type
: type
| 'var'
;
El simple_name _
también se considera una expresión de declaración si la búsqueda de nombres simple no encontró una declaración asociada (§12.8.4). Cuando se usa como expresión de declaración, _
se denomina descarte simple. Es semánticamente equivalente a var _
, pero se permite en más lugares.
Una expresión de declaración solo se producirá en los siguientes contextos sintácticos:
- Como argument_value
out
en un argument_list. - Como descarte
_
simple que comprende el lado izquierdo de una asignación simple (§12.21.2). - Como tuple_element en uno o varios tuple_expressionanidados recursivamente, el exterior más externo del cual consta del lado izquierdo de una asignación deconstrucción. Un deconstruction_expression da lugar a expresiones de declaración en esta posición, aunque las expresiones de declaración no estén presentes sintácticamente.
Nota: Esto significa que una expresión de declaración no se puede paréntesis. nota final
Se trata de un error para una variable con tipo implícito declarada con un declaration_expression al que se hace referencia dentro de la argument_list donde se declara.
Se trata de un error para una variable declarada con una declaration_expression a la que se hace referencia dentro de la asignación deconstrucción donde se produce.
Expresión de declaración que es un descarte simple o donde el local_variable_type es el identificador var
se clasifica como una variable con tipo implícito. La expresión no tiene ningún tipo y el tipo de la variable local se deduce en función del contexto sintáctico como se indica a continuación:
- En un argument_list el tipo inferido de la variable es el tipo declarado del parámetro correspondiente.
- Como lado izquierdo de una asignación simple, el tipo inferido de la variable es el tipo del lado derecho de la asignación.
- En un tuple_expression del lado izquierdo de una asignación simple, el tipo inferido de la variable es el tipo del elemento de tupla correspondiente en el lado derecho (después de la deconstrucción) de la asignación.
De lo contrario, la expresión de declaración se clasifica como una variable con tipo explícito y el tipo de la expresión, así como la variable declarada, será dada por el local_variable_type.
Una expresión de declaración con el identificador _
es un descarte (§9.2.9.1) y no introduce un nombre para la variable. Una expresión de declaración con un identificador _
distinto de introduce ese nombre en el espacio de declaración de variable local más cercano (§7.3).
Ejemplo:
string M(out int i, string s, out bool b) { ... } var s1 = M(out int i1, "One", out var b1); Console.WriteLine($"{i1}, {b1}, {s1}"); // Error: i2 referenced within declaring argument list var s2 = M(out var i2, M(out i2, "Two", out bool b2), out b2); var s3 = M(out int _, "Three", out var _);
La declaración de muestra expresiones de
s1
declaración explícita e implícitamente tipadas. El tipo deducido deb1
esbool
porque es el tipo del parámetro de salida correspondiente enM1
. El siguienteWriteLine
es capaz de accederi1
a yb1
, que se han introducido en el ámbito envolvente.La declaración de muestra un intento de usar
i2
en la llamada anidada aM
, que no se permite, porque la referencia se produce dentro des2
la lista de argumentos dondei2
se declaró. Por otro lado, se permite la referencia ab2
en el argumento final, porque se produce después del final de la lista de argumentos anidados dondeb2
se declaró.La declaración de
s3
muestra el uso de expresiones de declaración implícitas y explícitamente tipadas que se descartan. Dado que los descartes no declaran una variable con nombre, se permiten varias apariciones del identificador_
.(int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5);
En este ejemplo se muestra el uso de expresiones de declaración implícita y explícitamente tipadas para variables y descartes en una asignación de deconstrucción. El simple_name
_
es equivalente avar _
cuando no se encuentra ninguna declaración de_
.void M1(out int i) { ... } void M2(string _) { M1(out _); // Error: `_` is a string M1(out var _); }
En este ejemplo se muestra el uso de
var _
para proporcionar un descarte con tipo implícito cuando_
no está disponible, ya que designa una variable en el ámbito envolvente.ejemplo final
Operador condicional 12.18
El ?:
operador se denomina operador condicional. También se denomina operador ternario.
conditional_expression
: null_coalescing_expression
| null_coalescing_expression '?' expression ':' expression
| null_coalescing_expression '?' 'ref' variable_reference ':'
'ref' variable_reference
;
No se permite una expresión throw (§12.16) en un operador condicional si ref
está presente.
Una expresión condicional del formulario b ? x : y
evalúa primero la condición b
. A continuación, si b
es true
, x
se evalúa y se convierte en el resultado de la operación. De lo contrario, y
se evalúa y se convierte en el resultado de la operación. Una expresión condicional nunca evalúa y x
y
.
El operador condicional es asociativo a la derecha, lo que significa que las operaciones se agrupan de derecha a izquierda.
Ejemplo: una expresión del formulario
a ? b : c ? d : e
se evalúa comoa ? b : (c ? d : e)
. ejemplo final
El primer operando del ?:
operador será una expresión que se pueda convertir implícitamente en bool
, o una expresión de un tipo que implemente operator true
. Si no se cumple ninguno de estos requisitos, se produce un error en tiempo de compilación.
Si ref
está presente:
- Debe existir una conversión de identidad entre los tipos de los dos variable_references y el tipo del resultado puede ser cualquier tipo. Si cualquiera de los tipos es
dynamic
, la inferencia de tipos prefieredynamic
(§8.7). Si cualquiera de los tipos es un tipo de tupla (§8.3.11), la inferencia de tipos incluye los nombres de elemento cuando los nombres de elemento de la misma posición ordinal coinciden en ambas tuplas. - El resultado es una referencia variable, que se puede escribir si ambos variable_referencese pueden escribir.
Nota: Cuando
ref
está presente, el conditional_expression devuelve una referencia de variable, que se puede asignar a una variable de referencia mediante el= ref
operador o que se pasa como parámetro reference/input/output. nota final
Si ref
no está presente, los operandos segundo y tercero, x
y y
, del ?:
operador controlan el tipo de la expresión condicional:
- Si
x
tiene el tipoX
yy
tiene ese tipoY
,- Si existe una conversión de identidad entre
X
yY
, el resultado es el mejor tipo común de un conjunto de expresiones (§12.6.3.15). Si cualquiera de los tipos esdynamic
, la inferencia de tipos prefieredynamic
(§8.7). Si cualquiera de los tipos es un tipo de tupla (§8.3.11), la inferencia de tipos incluye los nombres de elemento cuando los nombres de elemento de la misma posición ordinal coinciden en ambas tuplas. - De lo contrario, si existe una conversión implícita (§10.2) de
X
aY
, pero no deY
aX
, entoncesY
es el tipo de la expresión condicional. - De lo contrario, si existe una conversión de enumeración implícita (§10.2.4) desde
X
aY
, entoncesY
es el tipo de la expresión condicional. - De lo contrario, si existe una conversión de enumeración implícita (§10.2.4) desde
Y
aX
, entoncesX
es el tipo de la expresión condicional. - De lo contrario, si existe una conversión implícita (§10.2) de
Y
aX
, pero no deX
aY
, entoncesX
es el tipo de la expresión condicional. - De lo contrario, no se puede determinar ningún tipo de expresión y se produce un error en tiempo de compilación.
- Si existe una conversión de identidad entre
- Si solo uno de
x
yy
tiene un tipo, y ambosx
yy
se convierten implícitamente en ese tipo, ese es el tipo de la expresión condicional. - De lo contrario, no se puede determinar ningún tipo de expresión y se produce un error en tiempo de compilación.
El procesamiento en tiempo de ejecución de una expresión condicional ref del formulario b ? ref x : ref y
consta de los pasos siguientes:
- En primer lugar,
b
se evalúa y elbool
valor deb
se determina:- Si existe una conversión implícita del tipo de
b
abool
, esta conversión implícita se realiza para generar unbool
valor. - De lo contrario, se invoca el
operator true
definido por el tipo deb
para generar unbool
valor.
- Si existe una conversión implícita del tipo de
- Si el
bool
valor generado por el paso anterior estrue
,x
se evalúa y la referencia de variable resultante se convierte en el resultado de la expresión condicional. - De lo contrario,
y
se evalúa y la referencia de variable resultante se convierte en el resultado de la expresión condicional.
El procesamiento en tiempo de ejecución de una expresión condicional del formulario b ? x : y
consta de los pasos siguientes:
- En primer lugar,
b
se evalúa y elbool
valor deb
se determina:- Si existe una conversión implícita del tipo de
b
abool
, esta conversión implícita se realiza para generar unbool
valor. - De lo contrario, se invoca el
operator true
definido por el tipo deb
para generar unbool
valor.
- Si existe una conversión implícita del tipo de
- Si el
bool
valor generado por el paso anterior estrue
,x
se evalúa y convierte al tipo de la expresión condicional y se convierte en el resultado de la expresión condicional. - De lo contrario,
y
se evalúa y se convierte en el tipo de la expresión condicional y se convierte en el resultado de la expresión condicional.
12.19 Expresiones de función anónimas
12.19.1 General
Una función anónima es una expresión que representa una definición de método "en línea". Una función anónima no tiene un valor o tipo en y de sí mismo, pero se puede convertir a un tipo de delegado o árbol de expresión compatible. La evaluación de una conversión de función anónima depende del tipo de destino de la conversión: si es un tipo delegado, la conversión se evalúa como un valor delegado que hace referencia al método que define la función anónima. Si es un tipo de árbol de expresión, la conversión se evalúa como un árbol de expresión que representa la estructura del método como una estructura de objeto.
Nota: Por razones históricas, hay dos tipos sintácticos de funciones anónimas, es decir , lambda_expressions y anonymous_method_expressions. Para casi todos los propósitos, lambda_expressions son más concisos y expresivos que anonymous_method_expressions, que permanecen en el lenguaje para la compatibilidad con versiones anteriores. nota final
lambda_expression
: 'async'? anonymous_function_signature '=>' anonymous_function_body
;
anonymous_method_expression
: 'async'? 'delegate' explicit_anonymous_function_signature? block
;
anonymous_function_signature
: explicit_anonymous_function_signature
| implicit_anonymous_function_signature
;
explicit_anonymous_function_signature
: '(' explicit_anonymous_function_parameter_list? ')'
;
explicit_anonymous_function_parameter_list
: explicit_anonymous_function_parameter
(',' explicit_anonymous_function_parameter)*
;
explicit_anonymous_function_parameter
: anonymous_function_parameter_modifier? type identifier
;
anonymous_function_parameter_modifier
: 'ref'
| 'out'
| 'in'
;
implicit_anonymous_function_signature
: '(' implicit_anonymous_function_parameter_list? ')'
| implicit_anonymous_function_parameter
;
implicit_anonymous_function_parameter_list
: implicit_anonymous_function_parameter
(',' implicit_anonymous_function_parameter)*
;
implicit_anonymous_function_parameter
: identifier
;
anonymous_function_body
: null_conditional_invocation_expression
| expression
| 'ref' variable_reference
| block
;
Al reconocer una anonymous_function_body si se aplican las alternativas de null_conditional_invocation_expression y expresión , se elegirá la primera.
Nota: La superposición de, y la prioridad entre, las alternativas aquí son únicamente para comodidad descriptiva; las reglas gramaticales se pueden elaborar para quitar la superposición. ANTLR y otros sistemas gramaticales adoptan la misma comodidad, por lo que anonymous_function_body tiene automáticamente la semántica especificada. nota final
Nota: Cuando se trata como una expresión, una forma sintáctica como
x?.M()
sería un error si el tipo de resultado deM
esvoid
(§12.8.13). Pero cuando se trata como un null_conditional_invocation_expression, se permite que el tipo de resultado seavoid
. nota final
Ejemplo: el tipo de resultado de
List<T>.Reverse
esvoid
. En el código siguiente, el cuerpo de la expresión anónima es un null_conditional_invocation_expression, por lo que no es un error.Action<List<int>> a = x => x?.Reverse();
ejemplo final
El =>
operador tiene la misma prioridad que la asignación (=
) y es asociativo a la derecha.
Una función anónima con el async
modificador es una función asincrónica y sigue las reglas descritas en §15.15.
Los parámetros de una función anónima en forma de lambda_expression se pueden escribir explícita o implícitamente. En una lista de parámetros con tipo explícito, el tipo de cada parámetro se indica explícitamente. En una lista de parámetros con tipo implícito, los tipos de los parámetros se deducen del contexto en el que se produce la función anónima, en concreto, cuando la función anónima se convierte en un tipo de delegado compatible o tipo de árbol de expresión, ese tipo proporciona los tipos de parámetro (§10.7).
En un lambda_expression con un único parámetro con tipo implícito, los paréntesis se pueden omitir de la lista de parámetros. En otras palabras, una función anónima del formulario
( «param» ) => «expr»
se puede abreviar a
«param» => «expr»
La lista de parámetros de una función anónima en forma de anonymous_method_expression es opcional. Si se especifica, los parámetros se escribirán explícitamente. Si no es así, la función anónima se puede convertir a un delegado con cualquier lista de parámetros que no contenga parámetros de salida.
Un cuerpo de bloque de una función anónima siempre es accesible (§13.2).
Ejemplo: a continuación se muestran algunos ejemplos de funciones anónimas:
x => x + 1 // Implicitly typed, expression body x => { return x + 1; } // Implicitly typed, block body (int x) => x + 1 // Explicitly typed, expression body (int x) => { return x + 1; } // Explicitly typed, block body (x, y) => x * y // Multiple parameters () => Console.WriteLine() // No parameters async (t1,t2) => await t1 + await t2 // Async delegate (int x) { return x + 1; } // Anonymous method expression delegate { return 1 + 1; } // Parameter list omitted
ejemplo final
El comportamiento de lambda_expressions y anonymous_method_expressions es el mismo, excepto en los puntos siguientes:
- anonymous_method_expression permite que la lista de parámetros se omita por completo, lo que produce la convertibilidad en tipos delegados de cualquier lista de parámetros de valor.
- lambda_expression permite que los tipos de parámetro se omitan e infieren, mientras que anonymous_method_expressions requieren que los tipos de parámetro se indiquen explícitamente.
- El cuerpo de un lambda_expression puede ser una expresión o un bloque, mientras que el cuerpo de un anonymous_method_expression será un bloque.
- Solo lambda_expressiontienen conversiones a tipos de árbol de expresión compatibles (§8.6).
12.19.2 Firmas de función anónimas
El anonymous_function_signature de una función anónima define los nombres y, opcionalmente, los tipos de los parámetros para la función anónima. El ámbito de los parámetros de la función anónima es el anonymous_function_body (§7.7). Junto con la lista de parámetros (si se indica), el cuerpo del método anónimo constituye un espacio de declaración (§7.3). Por lo tanto, es un error en tiempo de compilación para que el nombre de un parámetro de la función anónima coincida con el nombre de una variable local, una constante local o un parámetro cuyo ámbito incluya el anonymous_method_expression o lambda_expression.
Si una función anónima tiene un explicit_anonymous_function_signature, el conjunto de tipos de delegado y tipos de árbol de expresión compatibles está restringido a los que tienen los mismos tipos de parámetros y modificadores en el mismo orden (§10.7). A diferencia de las conversiones de grupo de métodos (§10.8), no se admite la varianza contra de los tipos de parámetros de función anónimos. Si una función anónima no tiene un anonymous_function_signature, el conjunto de tipos de delegado y tipos de árbol de expresión compatibles está restringido a los que no tienen parámetros de salida.
Tenga en cuenta que un anonymous_function_signature no puede incluir atributos ni una matriz de parámetros. Sin embargo, un anonymous_function_signature puede ser compatible con un tipo de delegado cuya lista de parámetros contiene una matriz de parámetros.
Tenga en cuenta también que la conversión a un tipo de árbol de expresión, incluso si es compatible, puede producirse un error en tiempo de compilación (§8.6).
12.19.3 Cuerpos de función anónimos
El cuerpo (expresión o bloque) de una función anónima está sujeto a las siguientes reglas:
- Si la función anónima incluye una firma, los parámetros especificados en la firma están disponibles en el cuerpo. Si la función anónima no tiene ninguna firma, se puede convertir en un tipo delegado o un tipo de expresión que tenga parámetros (§10.7), pero no se puede tener acceso a los parámetros en el cuerpo.
- Excepto los parámetros por referencia especificados en la firma (si existe) de la función anónima más cercana, es un error en tiempo de compilación para que el cuerpo acceda a un parámetro by-reference.
- Excepto en el caso de los parámetros especificados en la firma (si existe) de la función anónima más cercana, se trata de un error en tiempo de compilación para que el cuerpo acceda a un parámetro de un
ref struct
tipo. - Cuando el tipo de
this
es un tipo de estructura, es un error en tiempo de compilación para que el cuerpo accedathis
a . Esto es cierto si el acceso es explícito (como enthis.x
) o implícito (como en dondex
x
es un miembro de instancia de la estructura). Esta regla simplemente prohíbe este acceso y no afecta a si la búsqueda de miembros da como resultado un miembro de la estructura. - El cuerpo tiene acceso a las variables externas (§12.19.6) de la función anónima. El acceso de una variable externa hará referencia a la instancia de la variable que está activa en el momento en que se evalúa el lambda_expression o anonymous_method_expression (§12.19.7).
- Es un error en tiempo de compilación para que el cuerpo contenga una
goto
instrucción, unabreak
instrucción o unacontinue
instrucción cuyo destino está fuera del cuerpo o dentro del cuerpo de una función anónima independiente. - Una
return
instrucción del cuerpo devuelve el control de una invocación de la función anónima más cercana, no del miembro de función envolvente.
No se especifica explícitamente si hay alguna manera de ejecutar el bloque de una función anónima que no sea a través de la evaluación e invocación de la lambda_expression o anonymous_method_expression. En concreto, el compilador puede optar por implementar una función anónima mediante la síntesis de uno o varios métodos o tipos con nombre. Los nombres de cualquier elemento sintetizado de este tipo serán de un formulario reservado para el uso del compilador (§6.4.3).
12.19.4 Resolución de sobrecarga
Las funciones anónimas de una lista de argumentos participan en la inferencia de tipos y la resolución de sobrecargas. Consulte §12.6.3 y §12.6.4 para conocer las reglas exactas.
Ejemplo: en el ejemplo siguiente se muestra el efecto de las funciones anónimas en la resolución de sobrecargas.
class ItemList<T> : List<T> { public int Sum(Func<T, int> selector) { int sum = 0; foreach (T item in this) { sum += selector(item); } return sum; } public double Sum(Func<T, double> selector) { double sum = 0; foreach (T item in this) { sum += selector(item); } return sum; } }
La
ItemList<T>
clase tiene dosSum
métodos. Cada toma unselector
argumento, que extrae el valor para sumar de un elemento de lista. El valor extraído puede ser oint
ydouble
la suma resultante es del mismo modo oint
.double
Los
Sum
métodos pueden usarse, por ejemplo, para calcular sumas de una lista de líneas de detalle en un orden.class Detail { public int UnitCount; public double UnitPrice; ... } class A { void ComputeSums() { ItemList<Detail> orderDetails = GetOrderDetails( ... ); int totalUnits = orderDetails.Sum(d => d.UnitCount); double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount); ... } ItemList<Detail> GetOrderDetails( ... ) { ... } }
En la primera invocación de
orderDetails.Sum
, ambosSum
métodos son aplicables porque la funciónd => d.UnitCount
anónima es compatible con yFunc<Detail,int>
Func<Detail,double>
. Sin embargo, la resolución de sobrecarga elige el primerSum
método porque la conversión aFunc<Detail,int>
es mejor que la conversión aFunc<Detail,double>
.En la segunda invocación de
orderDetails.Sum
, solo se aplica el segundoSum
método porque la funciónd => d.UnitPrice * d.UnitCount
anónima genera un valor de tipodouble
. Por lo tanto, la resolución de sobrecarga elige el segundoSum
método para esa invocación.ejemplo final
12.19.5 Funciones anónimas y enlace dinámico
Una función anónima no puede ser un receptor, argumento o operando de una operación enlazada dinámicamente.
12.19.6 Variables externas
12.19.6.1 General
Cualquier variable local, parámetro de valor o matriz de parámetros cuyo ámbito incluya el lambda_expression o anonymous_method_expression se denomina variable externa de la función anónima. En un miembro de función de instancia de una clase, este valor se considera un parámetro value y es una variable externa de cualquier función anónima contenida en el miembro de función.
12.19.6.2 Variables externas capturadas
Cuando una función anónima hace referencia a una variable externa, se dice que la función anónima capturó la variable externa. Normalmente, la duración de una variable local se limita a la ejecución del bloque o instrucción con la que está asociado (§9.2.9). Sin embargo, la duración de una variable externa capturada se extiende al menos hasta que el árbol delegado o de expresión creado a partir de la función anónima es apto para la recolección de elementos no utilizados.
Ejemplo: en el ejemplo
delegate int D(); class Test { static D F() { int x = 0; D result = () => ++x; return result; } static void Main() { D d = F(); Console.WriteLine(d()); Console.WriteLine(d()); Console.WriteLine(d()); } }
La función anónima captura la variable
x
local y la duración dex
se extiende al menos hasta que el delegado devuelto deF
se convierte en apto para la recolección de elementos no utilizados. Dado que cada invocación de la función anónima funciona en la misma instancia dex
, la salida del ejemplo es:1 2 3
ejemplo final
Cuando una función anónima captura una variable local o un parámetro de valor, la variable local o el parámetro ya no se considera una variable fija (§23.4), pero en su lugar se considera una variable desplazable. Sin embargo, las variables externas capturadas no se pueden usar en una fixed
instrucción (§23.7), por lo que no se puede tomar la dirección de una variable externa capturada.
Nota: A diferencia de una variable no capturada, una variable local capturada se puede exponer simultáneamente a varios subprocesos de ejecución. nota final
12.19.6.3 Creación de instancias de variables locales
Se considera que se crea una instancia de una variable local cuando la ejecución entra en el ámbito de la variable.
Ejemplo: por ejemplo, cuando se invoca el método siguiente, se crea una instancia de la variable
x
local e se inicializa tres veces, una vez para cada iteración del bucle.static void F() { for (int i = 0; i < 3; i++) { int x = i * 2 + 1; ... } }
Sin embargo, mover la declaración de
x
fuera del bucle da como resultado una única creación de instancias 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 creación de instancias se vuelven evidentes.
Ejemplo: El ejemplo
delegate void D(); class Test { static D[] F() { D[] result = new D[3]; for (int i = 0; i < 3; i++) { int x = i * 2 + 1; result[i] = () => Console.WriteLine(x); } return result; } static void Main() { foreach (D d in F()) { d(); } } }
genera el resultado:
1 3 5
Sin embargo, cuando la declaración de
x
se mueve fuera del bucle:delegate void D(); class Test { static D[] F() { D[] result = new D[3]; int x; for (int i = 0; i < 3; i++) { x = i * 2 + 1; result[i] = () => Console.WriteLine(x); } return result; } static void Main() { foreach (D d in F()) { d(); } } }
la salida es:
5 5 5
Tenga en cuenta que el compilador está permitido (pero no necesario) para optimizar las tres instancias en una sola instancia de delegado (§10.7.2).
ejemplo final
Si un bucle for declara una variable de iteración, esa variable se considera declarada fuera del bucle.
Ejemplo: Por lo tanto, si se cambia el ejemplo para capturar la propia variable de iteración:
delegate void D(); class Test { static D[] F() { D[] result = new D[3]; for (int i = 0; i < 3; i++) { result[i] = () => Console.WriteLine(i); } return result; } static void Main() { foreach (D d in F()) { d(); } } }
solo se captura una instancia de la variable de iteración, que genera la salida:
3 3 3
ejemplo final
Es posible que los delegados de función anónimos compartan algunas variables capturadas pero tienen instancias independientes de otras.
Ejemplo: por ejemplo, si
F
se cambia astatic D[] F() { D[] result = new D[3]; int x = 0; for (int i = 0; i < 3; i++) { int y = 0; result[i] = () => Console.WriteLine($"{++x} {++y}"); } return result; }
los tres delegados capturan la misma instancia de
x
pero separan instancias dey
y la salida es:1 1 2 1 3 1
ejemplo final
Las funciones anónimas independientes pueden capturar la misma instancia de una variable externa.
Ejemplo: en el ejemplo:
delegate void Setter(int value); delegate int Getter(); class Test { static void Main() { int x = 0; Setter s = (int value) => x = value; Getter g = () => x; s(5); Console.WriteLine(g()); s(10); Console.WriteLine(g()); } }
las dos funciones anónimas capturan la misma instancia de la variable
x
local y, por tanto, pueden "comunicarse" a través de esa variable. La salida del ejemplo es:5 10
ejemplo final
12.19.7 Evaluación de expresiones de función anónimas
Una función F
anónima siempre se convertirá en un tipo D
delegado o en un tipo E
de árbol de expresión, ya sea directamente o a través de la ejecución de una expresión new D(F)
de creación de delegados . Esta conversión determina el resultado de la función anónima, como se describe en §10.7.
Ejemplo de implementación 12.19.8
Esta subclausa es informativa.
Esta subclause describe una posible implementación de conversiones de funciones anónimas en términos de otras construcciones de C#. La implementación descrita aquí se basa en los mismos principios que usa un compilador comercial de C#, pero no es por ningún medio una implementación obligatoria, ni es la única posible. Solo menciona brevemente conversiones a árboles de expresión, ya que su semántica exacta está fuera del ámbito de esta especificación.
El resto de esta subclausa proporciona varios ejemplos de código que contiene funciones anónimas con características diferentes. Para cada ejemplo, se proporciona una traducción correspondiente al código que usa solo otras construcciones de C#. En los ejemplos, se supone que el identificador D
representa el siguiente tipo de delegado:
public delegate void D();
La forma más sencilla de una función anónima es una que no captura variables externas:
delegate void D();
class Test
{
static void F()
{
D d = () => Console.WriteLine("test");
}
}
Esto se puede traducir a una instancia de delegado que hace referencia a un método estático generado por el compilador en el que se coloca el código de la función anónima:
delegate void D();
class Test
{
static void F()
{
D d = new D(__Method1);
}
static void __Method1()
{
Console.WriteLine("test");
}
}
En el ejemplo siguiente, la función anónima hace referencia a los miembros de instancia de this
:
delegate void D();
class Test
{
int x;
void F()
{
D d = () => Console.WriteLine(x);
}
}
Esto se puede traducir a un método de instancia generado por el compilador que contiene el código de la función anónima:
delegate void D();
class Test
{
int x;
void F()
{
D d = new D(__Method1);
}
void __Method1()
{
Console.WriteLine(x);
}
}
En este ejemplo, la función anónima captura una variable local:
delegate void D();
class Test
{
void F()
{
int y = 123;
D d = () => Console.WriteLine(y);
}
}
La duración de la variable local debe extenderse ahora al menos a la duración del delegado de función anónima. Esto se puede lograr mediante la "elevación" de la variable local en un campo de una clase generada por el compilador. La creación de instancias de la variable local (§12.19.6.3) corresponde a la creación de una instancia de la clase generada por el compilador y el acceso a la variable local corresponde al acceso a un campo en la instancia de la clase generada por el compilador. Además, la función anónima se convierte en un método de instancia de la clase generada por el compilador:
delegate void D();
class Test
{
void F()
{
__Locals1 __locals1 = new __Locals1();
__locals1.y = 123;
D d = new D(__locals1.__Method1);
}
class __Locals1
{
public int y;
public void __Method1()
{
Console.WriteLine(y);
}
}
}
Por último, la siguiente función anónima captura this
, así como dos variables locales con distintas duraciones:
delegate void D();
class Test
{
int x;
void F()
{
int y = 123;
for (int i = 0; i < 10; i++)
{
int z = i * 2;
D d = () => Console.WriteLine(x + y + z);
}
}
}
En este caso, se crea una clase generada por el compilador para cada bloque en el que se capturan las variables locales, de modo que las variables locales de los distintos bloques pueden tener duraciones independientes. Una instancia de __Locals2
, la clase generada por el compilador para el bloque interno, contiene la variable z
local y un campo que hace referencia a una instancia de __Locals1
. Una instancia de __Locals1
, la clase generada por el compilador para el bloque externo, contiene la variable y
local y un campo que hace referencia this
al miembro de función envolvente. Con estas estructuras de datos, es posible llegar a todas las variables externas capturadas a través de una instancia de __Local2
y, por tanto, el código de la función anónima se puede implementar como un método de instancia de esa clase.
delegate void D();
class Test
{
int x;
void F()
{
__Locals1 __locals1 = new __Locals1();
__locals1.__this = this;
__locals1.y = 123;
for (int i = 0; i < 10; i++)
{
__Locals2 __locals2 = new __Locals2();
__locals2.__locals1 = __locals1;
__locals2.z = i * 2;
D d = new D(__locals2.__Method1);
}
}
class __Locals1
{
public Test __this;
public int y;
}
class __Locals2
{
public __Locals1 __locals1;
public int z;
public void __Method1()
{
Console.WriteLine(__locals1.__this.x + __locals1.y + z);
}
}
}
La misma técnica que se aplica aquí para capturar variables locales también se puede usar al convertir funciones anónimas en árboles de expresión: las referencias a los objetos generados por el compilador se pueden almacenar en el árbol de expresiones y el acceso a las variables locales se puede representar como acceso de campo en estos objetos. La ventaja de este enfoque es que permite compartir las variables locales "levantadas" entre delegados y árboles de expresión.
Fin del texto informativo.
12.20 Expresiones de consulta
12.20.1 General
Las expresiones de consulta proporcionan una sintaxis integrada en el lenguaje para las consultas que son similares a los lenguajes de consulta relacionales y jerárquicos , como SQL y XQuery.
query_expression
: from_clause query_body
;
from_clause
: 'from' type? identifier 'in' expression
;
query_body
: query_body_clauses? select_or_group_clause query_continuation?
;
query_body_clauses
: query_body_clause
| query_body_clauses query_body_clause
;
query_body_clause
: from_clause
| let_clause
| where_clause
| join_clause
| join_into_clause
| orderby_clause
;
let_clause
: 'let' identifier '=' expression
;
where_clause
: 'where' boolean_expression
;
join_clause
: 'join' type? identifier 'in' expression 'on' expression
'equals' expression
;
join_into_clause
: 'join' type? identifier 'in' expression 'on' expression
'equals' expression 'into' identifier
;
orderby_clause
: 'orderby' orderings
;
orderings
: ordering (',' ordering)*
;
ordering
: expression ordering_direction?
;
ordering_direction
: 'ascending'
| 'descending'
;
select_or_group_clause
: select_clause
| group_clause
;
select_clause
: 'select' expression
;
group_clause
: 'group' expression 'by' expression
;
query_continuation
: 'into' identifier query_body
;
Una expresión de consulta comienza con una from
cláusula y termina con una select
cláusula o group
. La cláusula inicial from
puede ir seguida de cero o más from
cláusulas , let
, where
o orderby
join
. Cada from
cláusula es un generador que presenta una variable de rango que abarca los elementos de una secuencia. Cada let
cláusula introduce una variable de rango que representa un valor calculado mediante variables de intervalo anteriores. Cada where
cláusula es un filtro que excluye los elementos del resultado. Cada join
cláusula compara las claves especificadas de la secuencia de origen con claves de otra secuencia, lo que produce pares coincidentes. Cada orderby
cláusula reordena los elementos según los criterios especificados. La cláusula final select
o group
especifica la forma del resultado en términos de las variables de intervalo. Por último, se puede usar una into
cláusula para "splice" consultas tratando los resultados de una consulta como generador en una consulta posterior.
12.20.2 Ambigüedades en expresiones de consulta
Las expresiones de consulta usan una serie de palabras clave contextuales (§6.4.4): ascending
, descending
from
equals
by
, group
into
join
let
, on
, orderby
y . select
where
Para evitar ambigüedades que podrían surgir del uso de estos identificadores como palabras clave y nombres simples, estos identificadores se consideran palabras clave en cualquier parte de una expresión de consulta, a menos que tengan el prefijo "@
" (§6.4.4) en cuyo caso se consideran identificadores. Para ello, una expresión de consulta es cualquier expresión que comience por "from
identificador" seguida de cualquier token excepto "", "=
;
" o ",
".
12.20.3 Traducción de expresiones de consulta
12.20.3.1 General
El lenguaje C# no especifica la semántica de ejecución de expresiones de consulta. En su lugar, las expresiones de consulta se traducen en invocaciones de métodos que cumplen el patrón query-expression (§12.20.4). En concreto, las expresiones de consulta se traducen en invocaciones de métodos denominados Where
, , ThenBy
GroupJoin
Join
SelectMany
OrderByDescending
Select
OrderBy
, ThenByDescending
, , GroupBy
y .Cast
Se espera que estos métodos tengan firmas y tipos de valor devuelto concretos, como se describe en §12.20.4. Estos métodos pueden ser métodos de instancia del objeto que se consultan o métodos de extensión que son externos al objeto . Estos métodos implementan la ejecución real de la consulta.
La traducción de expresiones de consulta a invocaciones de método es una asignación sintáctica que se produce antes de que se haya realizado cualquier enlace de tipo o resolución de sobrecarga. Después de la traducción de expresiones de consulta, las invocaciones de método resultantes se procesan como invocaciones de método normales y esto puede, a su vez, descubrir errores en tiempo de compilación. Estas condiciones de error incluyen, entre otros, los métodos que no existen, los argumentos de los tipos incorrectos y los métodos genéricos en los que se produce un error en la inferencia de tipos.
Una expresión de consulta se procesa aplicando repetidamente las siguientes traducciones hasta que no se pueden reducir aún más. Las traducciones se enumeran en orden de aplicación: cada sección supone que las traducciones de las secciones anteriores se han realizado exhaustivamente y, una vez agotadas, una sección no se volverá a consultar más adelante en el procesamiento de la misma expresión de consulta.
Es un error en tiempo de compilación para que una expresión de consulta incluya una asignación a una variable de intervalo o el uso de una variable de rango como argumento para una referencia o parámetro de salida.
Algunas traducciones insertan variables de rango con identificadores transparentes indicados por *. Estos se describen más adelante en §12.20.3.8.
12.20.3.2 Expresiones de consulta con continuaciones
Expresión de consulta con una continuación después de su cuerpo de consulta
from «x1» in «e1» «b1» into «x2» «b2»
se traduce en
from «x2» in ( from «x1» in «e1» «b1» ) «b2»
Las traducciones de las secciones siguientes suponen que las consultas no tienen continuación.
Ejemplo: Ejemplo:
from c in customers group c by c.Country into g select new { Country = g.Key, CustCount = g.Count() }
se traduce en:
from g in (from c in customers group c by c.Country) select new { Country = g.Key, CustCount = g.Count() }
la traducción final de la cual es:
customers. GroupBy(c => c.Country). Select(g => new { Country = g.Key, CustCount = g.Count() })
ejemplo final
12.20.3.3 Tipos de variables de rango explícitos
Cláusula from
que especifica explícitamente un tipo de variable de rango
from «T» «x» in «e»
se traduce en
from «x» in ( «e» ) . Cast < «T» > ( )
Cláusula join
que especifica explícitamente un tipo de variable de rango
join «T» «x» in «e» on «k1» equals «k2»
se traduce en
join «x» in ( «e» ) . Cast < «T» > ( ) on «k1» equals «k2»
Las traducciones de las secciones siguientes suponen que las consultas no tienen tipos de variables de intervalo explícitos.
Ejemplo: El ejemplo
from Customer c in customers where c.City == "London" select c
se traduce en
from c in (customers).Cast<Customer>() where c.City == "London" select c
la traducción final de la cual es
customers. Cast<Customer>(). Where(c => c.City == "London")
ejemplo final
Nota: Los tipos de variables de intervalo explícitos son útiles para consultar colecciones que implementan la interfaz no genérica
IEnumerable
, pero no la interfaz genéricaIEnumerable<T>
. En el ejemplo anterior, este sería el caso si los clientes fueran de tipoArrayList
. nota final
12.20.3.4 Degenerar expresiones de consulta
Expresión de consulta del formulario
from «x» in «e» select «x»
se traduce en
( «e» ) . Select ( «x» => «x» )
Ejemplo: El ejemplo
from c in customers select c
se traduce en
(customers).Select(c => c)
ejemplo final
Una expresión de consulta degenerada es una que selecciona trivialmente los elementos del origen.
Nota: Las fases posteriores de la traducción (§12.20.3.6 y §12.20.3.7) quitan las consultas degeneradas introducidas por otros pasos de traducción reemplazandolas por su origen. Sin embargo, es importante asegurarse de que el resultado de una expresión de consulta nunca es el propio objeto de origen. De lo contrario, devolver el resultado de dicha consulta podría exponer accidentalmente datos privados (por ejemplo, una matriz de elementos) a un autor de la llamada. Por lo tanto, este paso protege las consultas degeneradas escritas directamente en el código fuente llamando
Select
explícitamente al origen. A continuación, es necesario que los implementadores de y otros operadores deSelect
consulta se aseguren de que estos métodos nunca devuelvan el propio objeto de origen. nota final
12.20.3.5 Desde, let, where, join y orderby cláusulas
Expresión de consulta con una segunda from
cláusula seguida de una select
cláusula
from «x1» in «e1»
from «x2» in «e2»
select «v»
se traduce en
( «e1» ) . SelectMany( «x1» => «e2» , ( «x1» , «x2» ) => «v» )
Ejemplo: El ejemplo
from c in customers from o in c.Orders select new { c.Name, o.OrderID, o.Total }
se traduce en
(customers). SelectMany(c => c.Orders, (c,o) => new { c.Name, o.OrderID, o.Total } )
ejemplo final
Expresión de consulta con una segunda from
cláusula seguida de un cuerpo Q
de consulta que contiene un conjunto no vacío de cláusulas de cuerpo de consulta:
from «x1» in «e1»
from «x2» in «e2»
Q
se traduce en
from * in («e1») . SelectMany( «x1» => «e2» ,
( «x1» , «x2» ) => new { «x1» , «x2» } )
Q
Ejemplo: El ejemplo
from c in customers from o in c.Orders orderby o.Total descending select new { c.Name, o.OrderID, o.Total }
se traduce en
from * in (customers). SelectMany(c => c.Orders, (c,o) => new { c, o }) orderby o.Total descending select new { c.Name, o.OrderID, o.Total }
la traducción final de la cual es
customers. SelectMany(c => c.Orders, (c,o) => new { c, o }). OrderByDescending(x => x.o.Total). Select(x => new { x.c.Name, x.o.OrderID, x.o.Total })
donde
x
es un identificador generado por el compilador que, de lo contrario, es invisible y inaccesible.ejemplo final
Expresión let
junto con su cláusula anterior from
:
from «x» in «e»
let «y» = «f»
...
se traduce en
from * in ( «e» ) . Select ( «x» => new { «x» , «y» = «f» } )
...
Ejemplo: El ejemplo
from o in orders let t = o.Details.Sum(d => d.UnitPrice * d.Quantity) where t >= 1000 select new { o.OrderID, Total = t }
se traduce en
from * in (orders).Select( o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) }) where t >= 1000 select new { o.OrderID, Total = t }
la traducción final de la cual es
orders .Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) }) .Where(x => x.t >= 1000) .Select(x => new { x.o.OrderID, Total = x.t })
donde
x
es un identificador generado por el compilador que, de lo contrario, es invisible y inaccesible.ejemplo final
Expresión where
junto con su cláusula anterior from
:
from «x» in «e»
where «f»
...
se traduce en
from «x» in ( «e» ) . Where ( «x» => «f» )
...
Una join
cláusula seguida inmediatamente de una select
cláusula
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2»
select «v»
se traduce en
( «e1» ) . Join( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «x2» ) => «v» )
Ejemplo: El ejemplo
from c in customersh join o in orders on c.CustomerID equals o.CustomerID select new { c.Name, o.OrderDate, o.Total }
se traduce en
(customers).Join( orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c.Name, o.OrderDate, o.Total })
ejemplo final
Una join
cláusula seguida de una cláusula de cuerpo de consulta:
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2»
...
se traduce en
from * in ( «e1» ) . Join(
«e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «x2» ) => new { «x1» , «x2» })
...
Una join
-into
cláusula seguida inmediatamente de una select
cláusula
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2» into «g»
select «v»
se traduce en
( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «g» ) => «v» )
Una join into
cláusula seguida de una cláusula de cuerpo de consulta
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2» into *g»
...
se traduce en
from * in ( «e1» ) . GroupJoin(
«e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «g» ) => new { «x1» , «g» })
...
Ejemplo: El ejemplo
from c in customers join o in orders on c.CustomerID equals o.CustomerID into co let n = co.Count() where n >= 10 select new { c.Name, OrderCount = n }
se traduce en
from * in (customers).GroupJoin( orders, c => c.CustomerID, o => o.CustomerID, (c, co) => new { c, co }) let n = co.Count() where n >= 10 select new { c.Name, OrderCount = n }
la traducción final de la cual es
customers .GroupJoin( orders, c => c.CustomerID, o => o.CustomerID, (c, co) => new { c, co }) .Select(x => new { x, n = x.co.Count() }) .Where(y => y.n >= 10) .Select(y => new { y.x.c.Name, OrderCount = y.n })
donde
x
yy
son identificadores generados por el compilador que, de lo contrario, son invisibles e inaccesibles.ejemplo final
Una orderby
cláusula y su cláusula anterior from
:
from «x» in «e»
orderby «k1» , «k2» , ... , «kn»
...
se traduce en
from «x» in ( «e» ) .
OrderBy ( «x» => «k1» ) .
ThenBy ( «x» => «k2» ) .
... .
ThenBy ( «x» => «kn» )
...
Si una ordering
cláusula especifica un indicador de dirección descendente, se genera una invocación de OrderByDescending
o ThenByDescending
en su lugar.
Ejemplo: El ejemplo
from o in orders orderby o.Customer.Name, o.Total descending select o
tiene la traducción final
(orders) .OrderBy(o => o.Customer.Name) .ThenByDescending(o => o.Total)
ejemplo final
En las traducciones siguientes se supone que no hay cláusulas let
, where
o join
orderby
y que no hay más de una cláusula inicial from
en cada expresión de consulta.
12.20.3.6 Cláusulas Select
Expresión de consulta del formulario
from «x» in «e» select «v»
se traduce en
( «e» ) . Select ( «x» => «v» )
excepto cuando «v»
es el identificador «x»
, la traducción es simplemente
( «e» )
Ejemplo: El ejemplo
from c in customers.Where(c => c.City == "London") select c
se traduce simplemente en
(customers).Where(c => c.City == "London")
ejemplo final
12.20.3.7 Cláusulas group
Una group
cláusula
from «x» in «e» group «v» by «k»
se traduce en
( «e» ) . GroupBy ( «x» => «k» , «x» => «v» )
excepto cuando «v»
es el identificador «x»
, la traducción es
( «e» ) . GroupBy ( «x» => «k» )
Ejemplo: El ejemplo
from c in customers group c.Name by c.Country
se traduce en
(customers).GroupBy(c => c.Country, c => c.Name)
ejemplo final
12.20.3.8 Identificadores transparentes
Algunas traducciones insertan variables de rango con identificadores transparentes indicados por *
. Los identificadores transparentes solo existen como un paso intermedio en el proceso de traducción de expresiones de consulta.
Cuando una traducción de consultas inserta un identificador transparente, los pasos de traducción adicionales propagan el identificador transparente a funciones anónimas y inicializadores de objetos anónimos. En esos contextos, los identificadores transparentes tienen el siguiente comportamiento:
- Cuando se produce un identificador transparente como parámetro en una función anónima, los miembros del tipo anónimo asociado se encuentran automáticamente en el ámbito del cuerpo de la función anónima.
- Cuando un miembro con un identificador transparente está en el ámbito, los miembros de ese miembro también están en el ámbito.
- Cuando un identificador transparente se produce como un declarador de miembro en un inicializador de objeto anónimo, introduce un miembro con un identificador transparente.
En los pasos de traducción descritos anteriormente, los identificadores transparentes siempre se presentan junto con tipos anónimos, con la intención de capturar varias variables de rango como miembros de un único objeto. Se permite que una implementación de C# use un mecanismo diferente al de los tipos anónimos para agrupar varias variables de intervalo. En los ejemplos de traducción siguientes se supone que se usan tipos anónimos y se muestra una posible traducción de identificadores transparentes.
Ejemplo: El ejemplo
from c in customers from o in c.Orders orderby o.Total descending select new { c.Name, o.Total }
se traduce en
from * in (customers).SelectMany(c => c.Orders, (c,o) => new { c, o }) orderby o.Total descending select new { c.Name, o.Total }
que se traduce aún más en
customers .SelectMany(c => c.Orders, (c,o) => new { c, o }) .OrderByDescending(* => o.Total) .Select(\* => new { c.Name, o.Total })
que, cuando se borran los identificadores transparentes, equivale a
customers .SelectMany(c => c.Orders, (c,o) => new { c, o }) .OrderByDescending(x => x.o.Total) .Select(x => new { x.c.Name, x.o.Total })
donde
x
es un identificador generado por el compilador que, de lo contrario, es invisible y inaccesible.En el ejemplo
from c in customers join o in orders on c.CustomerID equals o.CustomerID join d in details on o.OrderID equals d.OrderID join p in products on d.ProductID equals p.ProductID select new { c.Name, o.OrderDate, p.ProductName }
se traduce en
from * in (customers).Join( orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }) join d in details on o.OrderID equals d.OrderID join p in products on d.ProductID equals p.ProductID select new { c.Name, o.OrderDate, p.ProductName }
que se reduce aún más a
customers .Join(orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }) .Join(details, * => o.OrderID, d => d.OrderID, (*, d) => new { *, d }) .Join(products, * => d.ProductID, p => p.ProductID, (*, p) => new { c.Name, o.OrderDate, p.ProductName })
la traducción final de la cual es
customers .Join(orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }) .Join(details, x => x.o.OrderID, d => d.OrderID, (x, d) => new { x, d }) .Join(products, y => y.d.ProductID, p => p.ProductID, (y, p) => new { y.x.c.Name, y.x.o.OrderDate, p.ProductName })
donde
x
yy
son identificadores generados por el compilador que, de lo contrario, son invisibles e inaccesibles. ejemplo final
12.20.4 El patrón query-expression
El patrón Query-expression establece un patrón de métodos que los tipos pueden implementar para admitir expresiones de consulta.
Un tipo C<T>
genérico admite el patrón query-expression-pattern si sus métodos de miembro público y los métodos de extensión accesibles públicamente podrían reemplazarse por la siguiente definición de clase. Los miembros y los métodos extensos accesibles se conocen como la "forma" de un tipo C<T>
genérico . Se usa un tipo genérico para ilustrar las relaciones adecuadas entre los tipos de parámetro y de valor devuelto, pero también es posible implementar el patrón para tipos no genéricos.
delegate R Func<T1,R>(T1 arg1);
delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);
class C
{
public C<T> Cast<T>() { ... }
}
class C<T> : C
{
public C<T> Where(Func<T,bool> predicate) { ... }
public C<U> Select<U>(Func<T,U> selector) { ... }
public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
Func<T,U,V> resultSelector) { ... }
public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,U,V> resultSelector) { ... }
public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector) { ... }
public O<T> OrderBy<K>(Func<T,K> keySelector) { ... }
public O<T> OrderByDescending<K>(Func<T,K> keySelector) { ... }
public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector) { ... }
public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
Func<T,E> elementSelector) { ... }
}
class O<T> : C<T>
{
public O<T> ThenBy<K>(Func<T,K> keySelector) { ... }
public O<T> ThenByDescending<K>(Func<T,K> keySelector) { ... }
}
class G<K,T> : C<T>
{
public K Key { get; }
}
Los métodos anteriores usan los tipos Func<T1, R>
de delegado genéricos y Func<T1, T2, R>
, pero podrían haber usado igualmente otros tipos de árbol de expresiones o delegados con las mismas relaciones en los tipos de parámetro y valor devuelto.
Nota: La relación recomendada entre
C<T>
yO<T>
que garantiza que losThenBy
métodos yThenByDescending
solo están disponibles en el resultado de oOrderBy
OrderByDescending
. nota final
Nota: La forma recomendada del resultado de
GroupBy
: una secuencia de secuencias, donde cada secuencia interna tiene una propiedad adicionalKey
. nota final
Nota: Dado que las expresiones de consulta se traducen a invocaciones de método mediante una asignación sintáctica, los tipos tienen una flexibilidad considerable en la forma en que implementan cualquiera o todos los patrones de expresión de consulta. Por ejemplo, los métodos del patrón se pueden implementar como métodos de instancia o como métodos de extensión porque los dos tienen la misma sintaxis de invocación, y los métodos pueden solicitar delegados o árboles de expresión porque las funciones anónimas se pueden convertir en ambas. Los tipos que implementan solo algunos de los patrones de expresión de consulta solo admiten traducciones de expresiones de consulta que se asignan a los métodos que admiten el tipo. nota final
Nota: El
System.Linq
espacio de nombres proporciona una implementación del patrón query-expression para cualquier tipo que implemente laSystem.Collections.Generic.IEnumerable<T>
interfaz. nota final
12.21 Operadores de asignación
12.21.1 General
Todos los operadores de asignación, pero uno de los operadores de asignación, asigna un nuevo valor a una variable, una propiedad, un evento o un elemento indexador. La excepción , = ref
asigna una referencia de variable (§9.5) a una variable de referencia (§9.7).
assignment
: unary_expression assignment_operator expression
;
assignment_operator
: '=' 'ref'? | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<='
| right_shift_assignment
;
El operando izquierdo de una asignación debe ser una expresión clasificada como una variable, o, excepto , = ref
un acceso de propiedad, un acceso de indizador, un acceso a eventos o una tupla. Una expresión de declaración no se permite directamente como operando izquierdo, pero puede producirse como un paso en la evaluación de una asignación deconstrucción.
El =
operador se denomina operador de asignación simple. Asigna el valor o los valores del operando derecho a la variable, propiedad, elemento indexador o elementos de tupla proporcionados por el operando izquierdo. El operando izquierdo del operador de asignación simple no será un acceso a eventos (excepto como se describe en §15.8.2). El operador de asignación simple se describe en §12.21.2.
El operador = ref
se denomina operador de asignación de referencias. Hace que el operando derecho, que será un variable_reference (§9.5), el referente de la variable de referencia designada por el operando izquierdo. El operador de asignación ref se describe en §12.21.3.
Los operadores de asignación distintos de los =
operadores y = ref
se denominan operadores de asignación compuestos. Estos operadores realizan la operación indicada en los dos operandos y, a continuación, asignan el valor resultante a la variable, propiedad o elemento indexador proporcionado por el operando izquierdo. Los operadores de asignación compuesta se describen en §12.21.4.
Los +=
operadores y -=
con una expresión de acceso a eventos como operando izquierdo se denominan operadores de asignación de eventos. Ningún otro operador de asignación es válido con un acceso a eventos como operando izquierdo. Los operadores de asignación de eventos se describen en §12.21.5.
Los operadores de asignación son asociativos a la derecha, lo que significa que las operaciones se agrupan de derecha a izquierda.
Ejemplo: una expresión del formulario
a = b = c
se evalúa comoa = (b = c)
. ejemplo final
12.21.2 Asignación simple
El =
operador se denomina operador de asignación simple.
Si el operando izquierdo de una asignación simple es del formulario o donde tiene el tipo dynamic
en tiempo de compilación , la asignación se enlaza dinámicamente (§12.3.3).E
E[Ei]
E.P
En este caso, el tipo en tiempo de compilación de la expresión de asignación es dynamic
y la resolución descrita a continuación tendrá lugar en tiempo de ejecución en función del tipo de tiempo de ejecución de E
. Si el operando izquierdo tiene el formato E[Ei]
donde al menos un elemento de Ei
tiene el tipo dynamic
en tiempo de compilación y el tipo de tiempo de compilación de E
no es una matriz, el acceso al indexador resultante se enlaza dinámicamente, pero con comprobación limitada en tiempo de compilación (§12.6.5).
Una asignación simple donde el operando izquierdo se clasifica como una tupla también se denomina asignación deconstrucción. Si alguno de los elementos de tupla del operando izquierdo tiene un nombre de elemento, se produce un error en tiempo de compilación. Si alguno de los elementos de tupla del operando izquierdo es un declaration_expression y cualquier otro elemento no es un declaration_expression o un descarte simple, se produce un error en tiempo de compilación.
El tipo de una asignación x = y
simple es el tipo de una asignación a x
de y
, que se determina de forma recursiva como se indica a continuación:
- Si
x
es una expresión(x1, ..., xn)
de tupla yy
se puede descompilar en una expresión(y1, ..., yn)
de tupla conn
elementos (§12.7) y cada asignación axi
deyi
tiene el tipoTi
, la asignación tiene el tipo(T1, ..., Tn)
. - De lo contrario, si
x
se clasifica como una variable, la variable noreadonly
es ,x
tiene un tipoT
yy
tiene una conversión implícita aT
, la asignación tiene el tipoT
. - De lo contrario, si
x
se clasifica como una variable con tipo implícito (es decir, una expresión de declaración con tipo implícito) yy
tiene un tipo , el tipoT
inferido de la variable esT
y la asignación tiene el tipoT
. - De lo contrario, si
x
se clasifica como una propiedad o acceso al indexador, la propiedad o el indexador tiene un descriptor de acceso set accesible,x
tiene un tipoT
yy
tiene una conversión implícita enT
, la asignación tiene el tipoT
. - De lo contrario, la asignación no es válida y se produce un error en tiempo de enlace.
El procesamiento en tiempo de ejecución de una asignación simple del formulario x = y
con tipo T
se realiza como una asignación de y
x
con tipo T
, que consta de los siguientes pasos recursivos:
x
se evalúa si aún no lo era.- Si
x
se clasifica como una variable,y
se evalúa y, si es necesario, se convierte aT
a través de una conversión implícita (§10.2).- Si la variable dada por
x
es un elemento de matriz de un reference_type, se realiza una comprobación en tiempo de ejecución para asegurarse de que el valor calculado paray
es compatible con la instancia de matriz de la quex
es un elemento. La comprobación se realiza correctamente siy
es , o si existe una conversión de referencia implícita (§10.2.8) del tipo de la instancia a la que hacey
referencia el tipo de elemento real de la instancia de matriz que contienex
null
. De lo contrario, se produce una excepciónSystem.ArrayTypeMismatchException
. - El valor resultante de la evaluación y conversión de
y
se almacena en la ubicación dada por la evaluación dex
y se produce como resultado de la asignación.
- Si la variable dada por
- Si
x
se clasifica como acceso de propiedad o indexador:y
se evalúa y, si es necesario, se convierte aT
a través de una conversión implícita (§10.2).- El descriptor de acceso set de
x
se invoca con el valor resultante de la evaluación y conversión dey
como argumento value. - El valor resultante de la evaluación y conversión de
y
se produce como resultado de la asignación.
- Si
x
se clasifica como una tupla(x1, ..., xn)
con aridadn
:y
se descontruye conn
elementos en una expresióne
de tupla .- Se crea una tupla
t
de resultado mediante la conversióne
aT
mediante una conversión de tupla implícita. - para cada
xi
uno en orden de izquierda a derecha, se realiza una asignación axi
det.Itemi
, excepto quexi
no se evalúa de nuevo. t
se produce como resultado de la asignación.
Nota: si el tipo de tiempo de compilación de
x
esdynamic
y hay una conversión implícita del tipo de tiempo de compilación dey
adynamic
, no se requiere ninguna resolución en tiempo de ejecución. nota final
Nota: Las reglas de covarianza de matriz (§17.6) permiten que un valor de un tipo
A[]
de matriz sea una referencia a una instancia de un tipoB[]
de matriz, siempre que exista una conversión de referencia implícita desdeB
aA
. Debido a estas reglas, la asignación a un elemento de matriz de un reference_type requiere una comprobación en tiempo de ejecución para asegurarse de que el valor asignado es compatible con la instancia de matriz. En el ejemplostring[] sa = new string[10]; object[] oa = sa; oa[0] = null; // OK oa[1] = "Hello"; // OK oa[2] = new ArrayList(); // ArrayTypeMismatchException
la última asignación hace que se produzca un
System.ArrayTypeMismatchException
elemento porque una referencia a unArrayList
no se puede almacenar en un elemento de .string[]
nota final
Cuando una propiedad o indexador declarada en un struct_type es el destino de una asignación, la expresión de instancia asociada a la propiedad o el acceso al indexador se clasificará como una variable. Si la expresión de instancia se clasifica como un valor, se produce un error en tiempo de enlace.
Nota: Debido a §12.8.7, la misma regla también se aplica a los campos. nota final
Ejemplo: dadas las declaraciones:
struct Point { int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public int X { get { return x; } set { x = value; } } public int Y { get { return y; } set { y = value; } } } struct Rectangle { Point a, b; public Rectangle(Point a, Point b) { this.a = a; this.b = b; } public Point A { get { return a; } set { a = value; } } public Point B { get { return b; } set { b = value; } } }
en el ejemplo
Point p = new Point(); p.X = 100; p.Y = 100; Rectangle r = new Rectangle(); r.A = new Point(10, 10); r.B = p;
Las asignaciones a
p.X
,p.Y
,r.A
yr.B
se permiten porquep
yr
son 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.A
yr.B
no son variables.ejemplo final
12.21.3 Asignación ref
El = ref
operador se conoce como operador de asignación ref.
El operando izquierdo debe ser una expresión que se enlaza a una variable de referencia (§9.7), un parámetro de referencia (distinto this
de ), un parámetro de salida o un parámetro de entrada. El operando derecho debe ser una expresión que produce un variable_reference designando un valor del mismo tipo que el operando izquierdo.
Es un error en tiempo de compilación si el contexto ref-safe-context (§9.7.2) del operando izquierdo es más amplio que el contexto ref-safe-context del operando derecho.
El operando derecho se asignará definitivamente en el punto de la asignación ref.
Cuando el operando izquierdo se enlaza a un parámetro de salida, se trata de un error si ese parámetro de salida no se ha asignado definitivamente al principio del operador de asignación ref.
Si el operando izquierdo es una referencia que se puede escribir (es decir, designa algo distinto de un ref readonly
parámetro local o de entrada), el operando derecho será un variable_reference que se pueda escribir. Si la variable de operando derecho es grabable, el operando izquierdo puede ser una referencia de solo lectura o escritura.
La operación hace que el operando izquierdo sea un alias de la variable de operando derecho. El alias se puede convertir en de solo lectura incluso si la variable de operando derecho es grabable.
El operador de asignación de referencia produce un variable_reference del tipo asignado. Se puede escribir si el operando izquierdo es grabable.
El operador de asignación ref no leerá la ubicación de almacenamiento a la que hace referencia el operando derecho.
Ejemplo: Estos son algunos ejemplos de uso
= ref
de :public static int M1() { ... } public static ref int M2() { ... } public static ref uint M2u() { ... } public static ref readonly int M3() { ... } public static void Test() { int v = 42; ref int r1 = ref v; // OK, r1 refers to v, which has value 42 r1 = ref M1(); // Error; M1 returns a value, not a reference r1 = ref M2(); // OK; makes an alias r1 = ref M2u(); // Error; lhs and rhs have different types r1 = ref M3(); // error; M3 returns a ref readonly, which r1 cannot honor ref readonly int r2 = ref v; // OK; make readonly alias to ref r2 = ref M2(); // OK; makes an alias, adding read-only protection r2 = ref M3(); // OK; makes an alias and honors the read-only r2 = ref (r1 = ref M2()); // OK; r1 is an alias to a writable variable, // r2 is an alias (with read-only access) to the same variable }
ejemplo final
Nota: Al leer código mediante un
= ref
operador, puede resultar tentador leer laref
parte como parte del operando. Esto resulta especialmente confuso cuando el operando es una expresión condicional?:
. Por ejemplo, al leerloref int a = ref b ? ref x : ref y;
, es importante leerlo como= ref
operador yb ? ref x : ref y
ser el operando derecho:ref int a = ref (b ? ref x : ref y);
. Lo importante es que la expresiónref b
no forma parte de esa instrucción, aunque puede aparecer tan de primera vista. nota final
12.21.4 Asignación compuesta
Si el operando izquierdo de una asignación compuesta tiene el formato o donde tiene el tipo dynamic
en tiempo de compilación, la asignación se enlaza dinámicamente (§12.3.3).E
E[Ei]
E.P
En este caso, el tipo en tiempo de compilación de la expresión de asignación es dynamic
y la resolución descrita a continuación tendrá lugar en tiempo de ejecución en función del tipo de tiempo de ejecución de E
. Si el operando izquierdo tiene el formato E[Ei]
donde al menos un elemento de Ei
tiene el tipo dynamic
en tiempo de compilación y el tipo de tiempo de compilación de E
no es una matriz, el acceso al indexador resultante se enlaza dinámicamente, pero con comprobación limitada en tiempo de compilación (§12.6.5).
Una operación del formulario x «op»= y
se procesa aplicando la resolución de sobrecarga del operador binario (§12.4.5) como si la operación se escribiera x «op» y
. A continuación,
- Si el tipo de valor devuelto del operador seleccionado se puede convertir implícitamente al tipo de
x
, la operación se evalúa comox = x «op» y
, excepto quex
se evalúa solo una vez. - De lo contrario, si el operador seleccionado es un operador predefinido, si el tipo de valor devuelto del operador seleccionado se puede convertir explícitamente al tipo de
x
y siy
se convierte implícitamente en el tipo dex
o el operador es un operador de desplazamiento, la operación se evalúa comox = (T)(x «op» y)
, dondeT
es el tipo dex
, excepto quex
se evalúa solo una vez. - De lo contrario, la asignación compuesta no es válida y se produce un error en tiempo de enlace.
El término "evaluado solo una vez" significa que en la evaluación de x «op» y
, los resultados de cualquier expresión constituyente de x
se guardan temporalmente y, a continuación, se reutilizan al realizar la asignación a x
.
Ejemplo: En la asignación
A()[B()] += C()
, dondeA
es un método que devuelveint[]
y yB
C
son 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 propiedades o acceso de indexador, la propiedad o el indexador tendrán un descriptor de acceso get y un descriptor de acceso set. Si no es así, se produce un error en tiempo de enlace.
La segunda regla anterior permite x «op»= y
evaluarse como x = (T)(x «op» y)
en determinados contextos. La regla existe de modo que los operadores predefinidos se pueden usar como operadores compuestos cuando el operando izquierdo es de tipo sbyte
, , short
byte
, ushort
o char
. Incluso cuando ambos argumentos son de uno de esos tipos, los operadores predefinidos generan un resultado de tipo int
, como se describe en §12.4.7.3. Por lo tanto, sin una conversión no sería posible asignar el resultado al operando izquierdo.
El efecto intuitivo de la regla para los operadores predefinidos es simplemente que x «op»= y
se permite si se permiten ambos x «op» y
y x = y
.
Ejemplo: en el código siguiente
byte b = 0; char ch = '\0'; int i = 0; b += 1; // OK b += 1000; // Error, b = 1000 not permitted b += i; // Error, b = i not permitted b += (byte)i; // OK ch += 1; // Error, ch = 1 not permitted ch += (char)1; // OK
la razón intuitiva de cada error es que una asignación simple correspondiente también habría sido un error.
ejemplo final
Nota: Esto también significa que las operaciones de asignación compuestas admiten operadores elevados. Dado que una asignación
x «op»= y
compuesta se evalúa comox = x «op» y
ox = (T)(x «op» y)
, las reglas de evaluación cubren implícitamente operadores elevados. nota final
12.21.5 Asignación de eventos
Si el operando izquierdo del a += or -=
operador se clasifica como acceso a eventos, la expresión se evalúa de la siguiente manera:
- La expresión de instancia, si existe, del acceso al evento se evalúa.
- Se evalúa el
+=
operando derecho del operador o-=
y, si es necesario, se convierte al tipo del operando izquierdo a través de una conversión implícita (§10.2). - Se invoca un descriptor de acceso de eventos del evento, con una lista de argumentos que consta del valor calculado en el paso anterior. Si el operador era
+=
, se invoca el descriptor de acceso add; si el operador era-=
, se invoca el descriptor de acceso remove.
Una expresión de asignación de eventos no produce un valor. Por lo tanto, una expresión de asignación de eventos solo es válida en el contexto de un statement_expression (§13.7).
Expresión 12.22
Una expresión es un non_assignment_expression o una asignación.
expression
: non_assignment_expression
| assignment
;
non_assignment_expression
: declaration_expression
| conditional_expression
| lambda_expression
| query_expression
;
12.23 Expresiones constantes
Una expresión constante es una expresión que se evaluará completamente en tiempo de compilación.
constant_expression
: expression
;
Una expresión constante tendrá el valor null
o uno de los siguientes tipos:
sbyte
,byte
,short
,ushort
, ,uint
int
,long
,ulong
double
float
decimal
char
, , ;string
bool
- un tipo de enumeración; o
- una expresión de valor predeterminada (§12.8.21) para un tipo de referencia.
Solo se permiten las siguientes construcciones en expresiones constantes:
- Literales (incluido el
null
literal). - Referencias a
const
miembros de tipos de clase y estructura. - Referencias a miembros de tipos de enumeración.
- Referencias a constantes locales.
- Subexpresiones entre paréntesis, que son expresiones constantes.
- Expresiones de conversión.
checked
yunchecked
expresiones.nameof
Expresiones.- Los operadores predefinidos
+
,-
,!
(negación lógica) y~
unarios. - Los operadores binarios predefinidos
+
,-
,>>
<
&
|
<<
^
%
&&
||
/
*
!=
>
==
<=
y .>=
- Operador
?:
condicional. - Operador
!
null-forgiving (§12.8.9). sizeof
expresiones, siempre que el tipo no administrado sea uno de los tipos especificados en §23.6.9 para los quesizeof
devuelve un valor constante.- Expresiones de valor predeterminadas, siempre que el tipo sea uno de los tipos enumerados anteriormente.
Las conversiones siguientes se permiten en expresiones constantes:
- Conversiones de identidad
- Conversiones numéricas
- Conversiones de enumeración
- Conversiones de expresiones constantes
- Conversiones de referencia implícitas y explícitas, siempre que el origen de las conversiones sea una expresión constante que se evalúa como el
null
valor.
Nota: No se permiten otras conversiones que incluyen conversión boxing, unboxing y conversiones de referencia implícitas de valores que
null
no son en expresiones constantes. nota final
Ejemplo: en el código siguiente
class C { const object i = 5; // error: boxing conversion not permitted const object str = "hello"; // error: implicit reference conversion }
la inicialización de
i
es un error porque se requiere una conversión boxing. La inicialización destr
es un error porque se requiere una conversión de referencia implícita de un valor que nonull
es .ejemplo final
Cada vez que una expresión cumple los requisitos enumerados anteriormente, la expresión se evalúa en tiempo de compilación. Esto es cierto incluso si la expresión es una subexpresión de una expresión mayor que contiene construcciones que no son constantes.
La evaluación en tiempo de compilación de expresiones constantes usa las mismas reglas que la evaluación en tiempo de ejecución de expresiones no constantes, salvo que cuando la evaluación en tiempo de ejecución habría producido una excepción, la evaluación en tiempo de compilación provoca un error en tiempo de compilación.
A menos que una expresión constante se coloque explícitamente en un unchecked
contexto, los desbordamientos que se producen en operaciones aritméticas de tipo entero y conversiones durante la evaluación en tiempo de compilación de la expresión siempre provocan errores en tiempo de compilación (§12.8.20).
Las expresiones constantes son necesarias en los contextos que se enumeran a continuación y se indica en la gramática mediante constant_expression. En estos contextos, se produce un error en tiempo de compilación si una expresión no se puede evaluar completamente en tiempo de compilación.
- Declaraciones de constantes (§15.4)
- Declaraciones de miembro de enumeración (§19.4)
- Argumentos predeterminados de listas de parámetros (§15.6.2)
case
etiquetas de unaswitch
instrucción (§13.8.3).goto case
instrucciones (§13.10.4)- Longitudes de dimensión en una expresión de creación de matriz (§12.8.17.5) que incluye un inicializador.
- Atributos (§22)
- En un constant_pattern (§11.2.3)
Una conversión de expresión constante implícita (§10.2.11) permite convertir sbyte
una expresión constante de tipo int
en , , byte
short
, ushort
, uint
o ulong
, siempre que el valor de la expresión constante esté dentro del intervalo del tipo de destino.
12.24 Expresiones booleanas
Un boolean_expression es una expresión que produce un resultado de tipo bool
; ya sea directamente o a través de la aplicación de operator true
en determinados contextos, tal como se especifica en lo siguiente:
boolean_expression
: expression
;
La expresión condicional de control de un if_statement (§13.8.2), while_statement (§13.9.2), do_statement (§13.9.3) o for_statement (§13.9.4) es un boolean_expression. La expresión condicional de control del ?:
operador (§12.18) sigue las mismas reglas que una boolean_expression, pero por motivos de precedencia del operador se clasifica como un null_coalescing_expression.
Se requiere un boolean_expressionE
para poder generar un valor de tipo bool
, como se indica a continuación:
- Si E se convierte implícitamente en
bool
entonces en tiempo de ejecución, se aplica la conversión implícita. - De lo contrario, la resolución de sobrecarga del operador unario (§12.4.4) se usa para encontrar una mejor implementación única de
operator true
enE
y esa implementación se aplica en tiempo de ejecución. - Si no se encuentra ningún operador de este tipo, se produce un error en tiempo de enlace.
ECMA C# draft specification