8 tipos
8.1 General
Los tipos del lenguaje C# se dividen en dos categorías principales: tipos de referencia y tipos de valor. Los tipos de valor y los tipos de referencia pueden ser tipos genéricos, que toman uno o varios parámetros de tipo. Los parámetros de tipo pueden designar los tipos de valor y los tipos de referencia.
type
: reference_type
| value_type
| type_parameter
| pointer_type // unsafe code support
;
pointer_type (§23.3) solo está disponible en código no seguro (§23).
Los tipos de valor difieren de los tipos de referencia en que las variables de los tipos de valor contienen directamente sus datos, mientras que las variables de los tipos de referencia almacenan referencias a sus datos, lo último que se conoce como objetos. Con los tipos de referencia, es posible que dos variables hagan referencia al mismo objeto y, por tanto, es posible que las operaciones en una variable afecten al objeto al que hace referencia la otra variable. Con los tipos de valor, las variables tienen su propia copia de los datos y no es posible que las operaciones de una afecten a la otra.
Nota: Cuando una variable es un parámetro de referencia o salida, no tiene su propio almacenamiento, sino que hace referencia al almacenamiento de otra variable. En este caso, la variable ref o out es efectivamente un alias para otra variable y no una variable distinta. nota final
El sistema de tipos de C#está unificado de modo que un valor de cualquier tipo se pueda tratar como un objeto. Todos los tipos de C# directa o indirectamente se derivan del tipo de clase object
, y object
es la clase base definitiva de todos los tipos. Los valores de tipos de referencia se tratan como objetos mediante la visualización de los valores como tipo object
. Los valores de los tipos de valor se tratan como objetos realizando operaciones de conversión boxing y unboxing (§8.3.13).
Por comodidad, en esta especificación, algunos nombres de tipo de biblioteca se escriben sin usar su calificación de nombre completo. Consulte §C.5 para obtener más información.
8.2 Tipos de referencia
8.2.1 General
Un tipo de referencia es un tipo de clase, un tipo de interfaz, un tipo de matriz, un tipo delegado o el dynamic
tipo . Para cada tipo de referencia que no acepta valores NULL, hay un tipo de referencia que acepta valores NULL correspondiente indicado anexando al ?
nombre de tipo.
reference_type
: non_nullable_reference_type
| nullable_reference_type
;
non_nullable_reference_type
: class_type
| interface_type
| array_type
| delegate_type
| 'dynamic'
;
class_type
: type_name
| 'object'
| 'string'
;
interface_type
: type_name
;
array_type
: non_array_type rank_specifier+
;
non_array_type
: value_type
| class_type
| interface_type
| delegate_type
| 'dynamic'
| type_parameter
| pointer_type // unsafe code support
;
rank_specifier
: '[' ','* ']'
;
delegate_type
: type_name
;
nullable_reference_type
: non_nullable_reference_type nullable_type_annotation
;
nullable_type_annotation
: '?'
;
pointer_type solo está disponible en código no seguro (§23.3). nullable_reference_type se analiza más adelante en §8.9.
Un valor de tipo de referencia es una referencia a una instancia del tipo , lo último conocido como un objeto . El valor null
especial es compatible con todos los tipos de referencia e indica la ausencia de una instancia.
8.2.2 Tipos de clase
Un tipo de clase define una estructura de datos que contiene miembros de datos (constantes y campos), miembros de función (métodos, propiedades, eventos, indexadores, operadores, constructores de instancias, finalizadores y constructores estáticos) y tipos anidados. Los tipos de clase admiten la herencia, un mecanismo por el que las clases derivadas pueden extender y especializar clases base. Las instancias de tipos de clase se crean mediante object_creation_expression s (§12.8.17.2).
Los tipos de clase se describen en §15.
Algunos tipos de clase predefinidos tienen un significado especial en el lenguaje C#, como se describe en la tabla siguiente.
Tipo de clase | Descripción |
---|---|
System.Object |
Clase base definitiva de todos los demás tipos. Consulte §8.2.3. |
System.String |
Tipo de cadena del lenguaje C#. Consulte §8.2.5. |
System.ValueType |
Clase base de todos los tipos de valor. Consulte §8.3.2. |
System.Enum |
Clase base de todos los enum tipos. Consulte §19.5. |
System.Array |
Clase base de todos los tipos de matriz. Consulte §17.2.2. |
System.Delegate |
Clase base de todos los delegate tipos. Consulte §20.1. |
System.Exception |
La clase base de todos los tipos de excepción. Consulte §21.3. |
8.2.3 El tipo de objeto
El object
tipo de clase es la clase base definitiva de todos los demás tipos. Cada tipo de C# deriva directa o indirectamente del tipo de object
clase .
La palabra clave object
es simplemente un alias para la clase System.Object
predefinida .
8.2.4 El tipo dinámico
El dynamic
tipo, como object
, puede hacer referencia a cualquier objeto. Cuando las operaciones se aplican a expresiones de tipo dynamic
, su resolución se aplaza hasta que se ejecuta el programa. Por lo tanto, si la operación no se puede aplicar legítimamente al objeto al que se hace referencia, no se da ningún error durante la compilación. En su lugar, se producirá una excepción cuando se produzca un error en la resolución de la operación en tiempo de ejecución.
El dynamic
tipo se describe más adelante en §8.7 y enlace dinámico en §12.3.1.
8.2.5 El tipo de cadena
El string
tipo es un tipo de clase sellado que hereda directamente de object
. Las instancias de la string
clase representan cadenas de caracteres Unicode.
Los valores del string
tipo se pueden escribir como literales de cadena (§6.4.5.6).
La palabra clave string
es simplemente un alias para la clase System.String
predefinida .
8.2.6 Tipos de interfaz
Una interfaz define un contrato. Una clase o estructura que implemente una interfaz se adhiere a su contrato. Una interfaz puede heredar de varias interfaces base y una clase o estructura puede implementar varias interfaces.
Los tipos de interfaz se describen en §18.
8.2.7 Tipos de matriz
Una matriz es una estructura de datos que contiene cero o más variables, a las que se accede a través de índices calculados. Las variables contenidas en una matriz, denominadas también elementos de la matriz, son todas del mismo tipo y este tipo se conoce como tipo de elemento de la matriz.
Los tipos de matriz se describen en §17.
8.2.8 Tipos delegados
Un delegado es una estructura de datos que hace referencia a uno o varios métodos. Por ejemplo, los métodos también hacen referencia a sus instancias de objeto correspondientes.
Nota: El equivalente más cercano de un delegado en C o C++ es un puntero de función, pero mientras que un puntero de función solo puede hacer referencia a funciones estáticas, un delegado puede hacer referencia a métodos estáticos e de instancia. En este último caso, el delegado almacena no solo una referencia al punto de entrada del método, sino también una referencia a la instancia de objeto en la que se va a invocar el método. nota final
Los tipos de delegado se describen en §20.
8.3 Tipos de valor
8.3.1 General
Un tipo de valor es un tipo de estructura o un tipo de enumeración. C# proporciona un conjunto de tipos de estructura predefinidos denominados tipos simples. Los tipos simples se identifican mediante palabras clave.
value_type
: non_nullable_value_type
| nullable_value_type
;
non_nullable_value_type
: struct_type
| enum_type
;
struct_type
: type_name
| simple_type
| tuple_type
;
simple_type
: numeric_type
| 'bool'
;
numeric_type
: integral_type
| floating_point_type
| 'decimal'
;
integral_type
: 'sbyte'
| 'byte'
| 'short'
| 'ushort'
| 'int'
| 'uint'
| 'long'
| 'ulong'
| 'char'
;
floating_point_type
: 'float'
| 'double'
;
tuple_type
: '(' tuple_type_element (',' tuple_type_element)+ ')'
;
tuple_type_element
: type identifier?
;
enum_type
: type_name
;
nullable_value_type
: non_nullable_value_type nullable_type_annotation
;
A diferencia de una variable de un tipo de referencia, una variable de un tipo de valor solo puede contener el valor null
si el tipo de valor es un tipo de valor que acepta valores NULL (§8.3.12). Para cada tipo de valor que no acepta valores NULL, hay un tipo de valor que acepta valores NULL correspondiente que indica el mismo conjunto de valores más el valor null
.
La asignación a una variable de un tipo de valor crea una copia del valor que se asigna. Esto difiere de la asignación a una variable de un tipo de referencia, que copia la referencia, pero no el objeto identificado por la referencia.
8.3.2 El tipo System.ValueType
Todos los tipos de valor heredan implícitamente de , class
System.ValueType
que, a su vez, hereda de la clase object
. No es posible que ningún tipo derive de un tipo de valor y, por tanto, los tipos de valor estén sellados implícitamente (§15.2.2.3).
Tenga en cuenta que System.ValueType
no es un value_type. En su lugar, es un class_type del que se derivan automáticamente todos los value_type.
8.3.3 Constructores predeterminados
Todos los tipos de valor declaran implícitamente un constructor de instancia sin parámetros público denominado constructor predeterminado. El constructor predeterminado devuelve una instancia inicializada de cero conocida como el valor predeterminado para el tipo de valor:
- Para todos los simple_types, el valor predeterminado es el valor generado por un patrón de bits de todos los ceros:
- Para
sbyte
, ,byte
,short
,int
ushort
,uint
,long
yulong
, el valor predeterminado es0
. - En
char
, el valor predeterminado es'\x0000'
. - En
float
, el valor predeterminado es0.0f
. - En
double
, el valor predeterminado es0.0d
. - Para
decimal
, el valor predeterminado es0m
(es decir, el valor cero con la escala 0). - En
bool
, el valor predeterminado esfalse
. - Para un enum_type
E
, el valor predeterminado es0
, convertido al tipoE
.
- Para
- Para un struct_type, el valor predeterminado es el valor generado estableciendo todos los campos de tipo de valor en su valor predeterminado y todos los campos de tipo de referencia en
null
. - Para un nullable_value_type el valor predeterminado es una instancia para la que la
HasValue
propiedad es false. El valor predeterminado también se conoce como el valor NULL del tipo de valor que acepta valores NULL. Si se intenta leer laValue
propiedad de este valor, se produce una excepción de tipoSystem.InvalidOperationException
(§8.3.12).
Al igual que cualquier otro constructor de instancia, el constructor predeterminado de un tipo de valor se invoca mediante el new
operador .
Nota: Por motivos de eficacia, este requisito no está pensado para que la implementación genere realmente una llamada de constructor. Para los tipos de valor, la expresión de valor predeterminada (§12.8.21) genera el mismo resultado que el uso del constructor predeterminado. nota final
Ejemplo: en el código siguiente, las variables
j
i
yk
se inicializan en cero.class A { void F() { int i = 0; int j = new int(); int k = default(int); } }
ejemplo final
Dado que cada tipo de valor tiene implícitamente un constructor de instancia sin parámetros público, no es posible que un tipo de estructura contenga una declaración explícita de un constructor sin parámetros. Sin embargo, se permite que un tipo de estructura declare constructores de instancia con parámetros (§16.4.9).
Tipos de estructura 8.3.4
Un tipo de estructura es un tipo de valor que puede declarar constantes, campos, métodos, propiedades, eventos, indexadores, operadores, constructores de instancia, constructores estáticos y tipos anidados. La declaración de tipos de estructura se describe en §16.
8.3.5 Tipos simples
C# proporciona un conjunto de tipos predefinidos struct
denominados tipos simples. Los tipos simples se identifican mediante palabras clave, pero estas palabras clave son simplemente alias para tipos predefinidos struct
en el System
espacio de nombres, como se describe en la tabla siguiente.
Palabra clave | Tipo con alias |
---|---|
sbyte |
System.SByte |
byte |
System.Byte |
short |
System.Int16 |
ushort |
System.UInt16 |
int |
System.Int32 |
uint |
System.UInt32 |
long |
System.Int64 |
ulong |
System.UInt64 |
char |
System.Char |
float |
System.Single |
double |
System.Double |
bool |
System.Boolean |
decimal |
System.Decimal |
Dado que un tipo simple alias es un tipo de estructura, cada tipo simple tiene miembros.
Ejemplo:
int
tiene los miembros declarados enSystem.Int32
y los miembros heredados deSystem.Object
y se permiten las siguientes instrucciones:int i = int.MaxValue; // System.Int32.MaxValue constant string s = i.ToString(); // System.Int32.ToString() instance method string t = 123.ToString(); // System.Int32.ToString() instance method
ejemplo final
Nota: Los tipos simples difieren de otros tipos de estructura en que permiten ciertas operaciones adicionales:
- La mayoría de los tipos simples permiten crear valores escribiendo literales (§6.4.5), aunque C# no aprovisiona literales de tipos de estructura en general. Ejemplo:
123
es un literal de tipoint
y'a'
es un literal de tipochar
. ejemplo final- Cuando los operandos de una expresión son constantes de tipo simples, es posible que el compilador evalúe la expresión en tiempo de compilación. Esta expresión se conoce como constant_expression (§12.23). Las expresiones que implican operadores definidos por otros tipos de estructura no se consideran expresiones constantes
- A través
const
de declaraciones, es posible declarar constantes de los tipos simples (§15.4). No es posible tener constantes de otros tipos de estructura, pero se proporciona un efecto similar por campos estáticos de solo lectura.- Las conversiones que implican tipos simples pueden participar en la evaluación de operadores de conversión definidos por otros tipos de estructura, pero un operador de conversión definido por el usuario nunca puede participar en la evaluación de otro operador de conversión definido por el usuario (§10.5.3).
nota final.
8.3.6 Tipos enteros
C# admite nueve tipos enteros: sbyte
, , ushort
byte
short
, int
, uint
, long
, , ulong
y char
. Los tipos enteros tienen los siguientes tamaños y rangos de valores:
- El
sbyte
tipo representa enteros de 8 bits con signo con valores de-128
a127
, ambos incluidos. - El
byte
tipo representa enteros de 8 bits sin signo con valores de0
a255
, ambos incluidos. - El
short
tipo representa enteros de 16 bits con signo con valores de-32768
a32767
, ambos incluidos. - El
ushort
tipo representa enteros de 16 bits sin signo con valores de0
a65535
, ambos incluidos. - El
int
tipo representa enteros de 32 bits con signo con valores de-2147483648
a2147483647
, ambos incluidos. - El
uint
tipo representa enteros de 32 bits sin signo con valores de0
a4294967295
, ambos incluidos. - El
long
tipo representa enteros de 64 bits con signo con valores de-9223372036854775808
a9223372036854775807
, ambos incluidos. - El
ulong
tipo representa enteros de 64 bits sin signo con valores de0
a18446744073709551615
, ambos incluidos. - El
char
tipo representa enteros de 16 bits sin signo con valores de0
a65535
, ambos incluidos. El conjunto de valores posibles para elchar
tipo corresponde al juego de caracteres Unicode.Nota: Aunque
char
tiene la misma representación queushort
, no todas las operaciones permitidas en un tipo se permiten en la otra. nota final
Todos los tipos enteros firmados se representan mediante el formato de complemento de dos.
Los operadores unarios y binarios de integral_type funcionan siempre con precisión de 32 bits con signo, precisión de 32 bits sin signo, precisión de 64 bits con signo o precisión de 64 bits sin signo, como se detalla en §12.4.7.
El char
tipo se clasifica como un tipo entero, pero difiere de los otros tipos enteros de dos maneras:
- No hay conversiones implícitas predefinidas de otros tipos al
char
tipo . En concreto, aunque losbyte
tipos yushort
tienen intervalos de valores que son totalmente representables mediante elchar
tipo, conversiones implícitas de sbyte, byte oushort
parachar
no existir. - Las constantes del
char
tipo se escribirán como character_literals o como integer_literalen combinación con una conversión a tipo char.
Ejemplo:
(char)10
es el mismo que'\x000A'
. ejemplo final
Los checked
operadores y unchecked
las instrucciones se usan para controlar la comprobación de desbordamiento de operaciones aritméticas de tipo entero y conversiones (§12.8.20). En un checked
contexto, un desbordamiento genera un error en tiempo de compilación o hace que se produzca una System.OverflowException
excepción . En un unchecked
contexto, se omiten los desbordamientos y se descartan los bits de orden alto que no se ajusten al tipo de destino.
8.3.7 Tipos de punto flotante
C# admite dos tipos de punto flotante: float
y double
. Los float
tipos y double
se representan con los formatos IEC 60559 de precisión simple de 32 bits y de precisión doble de 64 bits, que proporcionan los siguientes conjuntos de valores:
- Cero positivo y cero negativo. En la mayoría de las situaciones, cero positivo y cero negativo se comportan de forma idéntica como el valor simple cero, pero ciertas operaciones distinguen entre las dos (§12.10.3).
- Infinito positivo e infinito negativo. Los infinitos se generan mediante operaciones como la división por cero de un número distinto de cero.
Ejemplo:
1.0 / 0.0
produce infinito positivo y–1.0 / 0.0
produce infinito negativo. ejemplo final - El valor Not-a-Number , a menudo abreviado NaN. Los Nan se generan mediante operaciones de punto flotante no válidas, como la división de cero por cero.
- Conjunto finito de valores distintos de cero del formulario s × m × 2e, donde s es 1 o -1, y m e están determinados por el tipo de punto flotante particular: para
float
, 0 <m< 2²⁴ y -149 ≤ e ≤ 104, y paradouble
, 0 <m< 2⁵³ y -1075 ≤ e ≤ 970. Los números de punto flotante desnormalizados se consideran valores no cero válidos. C# no requiere ni prohíbe que una implementación conforme admita números de punto flotante desnormalizados.
El float
tipo puede representar valores que van de aproximadamente 1,5 × 10⁻⁴⁵ a 3,4 × 10³⁸ con una precisión de 7 dígitos.
El double
tipo puede representar valores que van de aproximadamente 5,0 × 10⁻³²⁴ a 1,7 × 10³⁸ con una precisión de 15-16 dígitos.
Si cualquiera de los operandos de un operador binario es un tipo de punto flotante, se aplican promociones numéricas estándar, como se detalla en §12.4.7 y la operación se realiza con float
o double
precisión.
Los operadores de punto flotante, incluidos los operadores de asignación, nunca generan excepciones. En su lugar, en situaciones excepcionales, las operaciones de punto flotante producen cero, infinito o NaN, como se describe a continuación:
- El resultado de una operación de punto flotante se redondea al valor representable más cercano en el formato de destino.
- Si la magnitud del resultado de una operación de punto flotante es demasiado pequeña para el formato de destino, el resultado de la operación se convierte en cero positivo o negativo cero.
- Si la magnitud del resultado de una operación de punto flotante es demasiado grande para el formato de destino, el resultado de la operación se convierte en infinito positivo o infinito negativo.
- Si una operación de punto flotante no es válida, el resultado de la operación se convierte en NaN.
- Si uno o los dos operandos de una operación de punto flotante son NaN, el resultado de la operación se convierte en NaN.
Las operaciones de punto flotante se pueden realizar con mayor precisión que el tipo de resultado de la operación. Para forzar un valor de un tipo de punto flotante a la precisión exacta de su tipo, se puede usar una conversión explícita (§12.9.7).
Ejemplo: algunas arquitecturas de hardware admiten un tipo de punto flotante "extendido" o "long double" con mayor rango y precisión que el
double
tipo, y realizan implícitamente todas las operaciones de punto flotante con este tipo de precisión superior. Solo a un costo excesivo en el rendimiento se pueden realizar arquitecturas de hardware para realizar operaciones de punto flotante con menos precisión y, en lugar de requerir una implementación para perder el rendimiento y la precisión, C# permite usar un tipo de precisión superior para todas las operaciones de punto flotante. Aparte de ofrecer resultados más precisos, esto rara vez tiene efectos medibles. Sin embargo, en expresiones del formulariox * y / z
, donde la multiplicación genera un resultado que está fuera deldouble
intervalo, pero la división posterior devuelve el resultado temporal aldouble
intervalo, el hecho de que la expresión se evalúa en un formato de rango superior puede provocar que se genere un resultado finito en lugar de un infinito. ejemplo final
8.3.8 El tipo decimal
El tipo decimal
es un tipo de datos de 128 bits adecuado para cálculos financieros y monetarios. El decimal
tipo puede representar valores incluidos los del intervalo al menos -7,9 × 10⁻²⁸ a 7,9 × 10²⁸, con al menos una precisión de 28 dígitos.
El conjunto finito de valores de tipo decimal
es de la forma (–1)v × c × 10⁻e, donde el signo v es 0 o 1, el coeficiente c se da por 0 ≤ c<Cmax, y la escala e es tal que Emin ≤ e ≤ Emax, donde Cmax es al menos 1 × 10²⁸, Emin ≤ 0, y Emax ≥ 28. El decimal
tipo no admite necesariamente ceros, infinidades o naN firmados.
Un decimal
objeto se representa como un entero escalado por una potencia de diez. Para decimal
s con un valor absoluto menor que 1.0m
, el valor es exacto para al menos la posición decimal 28. Para decimal
s con un valor absoluto mayor o igual que 1.0m
, el valor es exacto a al menos 28 dígitos. Al contrario que los float
tipos de datos y double
, los números fraccionarios decimales como 0.1
se pueden representar exactamente en la representación decimal. En las float
representaciones y double
, estos números suelen tener expansiones binarias sin terminación, lo que hace que esas representaciones sean más propensas a errores de redondeo.
Si alguno de los operandos de un operador binario es de decimal
tipo , se aplican promociones numéricas estándar, como se detalla en §12.4.7 y la operación se realiza con double
precisión.
El resultado de una operación en valores de tipo decimal
es que el resultado sería calcular un resultado exacto (conservar la escala, según se define para cada operador) y, a continuación, redondear para ajustarse a la representación. Los resultados se redondean al valor representable más cercano y, cuando un resultado está igualmente cerca de dos valores representables, al valor que tiene un número par en la posición de dígito menos significativo (esto se conoce como "redondeo del banco"). Es decir, los resultados son exactos para al menos el 28º decimal. Tenga en cuenta que el redondeo puede producir un valor cero a partir de un valor distinto de cero.
Si una decimal
operación aritmética produce un resultado cuya magnitud es demasiado grande para el decimal
formato, se produce una System.OverflowException
excepción .
El decimal
tipo tiene mayor precisión, pero puede tener un intervalo menor que los tipos de punto flotante. Por lo tanto, las conversiones de los tipos de punto flotante a decimal
podrían producir excepciones de desbordamiento y las conversiones de decimal
a los tipos de punto flotante podrían provocar la pérdida de excepciones de precisión o desbordamiento. Por estos motivos, no existen conversiones implícitas entre los tipos de punto flotante y decimal
, y sin conversiones explícitas, se produce un error en tiempo de compilación cuando los operandos y decimal
de punto flotante se mezclan directamente en la misma expresión.
8.3.9 El tipo Bool
El bool
tipo representa cantidades lógicas booleanas. Los valores posibles de tipo bool
son true
y false
. La representación de false
se describe en §8.3.3. Aunque la representación de true
no está especificada, será diferente de la de false
.
No existen conversiones estándar entre bool
y otros tipos de valor. En concreto, el bool
tipo es distinto y independiente de los tipos enteros, no se puede usar un bool
valor en lugar de un valor entero y viceversa.
Nota: En los lenguajes C y C++, un valor entero o de punto flotante cero, o un puntero nulo se puede convertir en el valor booleano , y un valor entero o de punto flotante distinto de cero, o un puntero que no sea NULL se puede convertir al valor
false
true
booleano . En C#, estas conversiones se realizan comparando explícitamente un valor entero o de punto flotante con cero, o comparando explícitamente una referencia de objeto anull
. nota final
8.3.10 Tipos de enumeración
Un tipo de enumeración es un tipo distinto con constantes con nombre. Cada tipo de enumeración tiene un tipo subyacente, que debe ser , , ushort
, , int
, uint
o ulong
long
. short
sbyte
byte
El conjunto de valores del tipo de enumeración es el mismo que el conjunto de valores del tipo subyacente. Los valores del tipo de enumeración no están restringidos a los valores de las constantes con nombre. Los tipos de enumeración se definen mediante declaraciones de enumeración (§19.2).
8.3.11 Tipos de tupla
Un tipo de tupla representa una secuencia ordenada de valores de longitud fija con nombres opcionales y tipos individuales. El número de elementos de un tipo de tupla se conoce como aridad. Un tipo de tupla se escribe (T1 I1, ..., Tn In)
con n ≥ 2, donde los identificadores son nombres de elementos de tupla opcionales I1...In
.
Esta sintaxis es abreviada para un tipo construido con los tipos T1...Tn
de System.ValueTuple<...>
, que será un conjunto de tipos de estructura genéricos capaces de expresar directamente los tipos de tupla de cualquier aridad entre dos y siete inclusive.
No es necesario existir una System.ValueTuple<...>
declaración que coincida directamente con la aridad de cualquier tipo de tupla con un número correspondiente de parámetros de tipo. En su lugar, las tuplas con una aridad mayor que siete se representan con un tipo System.ValueTuple<T1, ..., T7, TRest>
de estructura genérico que, además de los elementos de tupla, tiene un Rest
campo que contiene un valor anidado de los elementos restantes, utilizando otro System.ValueTuple<...>
tipo. Este anidamiento puede ser observable de varias maneras, por ejemplo, a través de la presencia de un Rest
campo. Cuando solo se requiere un único campo adicional, se usa el tipo System.ValueTuple<T1>
de estructura genérico; este tipo no se considera un tipo de tupla en sí mismo. Cuando se requieren más de siete campos adicionales, System.ValueTuple<T1, ..., T7, TRest>
se usa de forma recursiva.
Los nombres de elementos dentro de un tipo de tupla deben ser distintos. Un nombre de elemento de tupla del formulario ItemX
, donde X
es cualquier secuencia de0
dígitos decimales no iniciados que podrían representar la posición de un elemento de tupla, solo se permite en la posición indicada por X
.
Los nombres de elementos opcionales no se representan en los ValueTuple<...>
tipos y no se almacenan en la representación en tiempo de ejecución de un valor de tupla. Las conversiones de identidad (§10.2.2) existen entre tuplas con secuencias convertibles de identidad de tipos de elementos.
El new
operador §12.8.17.2 no se puede aplicar con la sintaxis new (T1, ..., Tn)
de tipo de tupla . Los valores de tupla se pueden crear a partir de expresiones de tupla (§12.8.6) o aplicando el new
operador directamente a un tipo construido a partir de ValueTuple<...>
.
Los elementos de tupla son campos públicos con los nombres Item1
, Item2
, etc., y se puede acceder a ellos a través de un acceso de miembro en un valor de tupla (§12.8.7). Además, si el tipo de tupla tiene un nombre para un elemento determinado, ese nombre se puede usar para tener acceso al elemento en cuestión.
Nota: Incluso cuando las tuplas grandes se representan con valores anidados
System.ValueTuple<...>
, todavía se puede acceder a cada elemento de tupla directamente con elItem...
nombre correspondiente a su posición. nota final
Ejemplo: dados los ejemplos siguientes:
(int, string) pair1 = (1, "One"); (int, string word) pair2 = (2, "Two"); (int number, string word) pair3 = (3, "Three"); (int Item1, string Item2) pair4 = (4, "Four"); // Error: "Item" names do not match their position (int Item2, string Item123) pair5 = (5, "Five"); (int, string) pair6 = new ValueTuple<int, string>(6, "Six"); ValueTuple<int, string> pair7 = (7, "Seven"); Console.WriteLine($"{pair2.Item1}, {pair2.Item2}, {pair2.word}");
Los tipos de tupla para
pair1
,pair2
ypair3
son válidos, con nombres para no, algunos o todos los elementos de tipo de tupla.El tipo de tupla para
pair4
es válido porque los nombresItem1
yItem2
coinciden con sus posiciones, mientras que el tipo de tupla parapair5
no se permite, porque los nombresItem2
yItem123
no.Las declaraciones de
pair6
ypair7
demuestran que los tipos de tupla son intercambiables con tipos construidos del formularioValueTuple<...>
y que elnew
operador se permite con la última sintaxis.La última línea muestra que se puede tener acceso a los elementos de tupla por el
Item
nombre correspondiente a su posición, así como por el nombre del elemento de tupla correspondiente, si está presente en el tipo. ejemplo final
8.3.12 Tipos de valor que aceptan valores NULL
Un tipo de valor que acepta valores NULL puede representar todos los valores de su tipo subyacente más un valor NULL adicional. Se escribe T?
un tipo de valor que acepta valores NULL, donde T
es el tipo subyacente. Esta sintaxis es abreviada para System.Nullable<T>
y los dos formularios se pueden usar indistintamente.
Por el contrario, un tipo de valor que no acepta valores NULL es cualquier tipo distinto System.Nullable<T>
de y su abreviatura T?
(para cualquier ), además de cualquier T
parámetro de tipo restringido para que sea un tipo de valor que no acepta valores NULL (es decir, cualquier parámetro de tipo con una restricción de tipo de valor (§15.2.5)). El System.Nullable<T>
tipo especifica la restricción de tipo de valor para T
, lo que significa que el tipo subyacente de un tipo de valor que acepta valores NULL puede ser cualquier tipo de valor que no acepta valores NULL. El tipo subyacente de un tipo de valor que acepta valores NULL no puede ser un tipo de valor que acepta valores NULL ni un tipo de referencia. Por ejemplo, int??
es un tipo no válido. Los tipos de referencia que aceptan valores NULL se tratan en §8.9.
Una instancia de un tipo T?
de valor que acepta valores NULL tiene dos propiedades públicas de solo lectura:
- Propiedad
HasValue
de tipobool
- Propiedad
Value
de tipoT
Una instancia para la que se dice que HasValue
no true
es NULL. Una instancia que no es null contiene un valor conocido y Value
devuelve ese valor.
Instancia para la que se false
dice que HasValue
es null. Una instancia nula tiene un valor indefinido. Si se intenta leer la Value
de una instancia nula, se produce una System.InvalidOperationException
excepción . El proceso de acceso a la propiedad Value de una instancia que acepta valores NULL se conoce como desencapsulado.
Además del constructor predeterminado, cada tipo T?
de valor que acepta valores NULL tiene un constructor público con un único parámetro de tipo T
. Dado un valor x
de tipo T
, una invocación de constructor del formulario
new T?(x)
crea una instancia que no es NULL de T?
para la que la Value
propiedad es x
. El proceso de creación de una instancia que no es NULL de un tipo de valor que acepta valores NULL para un valor determinado se conoce como ajuste.
Las conversiones implícitas están disponibles desde el null
literal a T?
(§10.2.7) y de T
a T?
(§10.2.6).
El tipo T?
de valor que acepta valores NULL no implementa interfaces (§18). En concreto, esto significa que no implementa ninguna interfaz que haga el tipo T
subyacente.
8.3.13 Boxing y unboxing
El concepto de conversión boxing y unboxing proporciona un puente entre value_types y reference_types al permitir que cualquier valor de un value_type se convierta en y desde el tipo object
. Boxing y unboxing habilitan una vista unificada del sistema de tipos en la que, en última instancia, un valor de cualquier tipo se puede tratar como .object
Boxing se describe con más detalle en §10.2.9 y la unboxing se describe en §10.3.7.
8.4 Tipos construidos
8.4.1 General
Una declaración de tipo genérico, por sí misma, denota un tipo genérico sin enlazar que se usa como "plano técnico" para formar muchos tipos diferentes, mediante la aplicación de argumentos de tipo. Los argumentos de tipo se escriben entre corchetes angulares (<
y >
) inmediatamente después del nombre del tipo genérico. Un tipo que incluye al menos un argumento de tipo se denomina tipo construido. Un tipo construido se puede usar en la mayoría de los lugares del idioma en el que puede aparecer un nombre de tipo. Un tipo genérico no enlazado solo se puede usar dentro de un typeof_expression (§12.8.18).
Los tipos construidos también se pueden usar en expresiones como nombres simples (§12.8.4) o al acceder a un miembro (§12.8.7).
Cuando se evalúa un namespace_or_type_name , solo se tienen en cuenta los tipos genéricos con el número correcto de parámetros de tipo. Por lo tanto, es posible usar el mismo identificador para identificar distintos tipos, siempre y cuando los tipos tengan diferentes números de parámetros de tipo. Esto resulta útil al mezclar clases genéricas y no genéricas en el mismo programa.
Ejemplo:
namespace Widgets { class Queue {...} class Queue<TElement> {...} } namespace MyApplication { using Widgets; class X { Queue q1; // Non-generic Widgets.Queue Queue<int> q2; // Generic Widgets.Queue } }
ejemplo final
Las reglas detalladas para la búsqueda de nombres en las producciones de namespace_or_type_name se describen en §7.8. La resolución de ambigüedades en estas producciones se describe en §6.2.5. Un type_name podría identificar un tipo construido aunque no especifique directamente parámetros de tipo. Esto puede ocurrir cuando un tipo está anidado dentro de una declaración genérica class
y el tipo de instancia de la declaración contenedora se usa implícitamente para la búsqueda de nombres (§15.3.9.7).
Ejemplo:
class Outer<T> { public class Inner {...} public Inner i; // Type of i is Outer<T>.Inner }
ejemplo final
No se usará un tipo construido distinto de enumeración como unmanaged_type (§8.8).
8.4.2 Argumentos de tipo
Cada argumento de una lista de argumentos de tipo es simplemente un tipo.
type_argument_list
: '<' type_arguments '>'
;
type_arguments
: type_argument (',' type_argument)*
;
type_argument
: type
| type_parameter nullable_type_annotation?
;
Cada argumento de tipo cumplirá cualquier restricción en el parámetro de tipo correspondiente (§15.2.5). Argumento de tipo de referencia cuya nulabilidad no coincide con la nulabilidad del parámetro de tipo satisface la restricción; sin embargo, se puede emitir una advertencia.
8.4.3 Tipos abiertos y cerrados
Todos los tipos se pueden clasificar como tipos abiertos o tipos cerrados. Un tipo abierto es un tipo que implica parámetros de tipo. Más concretamente:
- Un parámetro de tipo define un tipo abierto.
- Un tipo de matriz es un tipo abierto si y solo si su tipo de elemento es un tipo abierto.
- Un tipo construido es un tipo abierto si y solo si uno o varios de sus argumentos de tipo es un tipo abierto. Un tipo anidado construido es un tipo abierto si y solo si uno o varios de sus argumentos de tipo o los argumentos de tipo de sus tipos contenedores son un tipo abierto.
Un tipo cerrado es un tipo que no es un tipo abierto.
En tiempo de ejecución, todo el código dentro de una declaración de tipo genérico se ejecuta en el contexto de un tipo construido cerrado que se creó aplicando argumentos de tipo a la declaración genérica. Cada parámetro de tipo dentro del tipo genérico está enlazado a un tipo en tiempo de ejecución determinado. El procesamiento en tiempo de ejecución de todas las instrucciones y expresiones siempre se produce con tipos cerrados y los tipos abiertos solo se producen durante el procesamiento en tiempo de compilación.
Dos tipos construidos cerrados son convertibles de identidad (§10.2.2) si se construyen a partir del mismo tipo genérico sin enlazar y existe una conversión de identidad entre cada uno de sus argumentos de tipo correspondientes. Los argumentos de tipo correspondientes se pueden cerrar tipos o tuplas construidos que son convertibles de identidad. Los tipos construidos cerrados que son convertibles de identidad comparten un único conjunto de variables estáticas. De lo contrario, cada tipo construido cerrado tiene su propio conjunto de variables estáticas. Dado que un tipo abierto no existe en tiempo de ejecución, no hay variables estáticas asociadas a un tipo abierto.
8.4.4 Tipos enlazados y sin enlazar
El término unbound type hace referencia a un tipo no genérico o a un tipo genérico sin enlazar. El término tipo enlazado hace referencia a un tipo no genérico o a un tipo construido.
Un tipo sin enlazar hace referencia a la entidad declarada por una declaración de tipo. Un tipo genérico no enlazado no es un tipo y no se puede usar como el tipo de una variable, argumento o valor devuelto, o como un tipo base. La única construcción en la que se puede hacer referencia a un tipo genérico sin enlazar es la typeof
expresión (§12.8.18).
8.4.5 Restricciones satisfactorias
Cada vez que se hace referencia a un tipo construido o método genérico, los argumentos de tipo proporcionados se comprueban con las restricciones de parámetro de tipo declaradas en el tipo o método genéricos (§15.2.5). Para cada where
cláusula, el argumento A
type que corresponde al parámetro de tipo con nombre se comprueba con cada restricción de la siguiente manera:
- Si la restricción es un
class
tipo, un tipo de interfaz o un parámetro de tipo, deje representarC
esa restricción con los argumentos de tipo proporcionados sustituidos por los parámetros de tipo que aparecen en la restricción. Para satisfacer la restricción, será el caso de que el tipoA
sea convertible al tipoC
por uno de los siguientes: - Si la restricción es la restricción de tipo de referencia (
class
), el tipoA
cumplirá uno de los siguientes requisitos:A
es un tipo de interfaz, un tipo de clase, un tipo delegado, un tipo de matriz o el tipo dinámico.
Nota:
System.ValueType
ySystem.Enum
son tipos de referencia que cumplen esta restricción. nota finalA
es un parámetro de tipo que se sabe que es un tipo de referencia (§8.2).
- Si la restricción es la restricción de tipo de valor (
struct
), el tipoA
cumplirá uno de los siguientes requisitos:A
es unstruct
tipo oenum
tipo, pero no un tipo de valor que acepta valores NULL.
Nota:
System.ValueType
ySystem.Enum
son tipos de referencia que no cumplen esta restricción. nota finalA
es un parámetro de tipo que tiene la restricción de tipo de valor (§15.2.5).
- Si la restricción es la restricción
new()
constructor , el tipoA
no debe serabstract
y tendrá un constructor sin parámetros público. Esto se cumple si se cumple una de las siguientes condiciones:A
es un tipo de valor, ya que todos los tipos de valor tienen un constructor predeterminado público (§8.3.3).A
es un parámetro de tipo que tiene la restricción de constructor (§15.2.5).A
es un parámetro de tipo que tiene la restricción de tipo de valor (§15.2.5).A
es unclass
que no es abstracto y contiene un constructor público declarado explícitamente sin parámetros.A
noabstract
es y tiene un constructor predeterminado (§15.11.5).
Se produce un error en tiempo de compilación si uno o varios de los argumentos de tipo especificados no cumplen las restricciones de un parámetro de tipo.
Dado que los parámetros de tipo no se heredan, tampoco se heredan restricciones.
Ejemplo: En lo siguiente,
D
debe especificar la restricción en su parámetroT
de tipo para queT
satisfaga la restricción impuesta por la baseclass
B<T>
. Por el contrario,class
E
no es necesario especificar una restricción, ya queList<T>
implementaIEnumerable
para cualquierT
.class B<T> where T: IEnumerable {...} class D<T> : B<T> where T: IEnumerable {...} class E<T> : B<List<T>> {...}
ejemplo final
8.5 Parámetros de tipo
Un parámetro de tipo es un identificador que designa un tipo de valor o un tipo de referencia al que está enlazado el parámetro en tiempo de ejecución.
type_parameter
: identifier
;
Dado que se puede crear una instancia de un parámetro de tipo con muchos argumentos de tipo diferentes, los parámetros de tipo tienen operaciones y restricciones ligeramente diferentes que otros tipos.
Nota: Estas incluyen:
- Un parámetro de tipo no se puede usar directamente para declarar una clase base (§15.2.4.2) ni una interfaz (§18.2.4).
- Las reglas para la búsqueda de miembros en parámetros de tipo dependen de las restricciones, si las hay, aplicadas al parámetro de tipo. Se detallan en §12.5.
- Las conversiones disponibles para un parámetro de tipo dependen de las restricciones, si las hay, aplicadas al parámetro de tipo. Se detallan en §10.2.12 y §10.3.8.
- El literal
null
no se puede convertir en un tipo dado por un parámetro de tipo, excepto si se sabe que el parámetro de tipo es un tipo de referencia (§10.2.12). Sin embargo, se puede usar en su lugar una expresión predeterminada (§12.8.21). Además, un valor con un tipo proporcionado por un parámetro de tipo se puede comparar con null mediante==
y!=
(§12.12.7), a menos que el parámetro de tipo tenga la restricción de tipo de valor.- Una
new
expresión (§12.8.17.2) solo se puede usar con un parámetro de tipo si el parámetro de tipo está restringido por un constructor_constraint o la restricción de tipo de valor (§15.2.5).- Un parámetro de tipo no se puede usar en ningún lugar dentro de un atributo.
- No se puede usar un parámetro de tipo en un acceso de miembro (§12.8.7) o en un nombre de tipo (§7.8) para identificar un miembro estático o un tipo anidado.
- No se puede usar un parámetro de tipo como unmanaged_type (§8.8).
nota final
Como tipo, los parámetros de tipo son puramente una construcción en tiempo de compilación. En tiempo de ejecución, cada parámetro de tipo está enlazado a un tipo en tiempo de ejecución que se especificó proporcionando un argumento de tipo a la declaración de tipo genérico. Por lo tanto, el tipo de una variable declarada con un parámetro de tipo será, en tiempo de ejecución, un tipo construido cerrado §8.4.3. La ejecución en tiempo de ejecución de todas las instrucciones y expresiones que implican parámetros de tipo usa el tipo que se proporcionó como argumento de tipo para ese parámetro.
8.6 Tipos de árbol de expresiones
Los árboles de expresión permiten representar expresiones lambda como estructuras de datos en lugar de código ejecutable. Los árboles de expresión son valores de tipos de árbol de expresión del formulario System.Linq.Expressions.Expression<TDelegate>
, donde TDelegate
es cualquier tipo de delegado. Para el resto de esta especificación, estos tipos se harán referencia al uso de la abreviatura Expression<TDelegate>
.
Si existe una conversión de una expresión lambda a un tipo D
delegado , también existe una conversión al tipo Expression<TDelegate>
de árbol de expresión . Mientras que la conversión de una expresión lambda a un tipo delegado genera un delegado que hace referencia al código ejecutable para la expresión lambda, la conversión a un tipo de árbol de expresión crea una representación de árbol de expresión de la expresión lambda. En §10.7.3 se proporcionan más detalles de esta conversión.
Ejemplo: el siguiente programa representa una expresión lambda como código ejecutable y como árbol de expresiones. Dado que existe una conversión en
Func<int,int>
, también existe una conversión enExpression<Func<int,int>>
:Func<int,int> del = x => x + 1; // Code Expression<Func<int,int>> exp = x => x + 1; // Data
Después de estas asignaciones, el delegado
del
hace referencia a un método que devuelvex + 1
y la expresión tree exp hace referencia a una estructura de datos que describe la expresiónx => x + 1
.ejemplo final
Expression<TDelegate>
proporciona un método Compile
de instancia que genera un delegado de tipo TDelegate
:
Func<int,int> del2 = exp.Compile();
Invocar este delegado hace que se ejecute el código representado por el árbol de expresiones. Por lo tanto, dadas las definiciones anteriores, del
y del2
son equivalentes, y las dos instrucciones siguientes tendrán el mismo efecto:
int i1 = del(1);
int i2 = del2(1);
Después de ejecutar este código y i1
i2
tendrá el valor 2
.
La superficie de API proporcionada por Expression<TDelegate>
es la implementación definida más allá del requisito de un Compile
método descrito anteriormente.
Nota: Aunque los detalles de la API proporcionada para los árboles de expresión están definidos por la implementación, se espera que una implementación:
- Habilite el código para inspeccionar y responder a la estructura de un árbol de expresión creado como resultado de una conversión de una expresión lambda.
- Habilitación de árboles de expresión para crearse mediante programación en el código de usuario
nota final
8.7 El tipo dinámico
El tipo dynamic
usa el enlace dinámico, como se describe en detalle en §12.3.2, en lugar de en el enlace estático que usan todos los demás tipos.
El tipo dynamic
se considera idéntico a object
excepto en los siguientes aspectos:
- Las operaciones en expresiones de tipo
dynamic
se pueden enlazar dinámicamente (§12.3.3). - La inferencia de tipos (§12.6.3) preferirá
dynamic
siobject
ambas son candidatas. dynamic
no se puede usar como- el tipo de un object_creation_expression (§12.8.17.2)
- un class_base (§15.2.4)
- un predefined_type en un member_access (§12.8.7.1)
- operando del
typeof
operador - un argumento de atributo
- una restricción
- un tipo de método de extensión
- cualquier parte de un argumento de tipo dentro de struct_interfaces (§16.2.5) o interface_type_list (§15.2.4.1).
Debido a esta equivalencia, se mantiene lo siguiente:
- Hay una conversión de identidad implícita
- entre
object
ydynamic
- entre tipos construidos que son los mismos al reemplazar por
dynamic
object
- entre tipos de tupla que son iguales al reemplazar por
dynamic
object
- entre
- Las conversiones implícitas y explícitas a y desde
object
también se aplican a y desdedynamic
. - Las firmas que son las mismas al reemplazar
dynamic
porobject
se consideran la misma firma. - El tipo
dynamic
es indistinguible del tipoobject
en tiempo de ejecución. - Una expresión del tipo
dynamic
se conoce como una expresión dinámica.
8.8 Tipos no administrados
unmanaged_type
: value_type
| pointer_type // unsafe code support
;
Un unmanaged_type es cualquier tipo que no sea un reference_type, un type_parameter o un tipo construido y no contenga campos de instancia cuyo tipo no sea un unmanaged_type. En otras palabras, un unmanaged_type es uno de los siguientes:
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
char
double
float
, , o .decimal
bool
- Cualquier enum_type.
- Cualquier struct_type definido por el usuario que no sea un tipo construido y contenga campos de instancia de solo unmanaged_type.
- En código no seguro (§23.2), cualquier pointer_type (§23.3).
8.9 Tipos de referencia y nulabilidad
8.9.1 General
Un tipo de referencia que acepta valores NULL se indica anexando un nullable_type_annotation (?
) a un tipo de referencia que no acepta valores NULL. No hay ninguna diferencia semántica entre un tipo de referencia que no acepta valores NULL y su tipo que acepta valores NULL correspondientes, ambos pueden ser una referencia a un objeto o null
. La presencia o ausencia del nullable_type_annotation declara si una expresión está pensada para permitir valores NULL o no. Un compilador puede proporcionar diagnósticos cuando no se usa una expresión según esa intención. El estado NULL de una expresión se define en §8.9.5. Existe una conversión de identidad entre un tipo de referencia que acepta valores NULL y su correspondiente tipo de referencia que no acepta valores NULL (§10.2.2).
Hay dos formas de nulabilidad para los tipos de referencia:
- nullable: se puede asignar
null
un tipo de referencia que acepta valores NULL. Su estado null predeterminado es tal vez null. - que no acepta valores NULL: no se debe asignar un valor a una
null
referencia que no acepta valores NULL. Su estado null predeterminado no es null.
Nota: Los tipos
R
yR?
se representan mediante el mismo tipo subyacente,R
. Una variable de ese tipo subyacente puede contener una referencia a un objeto o ser el valornull
, que indica "sin referencia". nota final
La distinción sintáctica entre un tipo de referencia que acepta valores NULL y su correspondiente tipo de referencia que no acepta valores NULL permite que un compilador genere diagnósticos. Un compilador debe permitir el nullable_type_annotation tal como se define en §8.2.1. Los diagnósticos deben limitarse a las advertencias. Ni la presencia o ausencia de anotaciones que aceptan valores NULL ni el estado del contexto que acepta valores NULL pueden cambiar el tiempo de compilación o el comportamiento en tiempo de ejecución de un programa, excepto los cambios en los mensajes de diagnóstico generados en tiempo de compilación.
8.9.2 Tipos de referencia que no aceptan valores NULL
Un tipo de referencia que no acepta valores NULL es un tipo de referencia del formulario T
, donde T
es el nombre del tipo. El estado null predeterminado de una variable que no acepta valores NULL no es NULL. Es posible que se generen advertencias cuando se usa una expresión que es tal vez null cuando se requiere un valor no NULL .
8.9.3 Tipos de referencia que aceptan valores NULL
Un tipo de referencia del formulario T?
(como string?
) es un tipo de referencia que acepta valores NULL. El estado null predeterminado de una variable que acepta valores NULL puede ser NULL. La anotación ?
indica la intención de que las variables de este tipo aceptan valores NULL. El compilador puede reconocer estas intenciones para emitir advertencias. Cuando el contexto de anotación que acepta valores NULL está deshabilitado, el uso de esta anotación puede generar una advertencia.
8.9.4 Contexto que acepta valores NULL
8.9.4.1 General
Cada línea de código fuente tiene un contexto que acepta valores NULL. Las anotaciones y advertencias marcan para las anotaciones que aceptan valores NULL (§8.9.4.3) y las advertencias que aceptan valores NULL (§8.9.4.4), respectivamente. Cada marca se puede habilitar o deshabilitar. El compilador puede usar el análisis de flujo estático para determinar el estado NULL de cualquier variable de referencia. El estado null de una variable de referencia (§8.9.5) no es null, tal vez null o tal vez predeterminado.
El contexto que acepta valores NULL puede especificarse en el código fuente a través de directivas que aceptan valores NULL (§6.5.9) o a través de algún mecanismo específico de la implementación externo al código fuente. Si se usan ambos enfoques, las directivas que aceptan valores NULL reemplazan la configuración realizada a través de un mecanismo externo.
El estado predeterminado del contexto que acepta valores NULL es la implementación definida.
A lo largo de esta especificación, se supone que todo el código de C# que no contiene directivas que aceptan valores NULL o sobre las que no se realiza ninguna instrucción con respecto al estado de contexto que acepta valores NULL actual, se supone que se ha compilado mediante un contexto que acepta valores NULL donde se habilitan las anotaciones y las advertencias.
Nota: Un contexto que acepta valores NULL donde ambas marcas están deshabilitadas coinciden con el comportamiento estándar anterior para los tipos de referencia. nota final
Deshabilitación que acepta valores NULL 8.9.4.2
Cuando las marcas de advertencia y anotaciones están deshabilitadas, el contexto que acepta valores NULL está deshabilitado.
Cuando el contexto que acepta valores NULL está deshabilitado:
- No se generará ninguna advertencia cuando se inicialice una variable de un tipo de referencia no anotado con o se le asigne un valor de .
null
- No se generará ninguna advertencia cuando una variable de un tipo de referencia que posiblemente tenga el valor NULL.
- Para cualquier tipo
T
de referencia , la anotación?
deT?
genera un mensaje y el tipoT?
es el mismo queT
. - Para cualquier restricción
where T : C?
de parámetro de tipo , la anotación?
deC?
genera un mensaje y el tipoC?
es el mismo queC
. - Para cualquier restricción
where T : U?
de parámetro de tipo , la anotación?
deU?
genera un mensaje y el tipoU?
es el mismo queU
. - La restricción
class?
genérica genera un mensaje de advertencia. El parámetro type debe ser un tipo de referencia.Nota: Este mensaje se caracteriza como "informativo" en lugar de "advertencia", por lo que no se debe confundir con el estado de la configuración de advertencia que acepta valores NULL, que no está relacionada. nota final
- El operador
!
null-forgiving (§12.8.9) no tiene ningún efecto.
Ejemplo:
#nullable disable annotations string? s1 = null; // Informational message; ? is ignored string s2 = null; // OK; null initialization of a reference s2 = null; // OK; null assignment to a reference char c1 = s2[1]; // OK; no warning on dereference of a possible null; // throws NullReferenceException c1 = s2![1]; // OK; ! is ignored
ejemplo final
8.9.4.3 Anotaciones que aceptan valores NULL
Cuando la marca de advertencia está deshabilitada y la marca de anotaciones está habilitada, el contexto que acepta valores NULL es anotaciones.
Cuando el contexto que acepta valores NULL es anotaciones:
- Para cualquier tipo
T
de referencia , la anotación?
de indicaT?
que un tipo que acepta valores NULL, mientras queT?
el valor no anotadoT
no acepta valores NULL. - No se generan advertencias de diagnóstico relacionadas con la nulabilidad.
- El operador
!
null-forgiving (§12.8.9) puede modificar el estado nulo analizado de su operando y qué advertencias de diagnóstico en tiempo de compilación se generan.
Ejemplo:
#nullable disable warnings #nullable enable annotations string? s1 = null; // OK; ? makes s2 nullable string s2 = null; // OK; warnings are disabled s2 = null; // OK; warnings are disabled char c1 = s2[1]; // OK; warnings are disabled; throws NullReferenceException c1 = s2![1]; // No warnings
ejemplo final
8.9.4.4 Advertencias que aceptan valores NULL
Cuando la marca de advertencia está habilitada y la marca de anotaciones está deshabilitada, el contexto que acepta valores NULL es advertencias.
Cuando el contexto que acepta valores NULL es advertencias, un compilador puede generar diagnósticos en los casos siguientes:
- Se desreferencia una variable de referencia que se ha determinado que puede ser null.
- Una variable de referencia de un tipo que no acepta valores NULL se asigna a una expresión que puede ser NULL.
?
se usa para anotar un tipo de referencia que acepta valores NULL.- El operador
!
null-forgiving (§12.8.9) se usa para establecer el estado NULL de su operando en no null.
Ejemplo:
#nullable disable annotations #nullable enable warnings string? s1 = null; // OK; ? makes s2 nullable string s2 = null; // OK; null-state of s2 is "maybe null" s2 = null; // OK; null-state of s2 is "maybe null" char c1 = s2[1]; // Warning; dereference of a possible null; // throws NullReferenceException c1 = s2![1]; // The warning is suppressed
ejemplo final
8.9.4.5 Habilitación que acepta valores NULL
Cuando la marca de advertencia y la marca de anotaciones están habilitadas, se habilita el contexto que acepta valores NULL.
Cuando el contexto que acepta valores NULL está habilitado:
- Para cualquier tipo
T
de referencia , la anotación?
deT?
haceT?
que un tipo que acepta valores NULL, mientras que el valor no anotadoT
no acepta valores NULL. - El compilador puede usar el análisis de flujo estático para determinar el estado NULL de cualquier variable de referencia. Cuando se habilitan las advertencias que aceptan valores NULL, el estado NULL de una variable de referencia (§8.9.5) no es null, tal vez null o tal vez predeterminado y
- El operador
!
null-forgiving (§12.8.9) establece el estado NULL de su operando en no null. - El compilador puede emitir una advertencia si la nulabilidad de un parámetro de tipo no coincide con la nulabilidad de su argumento de tipo correspondiente.
8.9.5 Valores NULLabilities y estados NULL
Un compilador no es necesario para realizar ningún análisis estático ni para generar ninguna advertencia de diagnóstico relacionada con la nulabilidad.
El resto de esta subclausa es condicionalmente normativo.
Un compilador que genera advertencias de diagnóstico se ajusta a estas reglas.
Cada expresión tiene uno de los tres estadosNULL:
- tal vez null: el valor de la expresión puede evaluarse como NULL.
- tal vez el valor predeterminado: el valor de la expresión puede evaluarse como el valor predeterminado de ese tipo.
- not null: el valor de la expresión no es NULL.
El estado null predeterminado de una expresión viene determinado por su tipo y el estado de la marca de anotaciones cuando se declara:
- El estado null predeterminado de un tipo de referencia que acepta valores NULL es:
- Tal vez null cuando su declaración está en texto donde está habilitada la marca de anotaciones.
- No es NULL cuando su declaración está en texto donde se deshabilita la marca de anotaciones.
- El estado null predeterminado de un tipo de referencia que no acepta valores NULL no es NULL.
Nota: El estado tal vez predeterminado se usa con parámetros de tipo sin restricciones cuando el tipo es un tipo que no acepta valores NULL, como
string
y la expresióndefault(T)
es el valor NULL. Dado que null no está en el dominio para el tipo que no acepta valores NULL, el estado puede ser predeterminado. nota final
Se puede producir un diagnóstico cuando se inicializa o asigna una variable (§9.2.1) de un tipo de referencia que no acepta valores NULL o se asigna a una expresión que puede ser null cuando esa variable se declara en texto donde está habilitada la marca de anotación.
Ejemplo: considere el siguiente método donde un parámetro acepta valores NULL y ese valor se asigna a un tipo que no acepta valores NULL:
#nullable enable public class C { public void M(string? p) { // Assignment of maybe null value to non-nullable variable string s = p; } }
El compilador puede emitir una advertencia en la que el parámetro que podría ser NULL se asigna a una variable que no debe ser NULL. Si el parámetro está comprobado en null antes de la asignación, el compilador puede usarlo en su análisis de estado que acepta valores NULL y no emite una advertencia:
#nullable enable public class C { public void M(string? p) { if (p != null) { string s = p; // Use s } } }
ejemplo final
El compilador puede actualizar el estado NULL de una variable como parte de su análisis.
Ejemplo: el compilador puede optar por actualizar el estado en función de las instrucciones del programa:
#nullable enable public void M(string? p) { // p is maybe-null int length = p.Length; // p is not null. string s = p; if (s != null) { int l2 = s.Length; } // s is maybe null int l3 = s.Length; }
En el ejemplo anterior, el compilador puede decidir que después de la instrucción
int length = p.Length;
, el estado null dep
no es null. Si fuera null, esa instrucción habría producido unaNullReferenceException
excepción . Esto es similar al comportamiento si el código se había precedido deif (p == null) throw NullReferenceException();
, salvo que el código como escrito puede generar una advertencia, cuyo propósito es advertir de que se puede producir implícitamente una excepción.
Más adelante en el método , el código comprueba que s
no es una referencia nula. El estado null de puede cambiar a tal vez null después de s
que se cierre el bloque comprobado por NULL. El compilador puede deducir que s
es posiblemente null porque el código se escribió para suponer que podría haber sido null. Por lo general, cuando el código contiene una comprobación nula, el compilador puede deducir que el valor podría haber sido NULL.ejemplo final
Ejemplo: El compilador puede tratar una propiedad (§15.7) como una variable con estado o como descriptores de acceso get y set independientes (§15.7.3). En otras palabras, un compilador puede elegir si la escritura en una propiedad cambia el estado NULL de lectura de la propiedad o si la lectura de una propiedad cambia el estado NULL de esa propiedad.
class Test { private string? _field; public string? DisappearingProperty { get { string tmp = _field; _field = null; return tmp; } set { _field = value; } } static void Main() { var t = new Test(); if (t.DisappearingProperty != null) { int len = t.DisappearingProperty.Length; } } }
En el ejemplo anterior, el campo de respaldo de
DisappearingProperty
se establece en NULL cuando se lee. Sin embargo, un compilador puede suponer que leer una propiedad no cambia el estado NULL de esa expresión. ejemplo final
Fin del texto normativo condicional
ECMA C# draft specification