Compartir a través de


10 Conversiones

10.1 General

Una conversión hace que una expresión se convierta o se trate como de un tipo determinado; en el primer caso, una conversión puede implicar un cambio en la representación. Las conversiones pueden ser implícitas o explícitas y esto determina si se requiere una conversión explícita.

Ejemplo: por ejemplo, la conversión de tipo int a tipo long es implícita, por lo que las expresiones de tipo int se pueden tratar implícitamente como tipo long. La conversión opuesta, de tipo long a tipo int, es explícita y, por tanto, se requiere una conversión explícita.

int a = 123;
long b = a;      // implicit conversion from int to long
int c = (int) b; // explicit conversion from long to int

ejemplo final

Algunas conversiones se definen mediante el idioma. Los programas también pueden definir sus propias conversiones (§10.5).

Algunas conversiones del lenguaje se definen de expresiones a tipos, otras de tipos a tipos. Una conversión de un tipo se aplica a todas las expresiones que tienen ese tipo.

Ejemplo:

enum Color { Red, Blue, Green }

// The expression 0 converts implicitly to enum types
Color c0 = 0;

// Other int expressions need explicit conversion
Color c1 = (Color)1;

// Conversion from null expression (no type) to string
string x = null;

// Conversion from lambda expression to delegate type
Func<int, int> square = x => x * x;

ejemplo final

10.2 Conversiones implícitas

10.2.1 General

Las conversiones siguientes se clasifican como conversiones implícitas:

  • Conversiones de identidad (§10.2.2)
  • Conversiones numéricas implícitas (§10.2.3)
  • Conversiones de enumeración implícitas (§10.2.4)
  • Conversiones implícitas de cadenas interpoladas (§10.2.5)
  • Conversiones de referencia implícitas (§10.2.8)
  • Conversiones de boxeo (§10.2.9)
  • Conversiones dinámicas implícitas (§10.2.10)
  • Conversiones implícitas de parámetros de tipo (§10.2.12)
  • Conversiones implícitas de expresiones constantes (§10.2.11)
  • Conversiones implícitas definidas por el usuario (incluida la elevación) (§10.2.14)
  • Conversiones de funciones anónimas (§10.2.15)
  • Conversiones de grupo de métodos (§10.2.15)
  • Conversiones de literales NULL (§10.2.7)
  • Conversiones implícitas que aceptan valores NULL (§10.2.6)
  • Conversiones implícitas de tupla (§10.2.13)
  • Conversiones literales predeterminadas (§10.2.16)
  • Conversiones de inicio implícitas (§10.2.17)

Las conversiones implícitas pueden producirse en diversas situaciones, incluidas las invocaciones de miembro de función (§12.6.6), expresiones de conversión (§12.9.7) y asignaciones (§12.21).

Las conversiones implícitas predefinidas siempre se realizan correctamente y nunca hacen que se produzcan excepciones.

Nota: Las conversiones implícitas definidas por el usuario correctamente diseñadas también deben mostrar estas características. nota final

Para la conversión, los tipos object y dynamic son convertibles de identidad (§10.2.2).

Sin embargo, las conversiones dinámicas (§10.2.10) solo se aplican a expresiones de tipo dynamic (§8.2.4).

10.2.2 Conversión de identidad

Una conversión de identidad se convierte de cualquier tipo al mismo tipo o a un tipo equivalente en tiempo de ejecución. Una razón por la que existe esta conversión es que se puede decir que un tipo T o una expresión de tipo T se pueden convertir a T sí mismos. Existen las siguientes conversiones de identidad:

  • Entre T y T, para cualquier tipo T.
  • Entre T y T? para cualquier tipo Tde referencia .
  • Entre object y dynamic.
  • Entre todos los tipos de tupla con la misma aridad y el tipo construido ValueTuple<...> correspondiente, cuando existe una conversión de identidad entre cada par de tipos de elementos correspondientes.
  • Entre los tipos construidos a partir del mismo tipo genérico donde existe una conversión de identidad entre cada argumento de tipo correspondiente.

Ejemplo: a continuación se muestra la naturaleza recursiva de la tercera regla:

(int a , string b) t1 = (1, "two");
(int c, string d) t2 = (3, "four");

// Identity conversions exist between
// the types of t1, t2, and t3.
var t3 = (5, "six");
t3 = t2;
t2 = t1;

var t4 = (t1, 7);
var t5 = (t2, 8);

// Identity conversions exist between
// the types of t4, t5, and t6.
var t6 =((8, "eight"), 9);
t6 = t5;
t5 = t4;

Los tipos de tuplas t1t2 y t3 todos tienen dos elementos: un int seguido de .string Los tipos de elementos de tupla pueden usarse por tuplas, como en t4, t5y t6. Existe una conversión de identidad entre cada par de tipos de elementos correspondientes, incluidas las tuplas anidadas, por lo que existe una conversión de identidad entre los tipos de tuplas t4, t5y t6.

ejemplo final

Todas las conversiones de identidad son simétricas. Si existe una conversión de identidad desde T₁ a T₂, existe una conversión de identidad de T₂ a T₁. Dos tipos son la identidad convertible cuando existe una conversión de identidad entre dos tipos.

En la mayoría de los casos, una conversión de identidad no tiene ningún efecto en tiempo de ejecución. Sin embargo, dado que las operaciones de punto flotante se pueden realizar con una precisión mayor de la indicada por su tipo (§8.3.7), la asignación de sus resultados puede dar lugar a una pérdida de precisión y se garantiza que las conversiones explícitas reduzcan la precisión a lo que prescribe el tipo (§12.9.7).

10.2.3 Conversiones numéricas implícitas

Las conversiones numéricas implícitas son:

  • De sbyte a short, int, long, float, doubleo decimal.
  • De byte a short, ushort, , uintint, long, ulong, floatdoubleo decimal.
  • De short a int, long, float, doubleo decimal.
  • De ushort a int, , longuint, ulong, float, doubleo decimal.
  • De int a long, float, doubleo decimal.
  • De uint a long, ulong, float, doubleo decimal.
  • De long a float, doubleo decimal.
  • De ulong a float, doubleo decimal.
  • De char a ushort, int, , longuint, ulong, float, , doubleo decimal.
  • De float a double.

Las conversiones de , long uinto ulong a float y desde long into ulong a double pueden provocar una pérdida de precisión, pero nunca provocarán una pérdida de magnitud. El resto de conversiones numéricas implícitas nunca pierden información.

No hay conversiones implícitas predefinidas al char tipo, por lo que los valores de los otros tipos enteros no se convierten automáticamente en el char tipo.

10.2.4 Conversiones implícitas de enumeración

Una conversión de enumeración implícita permite convertir un constant_expression (§12.23) con cualquier tipo entero y el valor cero que se convertirá en cualquier enum_type y en cualquier nullable_value_type cuyo tipo subyacente sea un enum_type. En este último caso, la conversión se evalúa convirtiendo en el enum_type subyacente y ajustando el resultado (§8.3.12).

10.2.5 Conversiones implícitas de cadenas interpoladas

Una conversión de cadena interpolada implícita permite convertir una interpolated_string_expression (§12.8.3) en System.IFormattable o System.FormattableString (que implementa System.IFormattable). Cuando se aplica esta conversión, un valor de cadena no se compone de la cadena interpolada. En su lugar, se crea una instancia de System.FormattableString , como se describe más adelante en §12.8.3.

10.2.6 Conversiones implícitas que aceptan valores NULL

Las conversiones implícitas que aceptan valores NULL son las conversiones que aceptan valores NULL (§10.6.1) derivadas de conversiones predefinidas implícitas.

10.2.7 Conversiones literales null

Existe una conversión implícita del null literal a cualquier tipo de referencia o tipo de valor que acepta valores NULL. Esta conversión genera una referencia nula si el tipo de destino es un tipo de referencia o el valor NULL (§8.3.12) del tipo de valor que acepta valores NULL especificado.

10.2.8 Conversiones de referencia implícitas

Las conversiones de referencia implícitas son:

  • De cualquier reference_type a object y dynamic.
  • De cualquier class_type S a cualquier class_type T, se deriva S de T.
  • Desde cualquier class_type a cualquier interface_type S T , proporcionado S implementa .T
  • De cualquier interface_type S a cualquier interface_type T, se deriva S de T.
  • De un array_type S con un tipo Sᵢ de elemento a un array_type T con un tipo Tᵢde elemento , siempre que se cumplan todas las siguientes condiciones:
    • S y T solo difieren en el tipo de elemento. En otras palabras, S y T tienen el mismo número de dimensiones.
    • Existe una conversión de referencia implícita de Sᵢ a Tᵢ.
  • Desde un tipo S[] de matriz unidimensional a System.Collections.Generic.IList<T>, System.Collections.Generic.IReadOnlyList<T>y sus interfaces base, siempre que haya una identidad implícita o una conversión de referencia de S a T.
  • Desde cualquier array_type hacia System.Array y las interfaces que implementa.
  • Desde cualquier delegate_type hacia System.Delegate y las interfaces que implementa.
  • Desde el literal null (§6.4.5.7) a cualquier tipo de referencia.
  • De cualquier reference_type a un reference_type T si tiene una conversión implícita de identidad o referencia a un reference_type T₀ y T₀ tiene una conversión de identidad a T.
  • De cualquier reference_type a un tipo T de interfaz o delegado si tiene una conversión implícita de identidad o referencia a un tipo T₀ de interfaz o delegado y T₀ es convertible de varianza (§18.2.3.3) a T.
  • Conversiones implícitas que implican parámetros de tipo que se sabe que son tipos de referencia. Consulte §10.2.12 para obtener más información sobre las conversiones implícitas que implican parámetros de tipo.

Las conversiones de referencia implícitas son esas conversiones entre reference_types que se pueden demostrar que siempre se realizan correctamente y, por lo tanto, no requieren comprobaciones en tiempo de ejecución.

Las conversiones de referencia, implícitas o explícitas, nunca cambian la identidad referencial del objeto que se va a convertir.

Nota: En otras palabras, mientras que una conversión de referencia puede cambiar el tipo de la referencia, nunca cambia el tipo o valor del objeto al que se hace referencia. nota final

10.2.9 Conversiones de boxeo

Una conversión boxing permite convertir implícitamente un value_type en un reference_type. Existen las siguientes conversiones de conversión boxing:

  • De cualquier value_type al tipo object.
  • De cualquier value_type al tipo System.ValueType.
  • Desde cualquier enum_type al tipo System.Enum.
  • Desde cualquier non_nullable_value_type a cualquier interface_type implementada por el non_nullable_value_type.
  • Desde cualquier non_nullable_value_type a cualquier interface_type I de modo que haya una conversión boxing de la non_nullable_value_type a otra interface_type I₀ y I₀ tenga una conversión de identidad a I.
  • De cualquier non_nullable_value_type a cualquier interface_type I de modo que haya una conversión boxing de la non_nullable_value_type a otra interface_type I₀ , y I₀ es convertible de varianza (§18.2.3.3) a I.
  • Desde cualquier nullable_value_type a cualquier reference_type donde haya una conversión boxing del tipo subyacente del nullable_value_type al reference_type.
  • A partir de un parámetro de tipo que no se sabe que es un tipo de referencia a ningún tipo, de modo que la conversión se permita mediante §10.2.12.

La conversión boxing de un valor que no acepta valores NULL consiste en asignar una instancia de objeto y copiar el valor en esa instancia.

La conversión boxing de un valor de un nullable_value_type genera una referencia nula si es el valor null (HasValue es false) o el resultado de desencapsular y boxear el valor subyacente de lo contrario.

Nota: El proceso de conversión boxing se puede imaginar en términos de la existencia de una clase boxing para cada tipo de valor. Por ejemplo, considere la posibilidad de implementar una struct S interfaz I, con una clase boxing denominada S_Boxing.

interface I
{
    void M();
}

struct S : I
{
    public void M() { ... }
}

sealed class S_Boxing : I
{
    S value;

    public S_Boxing(S value)
    {
        this.value = value;
    }

    public void M()
    {
        value.M();
    }
}

La conversión boxing de un valor v de tipo S consiste ahora en ejecutar la expresión new S_Boxing(v) y devolver la instancia resultante como un valor del tipo de destino de la conversión. Por lo tanto, las instrucciones

S s = new S();
object box = s;

puede considerarse similar a:

S s = new S();
object box = new S_Boxing(s);

El tipo de conversión boxing imaginado descrito anteriormente no existe realmente. En su lugar, un valor boxed de tipo S tiene el tipo Sen tiempo de ejecución y una comprobación de tipo en tiempo de ejecución mediante el is operador con un tipo de valor como el operando derecho comprueba si el operando izquierdo es una versión boxed del operando derecho. Por ejemplo,

int i = 123;
object box = i;
if (box is int)
{
    Console.Write("Box contains an int");
}

generará lo siguiente:

Box contains an int

Una conversión boxing implica realizar una copia del valor que se va a boxear. Esto es diferente de una conversión de un reference_type al tipo object, en el que el valor sigue haciendo referencia a la misma instancia y simplemente se considera como el tipo objectmenos derivado . Por ejemplo, lo siguiente:

struct Point
{
    public int x, y;

    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

class A
{
    void M() 
    {
        Point p = new Point(10, 10);
        object box = p;
        p.x = 20;
        Console.Write(((Point)box).x);
    }
}

generará el valor 10 en la consola porque la operación de conversión boxing implícita que se produce en la asignación de p para box hacer que se copie el valor de p . Se había Point declarado en class su lugar, el valor 20 sería de salida porque p y box haría referencia a la misma instancia.

La analogía de una clase boxing no debe usarse como más que una herramienta útil para imaginar cómo funciona el boxeo conceptualmente. Hay numerosas diferencias sutiles entre el comportamiento descrito por esta especificación y el comportamiento que daría lugar a la implementación boxing de esta manera precisamente.

nota final

10.2.10 Conversiones dinámicas implícitas

Existe una conversión dinámica implícita de una expresión de tipo dinámica a cualquier tipo T. La conversión se enlaza dinámicamente §12.3.3, lo que significa que se buscará una conversión implícita en tiempo de ejecución desde el tipo en tiempo de ejecución de la expresión a T. Si no se encuentra ninguna conversión, se produce una excepción en tiempo de ejecución.

Esta conversión implícita aparentemente infringe los consejos a principios de §10.2 que una conversión implícita nunca debe provocar una excepción. Sin embargo, no es la propia conversión, sino la búsqueda de la conversión que provoca la excepción. El riesgo de excepciones en tiempo de ejecución es inherente al uso del enlace dinámico. Si no se desea el enlace dinámico de la conversión, la expresión se puede convertir primero en objecty, a continuación, al tipo deseado.

Ejemplo: a continuación se muestran conversiones dinámicas implícitas:

object o = "object";
dynamic d = "dynamic";
string s1 = o;         // Fails at compile-time – no conversion exists
string s2 = d;         // Compiles and succeeds at run-time
int i = d;             // Compiles but fails at run-time – no conversion exists

Las asignaciones a s2 y i ambas emplean conversiones dinámicas implícitas, donde el enlace de las operaciones se suspende hasta el tiempo de ejecución. En tiempo de ejecución, se buscan conversiones implícitas del tipo en tiempo de ejecución de (string) al tipo de ddestino. Se encuentra una conversión en string , pero no en int.

ejemplo final

10.2.11 Conversiones de expresiones constantes implícitas

Una conversión de expresión constante implícita permite las conversiones siguientes:

  • Un constant_expression (§12.23) de tipo int se puede convertir al tipo sbyte, , byteshort, ushort, uinto ulong, siempre que el valor del constant_expression esté dentro del intervalo del tipo de destino.
  • Un constant_expression de tipo long se puede convertir en el tipo ulong, siempre que el valor del constant_expression no sea negativo.

10.2.12 Conversiones implícitas que implican parámetros de tipo

Para un type_parameter T que se sabe que es un tipo de referencia (§15.2.5), existen las siguientes conversiones de referencia implícitas (§10.2.8):

  • De T a su clase Cbase efectiva , de T a cualquier clase base de Cy de T a cualquier interfaz implementada por C.
  • De T a un interface_type I de la Tinterfaz efectiva establecida y de T a cualquier interfaz base de I.
  • De T a un parámetro U de tipo proporcionado que T depende de U (§15.2.5).

    Nota: Dado T que se sabe que es un tipo de referencia, dentro del ámbito de T, el tipo en tiempo de ejecución de U siempre será un tipo de referencia, aunque U no se sepa que es un tipo de referencia en tiempo de compilación. nota final

  • Desde el literal nulo (§6.4.5.7) a T.

Para una type_parameter T que no se sabe que es un tipo de referencia §15.2.5, las siguientes conversiones que implican T se consideran conversiones de conversión boxing (§10.2.9) en tiempo de compilación. En tiempo de ejecución, si T es un tipo de valor, la conversión se ejecuta como una conversión boxing. En tiempo de ejecución, si T es un tipo de referencia, la conversión se ejecuta como conversión de referencia implícita o conversión de identidad.

  • De T a su clase Cbase efectiva , de T a cualquier clase base de Cy de T a cualquier interfaz implementada por C.

    Nota: C será uno de los tipos System.Object, System.ValueTypeo System.Enum (de lo contrario T , se sabe que es un tipo de referencia). nota final

  • De T a un interface_type I de la Tinterfaz efectiva establecida y de T a cualquier interfaz base de I.

Para un type_parameter T que no se sabe que es un tipo de referencia, hay una conversión implícita de T a un parámetro U de tipo proporcionado T depende de U. En tiempo de ejecución, si T es un tipo de valor y U es un tipo de referencia, la conversión se ejecuta como una conversión boxing. En tiempo de ejecución, si ambos T y U son tipos de valor, T y U son necesariamente el mismo tipo y no se realiza ninguna conversión. En tiempo de ejecución, si T es un tipo de referencia, es U necesariamente también un tipo de referencia y la conversión se ejecuta como conversión de referencia implícita o conversión de identidad (§15.2.5).

Existen otras conversiones implícitas para un parámetro Tde tipo determinado:

  • De a un tipo de T referencia si tiene una conversión implícita a un tipo S₀ de referencia y S₀ tiene una conversión de identidad a S.S En tiempo de ejecución, la conversión se ejecuta de la misma manera que la conversión a S₀.
  • De a un tipo de T interfaz si tiene una conversión implícita a un tipo I₀de interfaz y I₀ es variable de varianza a I (§18.2.3.3).I En tiempo de ejecución, si T es un tipo de valor, la conversión se ejecuta como una conversión boxing. De lo contrario, la conversión se ejecuta como una conversión de referencia implícita o conversión de identidad.

En todos los casos, las reglas garantizan que una conversión se ejecute como una conversión boxing si y solo si en tiempo de ejecución la conversión procede de un tipo de valor a un tipo de referencia.

10.2.13 Conversiones implícitas de tupla

Existe una conversión implícita de una expresión E de tupla a un tipo T de tupla si E tiene la misma aridad que T y existe una conversión implícita de cada elemento en E al tipo de elemento correspondiente en T. La conversión se realiza mediante la creación de una instancia del Ttipo correspondiente System.ValueTuple<...> y la inicialización de cada uno de sus campos en orden de izquierda a derecha mediante la evaluación de la expresión del elemento de tupla correspondiente de E, convirtiéndola en el tipo de elemento correspondiente de T utilizando la conversión implícita encontrada e inicializando el campo con el resultado.

Si un nombre de elemento de la expresión de tupla no coincide con un nombre de elemento correspondiente en el tipo de tupla, se emitirá una advertencia.

Ejemplo:

(int, string) t1 = (1, "One");
(byte, string) t2 = (2, null);
(int, string) t3 = (null, null);        // Error: No conversion
(int i, string s) t4 = (i: 4, "Four");
(int i, string) t5 = (x: 5, s: "Five"); // Warning: Names are ignored

Las declaraciones de t1, t2t4 y t5 son válidas, ya que existen conversiones implícitas de las expresiones de elemento a los tipos de elemento correspondientes. La declaración de t3 no es válida, porque no hay ninguna conversión de null a int. La declaración de t5 provoca una advertencia porque los nombres de elemento de la expresión de tupla difieren de los del tipo de tupla.

ejemplo final

10.2.14 Conversiones implícitas definidas por el usuario

Una conversión implícita definida por el usuario consta de una conversión implícita estándar opcional, seguida de la ejecución de un operador de conversión implícita definido por el usuario, seguido de otra conversión implícita estándar opcional. Las reglas exactas para evaluar las conversiones implícitas definidas por el usuario se describen en §10.5.4.

10.2.15 Conversiones de funciones anónimas y conversiones de grupo de métodos

Las funciones anónimas y los grupos de métodos no tienen tipos en y de sí mismos, pero se pueden convertir implícitamente en tipos delegados. Además, algunas expresiones lambda se pueden convertir implícitamente en tipos de árbol de expresiones. Las conversiones de funciones anónimas se describen con más detalle en §10.7 y conversiones de grupos de métodos en §10.8.

10.2.16 Conversiones literales predeterminadas

Existe una conversión implícita de un default_literal (§12.8.21) a cualquier tipo. Esta conversión genera el valor predeterminado (§9.3) del tipo inferido.

10.2.17 Conversiones implícitas de lanzamiento

Aunque las expresiones throw no tienen un tipo, pueden convertirse implícitamente en cualquier tipo.

10.3 Conversiones explícitas

10.3.1 General

Las conversiones siguientes se clasifican como conversiones explícitas:

  • Todas las conversiones implícitas (§10.2)
  • Conversiones numéricas explícitas (§10.3.2)
  • Conversiones de enumeración explícitas (§10.3.3)
  • Conversiones explícitas que aceptan valores NULL (§10.3.4)
  • Conversiones de tupla explícitas (§10.3.6)
  • Conversiones de referencia explícitas (§10.3.5)
  • Conversiones de interfaz explícitas
  • Conversiones de unboxing (§10.3.7)
  • Conversiones explícitas de parámetros de tipo (§10.3.8)
  • Conversiones explícitas definidas por el usuario (§10.3.9)

Las conversiones explícitas pueden producirse en expresiones de conversión (§12.9.7).

El conjunto de conversiones explícitas incluye todas las conversiones implícitas.

Nota: Por ejemplo, permite usar una conversión explícita cuando existe una conversión de identidad implícita para forzar la selección de una sobrecarga de método determinada. nota final

Las conversiones explícitas que no son conversiones implícitas son conversiones que no se pueden demostrar siempre que se realizan correctamente, conversiones que se sabe que pueden perder información y conversiones entre dominios de tipos lo suficientemente diferentes para merecer la notación explícita.

10.3.2 Conversiones numéricas explícitas

Las conversiones numéricas explícitas son las conversiones de un numeric_type a otra numeric_type para las que aún no existe una conversión numérica implícita (§10.2.3):

  • De sbyte a byte, ushort, uint, ulongo char.
  • De byte a sbyte o char.
  • De short a sbyte, byte, ushort, uint, ulongo char.
  • De ushort a sbyte, byte, shorto char.
  • De int a sbyte, , shortbyte, ushort, uint, ulongo char.
  • De uint a sbyte, byte, short, ushort, into char.
  • De long a sbyte, byte, , ushortshort, int, uint, , ulongo char.
  • De ulong a sbyte, byte, , ushortshort, int, uint, , longo char.
  • De char a sbyte, byteo short.
  • De float a sbyte, , byte, ushortshort, int, uintcharlongulongo .decimal
  • De double a sbyte, , byte, ushortshort, int, uint, long, ulong, char, floato decimal.
  • De decimal a sbyte, , byte, ushortshort, int, uint, long, ulong, char, floato double.

Dado que las conversiones explícitas incluyen todas las conversiones numéricas implícitas y explícitas, siempre es posible convertir de cualquier numeric_type a cualquier otra numeric_type mediante una expresión de conversión (§12.9.7).

Las conversiones numéricas explícitas posiblemente pierden información o, posiblemente, hacen que se produzcan excepciones. Una conversión numérica explícita se procesa de la siguiente manera:

  • Para una conversión de un tipo entero a otro tipo entero, el procesamiento depende del contexto de comprobación de desbordamiento (§12.8.20) en el que tiene lugar la conversión:
    • En un checked contexto, la conversión se realiza correctamente si el valor del operando de origen está dentro del intervalo del tipo de destino, pero produce un System.OverflowException si el valor del operando de origen está fuera del intervalo del tipo de destino.
    • En un unchecked contexto, la conversión siempre se realiza correctamente y continúa de la siguiente manera.
      • Si el tipo de origen es mayor que el tipo de destino, el valor de origen se trunca descartando sus bits más significativos. El resultado se trata como un valor del tipo de destino.
      • Si el tipo de origen tiene el mismo tamaño que el tipo de destino, el valor de origen se trata como un valor del tipo de destino.
  • Para una conversión de decimal a un tipo entero, el valor de origen se redondea hacia cero hasta el valor entero más cercano y este valor entero se convierte en el resultado de la conversión. Si el valor entero resultante está fuera del intervalo del tipo de destino, se produce una System.OverflowException excepción .
  • Para una conversión de float o double a un tipo entero, el procesamiento depende del contexto de comprobación de desbordamiento (§12.8.20) en el que tiene lugar la conversión:
    • En un contexto comprobado, la conversión continúa de la siguiente manera:
      • Si el valor del operando es NaN o infinito, se produce una System.OverflowException excepción .
      • De lo contrario, el operando de origen se redondea hacia cero hasta el valor entero más cercano. Si este valor entero está dentro del intervalo del tipo de destino, este valor es el resultado de la conversión.
      • De lo contrario, se produce una excepción System.OverflowException.
    • En un contexto sin marcar, la conversión siempre se realiza correctamente y continúa como se indica a continuación.
      • Si el valor del operando es NaN o infinito, el resultado de la conversión es un valor no especificado del tipo de destino.
      • De lo contrario, el operando de origen se redondea hacia cero hasta el valor entero más cercano. Si este valor entero está dentro del intervalo del tipo de destino, este valor es el resultado de la conversión.
      • De lo contrario, el resultado de la conversión es un valor no especificado del tipo de destino.
  • Para una conversión de double a float, el double valor se redondea al valor más cercano float . Si el double valor es demasiado pequeño para representar como float, el resultado se convierte en cero con el mismo signo que el valor. Si la magnitud del double valor es demasiado grande para representar como float, el resultado se convierte en infinito con el mismo signo que el valor. Si el double valor es NaN, el resultado también es NaN.
  • Para una conversión de float o double a decimal, el valor de origen se convierte en decimal representación y se redondea al número más cercano si es necesario (§8.3.8).
    • Si el valor de origen es demasiado pequeño para representar como decimal, el resultado se convierte en cero, conservando el signo del valor original si decimal admite valores cero firmados.
    • Si la magnitud del valor de origen es demasiado grande para representar como , decimalo ese valor es infinito, el resultado es infinito conservando el signo del valor original, si la representación decimal admite infinities; de lo contrario, se produce una excepción System.OverflowException.
    • Si el valor de origen es NaN, el resultado es NaN si la representación decimal admite NaNs; De lo contrario, se produce una excepción System.OverflowException.
  • Para una conversión de a float decimal o double, el decimal valor se redondea al valor más cercano double o float . Si la magnitud del valor de origen es demasiado grande para representar en el tipo de destino o ese valor es infinito, el resultado es infinito conservando el signo del valor original. Si el valor de origen es NaN, el resultado es NaN. Aunque esta conversión puede perder precisión, nunca hace que se produzca una excepción.

Nota: El decimal tipo no es necesario para admitir las infinities o los valores NaN, pero puede hacerlo; su intervalo puede ser menor que el intervalo de float y double, pero no se garantiza que sea. Para decimal representaciones sin infinities o valores NaN, y con un intervalo menor que float, el resultado de una conversión de decimal a o float double nunca será infinito o NaN. nota final

10.3.3 Conversiones de enumeración explícitas

Las conversiones de enumeración explícitas son:

  • De sbyte, byte, short, , intushort, uint, longulongcharfloat, o decimal doublea cualquier enum_type.
  • De cualquier enum_type a sbyte, byte, uintlongintushortcharshortulong, float, , doubleo .decimal
  • Desde cualquier enum_type a cualquier otro enum_type.

Una conversión de enumeración explícita entre dos tipos se procesa tratando cualquier enum_type participante como el tipo subyacente de esa enum_type y, a continuación, realizando una conversión numérica implícita o explícita entre los tipos resultantes.

Ejemplo: Dado un enum_type E con y el tipo subyacente de int, una conversión de E a byte se procesa como una conversión numérica explícita (§10.3.2) de int a y una conversión de byte a byteE se procesa como una conversión numérica implícita (§10.2.3) de byte a int. ejemplo final

10.3.4 Conversiones explícitas que aceptan valores NULL

Las conversiones explícitas que aceptan valores NULL son las conversiones que aceptan valores NULL (§10.6.1) derivadas de conversiones predefinidas explícitas e implícitas.

10.3.5 Conversiones de referencia explícitas

Las conversiones de referencia explícitas son:

  • De objeto a cualquier otro reference_type.
  • De cualquier class_type S a cualquier class_type T, proporcionado S es una clase base de T.
  • Desde cualquier class_type a cualquier interface_type TS , siempre S que no esté sellado y proporcionado S no implemente .T
  • Desde cualquier interface_type S a cualquier class_type T, siempre T que no esté sellado o proporcionado T implemente S.
  • De cualquier interface_typeS a cualquier interface_type T, proporcionado S no se deriva de T.
  • De un array_type S con un tipo Sᵢ de elemento a un array_type T con un tipo Tᵢde elemento , siempre que se cumplan todas las siguientes condiciones:
    • S y T solo difieren en el tipo de elemento. En otras palabras, S y T tienen el mismo número de dimensiones.
    • Existe una conversión de referencia explícita de Sᵢ a Tᵢ.
  • Desde System.Array y las interfaces que implementa, en cualquier array_type.
  • Desde un array_type S[] unidimensional a System.Collections.Generic.IList<T>, System.Collections.Generic.IReadOnlyList<T>y sus interfaces base, siempre que haya una conversión de identidad o una conversión de referencia explícita de S a T.
  • Desde System.Collections.Generic.IList<S>, System.Collections.Generic.IReadOnlyList<S>y sus interfaces base a un tipo T[]de matriz unidimensional, siempre que haya una conversión de identidad o una conversión de referencia explícita de S a T.
  • Desde System.Delegate y las interfaces que implementa en cualquier delegate_type.
  • Desde un tipo S de referencia a un tipo T de referencia si tiene una conversión de referencia explícita de S a un tipo T₀ de referencia y T₀ hay una conversión de identidad de T₀ a T.
  • Desde un tipo S de referencia a una interfaz o un tipo T delegado si hay una conversión de referencia explícita de S a una interfaz o un tipo T₀ delegado y es T₀ variable de varianza a T §T T₀ 18.2.3.3.
  • De D<S₁...Sᵥ> a donde D<X₁...Xᵥ> D<T₁...Tᵥ> es un tipo delegado genérico, D<S₁...Sᵥ> no es compatible con o idéntico a D<T₁...Tᵥ>, y para cada parámetro Xᵢ de tipo de D las siguientes suspensiones:
    • Si Xᵢ es invariable, entonces Sᵢ es idéntico a Tᵢ.
    • Si Xᵢ es covariante, hay una conversión de identidad, conversión de referencia implícita o conversión de referencia explícita de Sᵢ a Tᵢ.
    • Si Xᵢ es contravariante, Sᵢ y Tᵢ son idénticos o ambos tipos de referencia.
  • Conversiones explícitas que implican parámetros de tipo que se sabe que son tipos de referencia. Para obtener más información sobre las conversiones explícitas que implican parámetros de tipo, consulte §10.3.8.

Las conversiones de referencia explícitas son esas conversiones entre reference_types que requieren comprobaciones en tiempo de ejecución para asegurarse de que son correctas.

Para que una conversión de referencia explícita se realice correctamente en tiempo de ejecución, el valor del operando de origen será nullo el tipo del objeto al que hace referencia el operando de origen será un tipo que se pueda convertir al tipo de destino mediante una conversión de referencia implícita (§10.2.8). Si se produce un error en una conversión de referencia explícita, se produce una System.InvalidCastException excepción .

Nota: Las conversiones de referencia, implícitas o explícitas, nunca cambian el valor de la propia referencia (§8.2.1), solo su tipo; tampoco cambia el tipo o el valor del objeto al que se hace referencia. nota final

10.3.6 Conversiones explícitas de tupla

Existe una conversión explícita desde una expresión E de tupla a un tipo T de tupla si E tiene la misma aridad que T y existe una conversión implícita o explícita de cada elemento en E al tipo de elemento correspondiente en T. La conversión se realiza mediante la creación de una instancia del Ttipo correspondiente System.ValueTuple<...> y la inicialización de cada uno de sus campos en orden de izquierda a derecha mediante la evaluación de la expresión del elemento de tupla correspondiente de E, convirtiéndola en el tipo de elemento correspondiente de T utilizando la conversión explícita encontrada e inicializando el campo con el resultado.

10.3.7 Conversiones de unboxing

Una conversión de unboxing permite convertir explícitamente un reference_type en un value_type. Existen las siguientes conversiones de unboxing:

  • Desde el tipo object hasta cualquier value_type.
  • Desde el tipo System.ValueType hasta cualquier value_type.
  • Desde el tipo System.Enum hasta cualquier enum_type.
  • Desde cualquier interface_type a cualquier non_nullable_value_type que implemente el interface_type.
  • Desde cualquier interface_type a cualquier non_nullable_value_type I donde haya una conversión unboxing de un interface_type al tipo de non_nullable_value I₀ y una conversión de identidad de I a .I₀
  • De cualquier interface_type I a cualquier non_nullable_value_type donde haya una conversión de unboxing de un interface_type a la non_nullable_value_type I₀ y sea I₀ variance_convertible a I o I sea de varianza convertible a I₀ (§18.2.3.3).
  • Desde cualquier reference_type a cualquier nullable_value_type donde haya una conversión unboxing de reference_type a la non_nullable_value_type subyacente del nullable_value_type.
  • A partir de un parámetro de tipo que no se sabe que es un tipo de valor a cualquier tipo, de modo que la conversión se permita mediante §10.3.8.

Una operación de unboxing en un non_nullable_value_type consiste en comprobar primero que la instancia de objeto es un valor con conversión boxing del non_nullable_value_type especificado y, a continuación, copiar el valor fuera de la instancia.

La unboxing a un nullable_value_type genera el valor NULL del nullable_value_type si el operando de origen es nullo el resultado ajustado de desencapsular la instancia del objeto en el tipo subyacente del nullable_value_type de lo contrario.

Nota: Referencia a la clase boxing imaginaria descrita en §10.2.9, una conversión unboxing de un cuadro de objeto a un value_type S consiste en ejecutar la expresión ((S_Boxing)box).value. Por lo tanto, las instrucciones

object box = new S();
S s = (S)box;

corresponde conceptualmente a

object box = new S_Boxing(new S());
S s = ((S_Boxing)box).value;

nota final

Para que una conversión unboxing a un non_nullable_value_type determinado se realice correctamente en tiempo de ejecución, el valor del operando de origen será una referencia a un valor con conversión boxing de ese non_nullable_value_type. Si el operando de origen es null un System.NullReferenceException se produce. Si el operando de origen es una referencia a un objeto incompatible, se produce una System.InvalidCastException excepción .

Para que una conversión unboxing a un nullable_value_type determinado se realice correctamente en tiempo de ejecución, el valor del operando de origen será null o una referencia a un valor con conversión boxing del non_nullable_value_type subyacente del nullable_value_type. Si el operando de origen es una referencia a un objeto incompatible, se produce una System.InvalidCastException excepción .

10.3.8 Conversiones explícitas que implican parámetros de tipo

Para un type_parameter T que se sabe que es un tipo de referencia (§15.2.5), existen las siguientes conversiones de referencia explícitas (§10.3.5):

  • De la clase C base efectiva de T a T y de cualquier clase base de C a T.
  • De cualquier interface_type a T.
  • De T a cualquier interface_type I proporcionado ya no hay una conversión de referencia implícita de T a I.
  • De un type_parameter U a T siempre que T dependa U de (§15.2.5).

    Nota: Dado T que se sabe que es un tipo de referencia, dentro del ámbito de T, el tipo en tiempo de ejecución de siempre será un tipo de referencia, aunque U no se sepa que es un tipo de referencia en tiempo de compilación. nota final

Para un type_parameter T que no se sabe que es un tipo de referencia (§15.2.5), las siguientes conversiones que implican T se consideran conversiones de unboxing (§10.3.7) en tiempo de compilación. En tiempo de ejecución, si T es un tipo de valor, la conversión se ejecuta como una conversión de unboxing. En tiempo de ejecución, si T es un tipo de referencia, la conversión se ejecuta como una conversión de referencia explícita o conversión de identidad.

  • De la clase C base efectiva de T a T y de cualquier clase base de C a T.

    Nota: C será uno de los tipos System.Object, System.ValueTypeo System.Enum (de lo contrario T , se sabe que es un tipo de referencia). nota final

  • De cualquier interface_type a T.

Para un type_parameter T que no se sabe que es un tipo de referencia (§15.2.5), existen las siguientes conversiones explícitas:

  • De T a cualquier interface_type I siempre que no haya una conversión implícita de T a I. Esta conversión consta de una conversión boxing implícita (§10.2.9) de a object seguida de T una conversión de referencia explícita de object a I. En tiempo de ejecución, si T es un tipo de valor, la conversión se ejecuta como una conversión boxing seguida de una conversión de referencia explícita. En tiempo de ejecución, si T es un tipo de referencia, la conversión se ejecuta como una conversión de referencia explícita.
  • De un parámetro U de tipo a T proporcionado que T depende de U (§15.2.5). En tiempo de ejecución, si T es un tipo de valor y U es un tipo de referencia, la conversión se ejecuta como una conversión de unboxing. En tiempo de ejecución, si ambos T y U son tipos de valor, T y U son necesariamente el mismo tipo y no se realiza ninguna conversión. En tiempo de ejecución, si T es un tipo de referencia, entonces U también es necesariamente un tipo de referencia y la conversión se ejecuta como una conversión de referencia explícita o conversión de identidad.

En todos los casos, las reglas garantizan que una conversión se ejecute como una conversión unboxing si y solo si en tiempo de ejecución la conversión procede de un tipo de referencia a un tipo de valor.

Las reglas anteriores no permiten una conversión explícita directa de un parámetro de tipo sin restricciones a un tipo que no sea de interfaz, lo que puede ser sorprendente. La razón de esta regla es evitar confusiones y aclarar la semántica de estas conversiones.

Ejemplo: Considere la siguiente declaración:

class X<T>
{
    public static long F(T t)
    {
        return (long)t;         // Error
    }
}

Si se permitía la conversión explícita directa de t a long , podría esperarse fácilmente que X<int>.F(7) devolvería 7L. Sin embargo, no lo haría, ya que las conversiones numéricas estándar solo se consideran cuando se sabe que los tipos son numéricos en tiempo de enlace. Para que la semántica sea clara, el ejemplo anterior debe escribirse en su lugar:

class X<T>
{
    public static long F(T t)
    {
        return (long)(object)t;         // Ok, but will only work when T is long
    }
}

Este código ahora se compilará, pero la ejecución X<int>.F(7) produciría una excepción en tiempo de ejecución, ya que un cuadro int no se puede convertir directamente en .long

ejemplo final

10.3.9 Conversiones explícitas definidas por el usuario

Una conversión explícita definida por el usuario consta de una conversión explícita estándar opcional, seguida de la ejecución de un operador de conversión implícito o explícito definido por el usuario, seguido de otra conversión explícita estándar opcional. Las reglas exactas para evaluar conversiones explícitas definidas por el usuario se describen en §10.5.5.

10.4 Conversiones estándar

10.4.1 General

Las conversiones estándar son aquellas conversiones predefinidas que pueden producirse como parte de una conversión definida por el usuario.

10.4.2 Conversiones implícitas estándar

Las conversiones implícitas siguientes se clasifican como conversiones implícitas estándar:

  • Conversiones de identidad (§10.2.2)
  • Conversiones numéricas implícitas (§10.2.3)
  • Conversiones implícitas que aceptan valores NULL (§10.2.6)
  • Conversiones de literales NULL (§10.2.7)
  • Conversiones de referencia implícitas (§10.2.8)
  • Conversiones de boxeo (§10.2.9)
  • Conversiones implícitas de expresiones constantes (§10.2.11)
  • Conversiones implícitas que implican parámetros de tipo (§10.2.12)

Las conversiones implícitas estándar excluyen específicamente las conversiones implícitas definidas por el usuario.

10.4.3 Conversiones explícitas estándar

Las conversiones explícitas estándar son todas las conversiones implícitas estándar más el subconjunto de las conversiones explícitas para las que existe una conversión implícita estándar opuesta.

Nota: En otras palabras, si existe una conversión implícita estándar de un tipo A a un tipo B, existe una conversión explícita estándar de tipo A a tipo B y de tipo B a tipo A. nota final

10.5 Conversiones definidas por el usuario

10.5.1 General

C# permite aumentar las conversiones implícitas y explícitas predefinidas por las conversiones definidas por el usuario. Las conversiones definidas por el usuario se introducen declarando operadores de conversión (§15.10.4) en tipos de clase y estructura.

10.5.2 Conversiones definidas por el usuario permitidas

C# solo permite declarar determinadas conversiones definidas por el usuario. En concreto, no es posible volver a definir una conversión implícita o explícita ya existente.

Para un tipo de origen y un tipo TS de destino determinado , si S o T son tipos de valor que aceptan valores NULL, deje S₀ y T₀ haga referencia a sus tipos subyacentes; de lo contrarioS₀, y T₀ son iguales a S y T respectivamente. Una clase o estructura puede declarar una conversión de un tipo de origen a un tipo S T de destino solo si se cumplen todas las siguientes condiciones:

  • S₀ y T₀ son tipos diferentes.
  • T₀ O S₀ es el tipo de clase o estructura en el que tiene lugar la declaración del operador.
  • Ni ni S₀ T₀ es un interface_type.
  • Excluyendo las conversiones definidas por el usuario, una conversión no existe de S a T o de T a S.

Las restricciones que se aplican a las conversiones definidas por el usuario se especifican en §15.10.4.

10.5.3 Evaluación de conversiones definidas por el usuario

Una conversión definida por el usuario convierte una expresión de origen, que puede tener un tipo de origen, a otro tipo, denominado tipo de destino. La evaluación de una conversión definida por el usuario se centra en encontrar el operador de conversión definido por el usuario más específico para la expresión de origen y el tipo de destino. Esta determinación se divide en varios pasos:

  • Buscar el conjunto de clases y estructuras desde las que se considerarán los operadores de conversión definidos por el usuario. Este conjunto consta del tipo de origen y sus clases base, si el tipo de origen existe, junto con el tipo de destino y sus clases base. Para ello, se supone que solo las clases y estructuras pueden declarar operadores definidos por el usuario y que los tipos que no son de clase no tienen clases base. Además, si el tipo de origen o de destino es un tipo de valor que acepta valores NULL, su tipo subyacente se usa en su lugar.
  • A partir de ese conjunto de tipos, determinar qué operadores de conversión definidos por el usuario y elevado son aplicables. Para que un operador de conversión sea aplicable, será posible realizar una conversión estándar (§10.4) desde la expresión de origen al tipo de operando del operador y será posible realizar una conversión estándar del tipo de resultado del operador al tipo de destino.
  • A partir del conjunto de operadores definidos por el usuario aplicables, determinar qué operador es inequívocamente el más específico. En términos generales, el operador más específico es el operador cuyo tipo de operando es "más cercano" a la expresión de origen y cuyo tipo de resultado es "más cercano" al tipo de destino. Los operadores de conversión definidos por el usuario se prefieren sobre los operadores de conversión elevado. Las reglas exactas para establecer el operador de conversión definido por el usuario más específico se definen en las subclases siguientes.

Una vez identificado un operador de conversión definido por el usuario más específico, la ejecución real de la conversión definida por el usuario implica hasta tres pasos:

  • En primer lugar, si es necesario, realizar una conversión estándar de la expresión de origen al tipo de operando del operador de conversión definido por el usuario o elevado.
  • A continuación, invocar al operador de conversión definido por el usuario o elevado para realizar la conversión.
  • Por último, si es necesario, realizar una conversión estándar del tipo de resultado del operador de conversión definido por el usuario al tipo de destino.

La evaluación de una conversión definida por el usuario nunca implica más de un operador de conversión definido por el usuario o elevado. En otras palabras, una conversión de tipo S a tipo T nunca ejecutará primero una conversión definida por el usuario desde S a X y, a continuación, ejecutará una conversión definida por el usuario desde X a T.

  • Las definiciones exactas de evaluación de conversiones implícitas o explícitas definidas por el usuario se proporcionan en las subclases siguientes. Las definiciones hacen uso de los siguientes términos:
  • Si existe una conversión implícita estándar (§10.4.2) de un tipo A a un tipo By, si ni A B ni son interface_type s , A se dice que está abarcado porB y B se dice que abarca .A
  • Si existe una conversión implícita estándar (§10.4.2) desde una expresión E a un tipo B, y si ni B ni el tipo de E (si tiene uno) son interface_type s, E se dice que está abarcado porB y B se dice que abarcaE.
  • El tipo más abarcante de un conjunto de tipos es el que abarca todos los demás tipos del conjunto. Si ningún tipo único abarca todos los demás tipos, el conjunto no tiene ningún tipo que abarque más. En términos más intuitivos, el tipo más abarcativo es el tipo "más grande" del conjunto, el tipo en el que se puede convertir implícitamente cada uno de los otros tipos.
  • El tipo más abarcado de un conjunto de tipos es el que abarca todos los demás tipos del conjunto. Si no hay ningún tipo único incluido en todos los demás tipos, el conjunto no tiene ningún tipo más abarcado. En términos más intuitivos, el tipo más abarcado es el tipo "más pequeño" del conjunto, el único tipo que se puede convertir implícitamente en cada uno de los otros tipos.

10.5.4 Conversiones implícitas definidas por el usuario

Una conversión implícita definida por el usuario de una expresión E a un tipo T se procesa de la siguiente manera:

  • Determine los tipos Sy S₀ T₀.

    • Si E tiene un tipo, vamos S a ser ese tipo.
    • Si S o T son tipos de valor que aceptan valores NULL, let Sᵢ y Tᵢ sean sus tipos subyacentes, de lo contrario, let Sᵢ y be S y Tᵢ T, respectivamente.
    • Si Sᵢ o Tᵢ son parámetros de tipo, let S₀ y T₀ sean sus clases base efectivas, de lo contrario, let y S₀ be Sₓ y T₀ Tᵢ, respectivamente.
  • Busque el conjunto de tipos, , Ddesde el que se considerarán los operadores de conversión definidos por el usuario. Este conjunto consta de S₀ (si S₀ existe y es una clase o estructura), las clases base de S₀ (si S₀ existe y es una clase) y T₀ (si T₀ es una clase o estructura). Un tipo se agrega al conjunto D solo si no existe una conversión de identidad a otro tipo ya incluido en el conjunto.

  • Busque el conjunto de operadores de conversión definidos por el usuario y eliminados aplicables, U. Este conjunto consta de los operadores de conversión implícitos definidos por el usuario declarados por las clases o estructuras de D que convierten de un tipo que abarca E a un tipo incluido en T. Si U está vacío, la conversión no está definida y se produce un error en tiempo de compilación.

    • Si S existe y cualquiera de los operadores de U conversión de S, Sₓ es S.
    • De lo contrario, Sₓ es el tipo más abarcado del conjunto combinado de tipos de origen de los operadores de U. Si no se encuentra exactamente un tipo más abarcado, la conversión es ambigua y se produce un error en tiempo de compilación.
  • Busque el tipo de destino más específico, , Tₓde los operadores en U:

    • Si alguno de los operadores de conversión en U T, Tₓ es T.
    • De lo contrario, Tₓ es el tipo más abarcante del conjunto combinado de tipos de destino de los operadores de U. Si no se encuentra exactamente un tipo más abarcador, la conversión es ambigua y se produce un error en tiempo de compilación.
  • Busque el operador de conversión más específico:

    • Si U contiene exactamente un operador de conversión definido por el usuario que convierte de Sₓ a Tₓ, este es el operador de conversión más específico.
    • De lo contrario, si U contiene exactamente un operador de conversión elevado que convierte de Sₓ a Tₓ, este es el operador de conversión más específico.
    • De lo contrario, la conversión es ambigua y se produce un error en tiempo de compilación.
  • Por último, aplique la conversión:

    • Si E aún no tiene el tipo Sₓ, se realiza una conversión implícita estándar de E a Sₓ .
    • El operador de conversión más específico se invoca para convertir de Sₓ a Tₓ.
    • Si Tₓ no Tes , se realiza una conversión implícita estándar de Tₓ a T .

Existe una conversión implícita definida por el usuario de un tipo S a un tipo T si existe una conversión implícita definida por el usuario desde una variable de tipo S a T.

10.5.5 Conversiones explícitas definidas por el usuario

Una conversión explícita definida por el usuario de una expresión E a un tipo T se procesa de la siguiente manera:

  • Determine los tipos Sy S₀ T₀.
    • Si E tiene un tipo, vamos S a ser ese tipo.
    • Si S o T son tipos de valor que aceptan valores NULL, let Sᵢ y Tᵢ sean sus tipos subyacentes, de lo contrario, let Sᵢ y be S y Tᵢ T, respectivamente.
    • Si Sᵢ o Tᵢ son parámetros de tipo, let S₀ y T₀ sean sus clases base efectivas, de lo contrario, let y S₀ be Sᵢ y T₀ Tᵢ, respectivamente.
  • Busque el conjunto de tipos, , Ddesde el que se considerarán los operadores de conversión definidos por el usuario. Este conjunto consta de S₀ (si S₀ existe y es una clase o estructura), las clases base de S₀ (si S₀ existe y es una clase), T₀ (si T₀ es una clase o estructura) y las clases base de T₀ (si T₀ es una clase). A el tipo se agrega al conjunto D solo si no existe una conversión de identidad a otro tipo incluido en el conjunto.
  • Busque el conjunto de operadores de conversión definidos por el usuario y eliminados aplicables, U. Este conjunto consta de los operadores de conversión implícitos o explícitos definidos por el usuario declarados por las clases o estructuras en D que se convierten de un tipo que abarca E o abarca (Ssi existe) a un tipo que abarca o abarca .T Si U está vacío, la conversión no está definida y se produce un error en tiempo de compilación.
  • Busque el tipo de origen más específico, , Sₓde los operadores en U:
    • Si existe S y cualquiera de los operadores de U conversión de S, es Sₓ S.
    • De lo contrario, si alguno de los operadores de U conversión de tipos que abarcan , es Sₓ el tipo más abarcado Een el conjunto combinado de tipos de origen de esos operadores. Si no se encuentra ningún tipo más abarcado, la conversión es ambigua y se produce un error en tiempo de compilación.
    • De lo contrario, Sₓ es el tipo más abarcante del conjunto combinado de tipos de origen de los operadores de U. Si no se encuentra exactamente un tipo más abarcador, la conversión es ambigua y se produce un error en tiempo de compilación.
  • Busque el tipo de destino más específico, , Tₓde los operadores en U:
    • Si alguno de los operadores de conversión en U T, Tₓ es T.
    • De lo contrario, si alguno de los operadores de U conversión a tipos que están incluidos en T, es Tₓ el tipo más abarcante del conjunto combinado de tipos de destino de esos operadores. Si no se encuentra exactamente un tipo más abarcador, la conversión es ambigua y se produce un error en tiempo de compilación.
    • De lo contrario, Tₓ es el tipo más abarcado del conjunto combinado de tipos de destino de los operadores de U. Si no se encuentra ningún tipo más abarcado, la conversión es ambigua y se produce un error en tiempo de compilación.
  • Busque el operador de conversión más específico:
    • Si U contiene exactamente un operador de conversión definido por el usuario que convierte de Sₓ a Tₓ, este es el operador de conversión más específico.
    • De lo contrario, si U contiene exactamente un operador de conversión elevado que convierte de Sₓ a Tₓ, este es el operador de conversión más específico.
    • De lo contrario, la conversión es ambigua y se produce un error en tiempo de compilación.
  • Por último, aplique la conversión:
    • Si E aún no tiene el tipo Sₓ, se realiza una conversión explícita estándar de E a Sₓ .
    • El operador de conversión definido por el usuario más específico se invoca para convertir de Sₓ a Tₓ.
    • Si Tₓ no Tes , se realiza una conversión explícita estándar de Tₓ a T .

Existe una conversión explícita definida por el usuario de un tipo S a un tipo T si existe una conversión explícita definida por el usuario desde una variable de tipo S a T.

10.6 Conversiones que implican tipos que aceptan valores NULL

10.6.1 Conversiones que aceptan valores NULL

Las conversiones que aceptan valores NULL permiten conversiones predefinidas que operan en tipos de valor que no aceptan valores NULL para que también se usen con formas que aceptan valores NULL de esos tipos. Para cada una de las conversiones implícitas o explícitas predefinidas que convierten de un tipo de valor que no acepta valores NULL a un tipo S T de valor que no acepta valores NULL (§10.2.2, §10.2.4, §10.2.11, §10.3.2 y §10.3.3), existen las siguientes conversiones que aceptan valores NULL:

  • Conversión implícita o explícita de S? a T?
  • Conversión implícita o explícita de S a T?
  • Conversión explícita de S? a T.

Una conversión que acepta valores NULL se clasifica como una conversión implícita o explícita.

Algunas conversiones que aceptan valores NULL se clasifican como conversiones estándar y pueden producirse como parte de una conversión definida por el usuario. En concreto, todas las conversiones implícitas que aceptan valores NULL se clasifican como conversiones implícitas estándar (§10.4.2) y esas conversiones explícitas que cumplen los requisitos de §10.4.3 se clasifican como conversiones explícitas estándar.

Evaluación de una conversión que acepta valores NULL en función de una conversión subyacente de S para T continuar de la siguiente manera:

  • Si la conversión que acepta valores NULL es de S? a T?:
    • Si el valor de origen es null (HasValue la propiedad es false), el resultado es el valor null de tipo T?.
    • De lo contrario, la conversión se evalúa como una desencapsulación de S? a S, seguida de la conversión subyacente de S a T, seguida de un ajuste de T a T?.
  • Si la conversión que acepta valores NULL es de S a T?, la conversión se evalúa como la conversión subyacente de S a T seguida de un ajuste de T a T?.
  • Si la conversión que acepta valores NULL es de S? a T, la conversión se evalúa como una desencapsulación de S? a S seguida de la conversión subyacente de S a T.

10.6.2 Conversiones levantadas

Dado un operador de conversión definido por el usuario que convierte de un tipo S de valor que no acepta valores NULL a un tipo Tde valor que no acepta valores NULL, existe un operador de conversión elevado que convierte de S? a T?. Este operador de conversión elevado realiza una desencapsulación de S? a S seguida de la conversión definida por el usuario de a T seguida de S un ajuste de T a T?, excepto que un valor S? NULO convierte directamente en un valor NULL con valores NULL.T? Un operador de conversión elevado tiene la misma clasificación implícita o explícita que su operador de conversión definido por el usuario subyacente.

10.7 Conversiones de funciones anónimas

10.7.1 General

Un anonymous_method_expression o lambda_expression se clasifica como una función anónima (§12.19). La expresión no tiene un tipo, pero se puede convertir implícitamente en un tipo delegado compatible. Algunas expresiones lambda también se pueden convertir implícitamente en un tipo de árbol de expresión compatible.

En concreto, una función F anónima es compatible con un tipo D de delegado proporcionado:

  • Si F contiene un anonymous_function_signature, D y F tienen el mismo número de parámetros.
  • Si F no contiene un anonymous_function_signature, D puede tener cero o más parámetros de cualquier tipo, siempre y cuando ningún parámetro de sea un parámetro de D salida.
  • Si F tiene una lista de parámetros con tipo explícito, cada parámetro de D tiene los mismos modificadores que el parámetro correspondiente en F y existe una conversión de identidad entre el parámetro correspondiente en F.
  • Si F tiene una lista de parámetros con tipo implícito, D no tiene parámetros de referencia ni de salida.
  • Si el cuerpo de F es una expresión yD tiene un tipo de valor devuelto void oF es asincrónico y D tiene un «TaskType» tipo de valor devuelto (§15.15.1), cuando cada parámetro de F tiene el tipo del parámetro correspondiente en D, el cuerpo de F es una expresión válida (w.r.t §12) que se permitiría como un statement_expression (§13.7).
  • Si el cuerpo de F es un bloque y Dtiene un tipo de valor devuelto void oF es asincrónico y D tiene un «TaskType» tipo de valor devuelto , cuando cada parámetro de F recibe el tipo del parámetro correspondiente en D, el cuerpo de F es un bloque válido (w.r.t §13.3) en el que ninguna return instrucción especifica una expresión.
  • Si el cuerpo de F es una expresión y noF es asincrónico y D tiene unvoid tipo Tde valor devuelto , oF es asincrónico y D tiene un «TaskType»<T> tipo de valor devuelto (§15.15.1), cuando cada parámetro de F tiene el tipo del parámetro correspondiente en , el cuerpo de F es una expresión válida (w.r.t §12) que se puede convertir implícitamente en DT.
  • Si el cuerpo de F es un bloque y noF es asincrónico y D tiene un tipo Tde valor devuelto no nulo , oF es asincrónico y D tiene un «TaskType»<T> tipo de valor devuelto, cuando cada parámetro de F se asigna el tipo del parámetro correspondiente en D, el cuerpo de es un bloque de F instrucciones válido (w.r.t §13.3) con un punto final no accesible en el que cada instrucción de devolución especifica una expresión que se puede convertir Timplícitamente en .

Ejemplo: En los ejemplos siguientes se muestran estas reglas:

delegate void D(int x);
D d1 = delegate { };                         // Ok
D d2 = delegate() { };                       // Error, signature mismatch
D d3 = delegate(long x) { };                 // Error, signature mismatch
D d4 = delegate(int x) { };                  // Ok
D d5 = delegate(int x) { return; };          // Ok
D d6 = delegate(int x) { return x; };        // Error, return type mismatch

delegate void E(out int x);
E e1 = delegate { };                         // Error, E has an output parameter
E e2 = delegate(out int x) { x = 1; };       // Ok
E e3 = delegate(ref int x) { x = 1; };       // Error, signature mismatch

delegate int P(params int[] a);
P p1 = delegate { };                         // Error, end of block reachable
P p2 = delegate { return; };                 // Error, return type mismatch
P p3 = delegate { return 1; };               // Ok
P p4 = delegate { return "Hello"; };         // Error, return type mismatch
P p5 = delegate(int[] a)                     // Ok
{
    return a[0];
};
P p6 = delegate(params int[] a)              // Error, params modifier
{
    return a[0];
};
P p7 = delegate(int[] a)                     // Error, return type mismatch
{
    if (a.Length > 0) return a[0];
    return "Hello";
};

delegate object Q(params int[] a);
Q q1 = delegate(int[] a)                    // Ok
{
    if (a.Length > 0) return a[0];
    return "Hello";
};

ejemplo final

Ejemplo: Los ejemplos siguientes usan un tipo Func<A,R> de delegado genérico que representa una función que toma un argumento de tipo A y devuelve un valor de tipo R:

delegate R Func<A,R>(A arg);

En las asignaciones

Func<int,int> f1 = x => x + 1; // Ok
Func<int,double> f2 = x => x + 1; // Ok
Func<double,int> f3 = x => x + 1; // Error
Func<int, Task<int>> f4 = async x => x + 1; // Ok

El parámetro y los tipos devueltos de cada función anónima se determinan a partir del tipo de la variable a la que se asigna la función anónima.

La primera asignación convierte correctamente la función anónima en el tipo Func<int,int> delegado porque, cuando x se especifica el tipo int, x + 1 es una expresión válida que se puede convertir implícitamente al tipo int.

Del mismo modo, la segunda asignación convierte correctamente la función anónima en el tipo delegado Func<int,double> porque el resultado de x + 1 (de tipo int) se convierte implícitamente en el tipo double.

Sin embargo, la tercera asignación es un error en tiempo de compilación porque, cuando x se da el tipo double, el resultado de x + 1 (de tipo double) no se puede convertir implícitamente al tipo int.

La cuarta asignación convierte correctamente la función asincrónica anónima en el tipo Func<int, Task<int>> de delegado porque el resultado de x + 1 (de tipo int) se puede convertir implícitamente al tipo int de valor devuelto efectivo de la lambda asincrónica, que tiene un tipo Task<int>de valor devuelto .

ejemplo final

Una expresión F lambda es compatible con un tipo Expression<D> de árbol de expresión si F es compatible con el tipo Ddelegado . Esto no se aplica a métodos anónimos, solo expresiones lambda.

Las funciones anónimas pueden influir en la resolución de sobrecargas y participar en la inferencia de tipos. Consulte §12.6 para obtener más información.

10.7.2 Evaluación de conversiones de funciones anónimas a tipos delegados

La conversión de una función anónima a un tipo delegado genera una instancia de delegado que hace referencia a la función anónima y al conjunto (posiblemente vacío) de variables externas capturadas que están activas en el momento de la evaluación. Cuando se invoca el delegado, se ejecuta el cuerpo de la función anónima. El código del cuerpo se ejecuta mediante el conjunto de variables externas capturadas a las que hace referencia el delegado. Un delegate_creation_expression (§12.8.17.6) se puede usar como sintaxis alternativa para convertir un método anónimo en un tipo delegado.

La lista de invocación de un delegado generado a partir de una función anónima contiene una sola entrada. No se especifican el objeto de destino exacto y el método de destino del delegado. En concreto, no se especifica si el objeto de destino del delegado es null, el this valor del miembro de función envolvente o algún otro objeto.

Las conversiones de funciones anónimas semánticamente idénticas con el mismo conjunto (posiblemente vacío) de instancias de variables externas capturadas en los mismos tipos de delegado se permiten (pero no son necesarios) para devolver la misma instancia de delegado. El término semánticamente idéntico se usa aquí para significar que la ejecución de las funciones anónimas producirá, en todos los casos, los mismos efectos dados los mismos argumentos. Esta regla permite optimizar el código como el siguiente.

delegate double Function(double x);

class Test
{
    static double[] Apply(double[] a, Function f)
    {
        double[] result = new double[a.Length];
        for (int i = 0; i < a.Length; i++)
        {
            result[i] = f(a[i]);
        }
        return result;
    }

    static void F(double[] a, double[] b)
    {
        a = Apply(a, (double x) => Math.Sin(x));
        b = Apply(b, (double y) => Math.Sin(y));
        ...
    }
}

Dado que los dos delegados de función anónima tienen el mismo conjunto (vacío) de variables externas capturadas y, dado que las funciones anónimas son semánticamente idénticas, el compilador puede hacer que los delegados hagan referencia al mismo método de destino. De hecho, el compilador puede devolver la misma instancia de delegado de ambas expresiones de función anónimas.

10.7.3 Evaluación de conversiones de expresiones lambda a tipos de árbol de expresiones

La conversión de una expresión lambda a un tipo de árbol de expresión genera un árbol de expresión (§8.6). De forma más precisa, la evaluación de la conversión de expresiones lambda genera una estructura de objeto que representa la estructura de la propia expresión lambda.

No todas las expresiones lambda se pueden convertir en tipos de árbol de expresión. La conversión a un tipo de delegado compatible siempre existe, pero puede producirse un error en tiempo de compilación por motivos definidos por la implementación.

Nota: Entre las razones comunes para que una expresión lambda no se convierta en un tipo de árbol de expresión se incluyen:

  • Tiene un cuerpo de bloque
  • Tiene el async modificador
  • Contiene un operador de asignación
  • Contiene un parámetro de salida o referencia.
  • Contiene una expresión enlazada dinámicamente

nota final

10.8 Conversiones de grupo de métodos

Existe una conversión implícita de un grupo de métodos (§12.2) a un tipo de delegado compatible (§20.4). Si D es un tipo de delegado y E es una expresión que se clasifica como un grupo de métodos, D entonces es compatible con E si y solo si E contiene al menos un método que sea aplicable en su forma normal (§12.6.4.2) a cualquier lista de argumentos (§12.6.2) que tenga tipos y modificadores que coincidan con los tipos de parámetro y modificadores de D, como se describe en lo siguiente.

La aplicación en tiempo de compilación de la conversión de un grupo E de métodos a un tipo D delegado se describe en lo siguiente.

  • Se selecciona un único método correspondiente a una invocación de método M (§12.8.10.2) del formulario E(A), con las siguientes modificaciones:
    • La lista A de argumentos es una lista de expresiones, cada una clasificada como una variable y con el tipo y modificador (in, outo ref) del parámetro correspondiente en el parameter_list de D , excepto los parámetros de tipo dynamic, donde la expresión correspondiente tiene el tipo object en lugar de dynamic.
    • Los métodos candidatos considerados son solo los métodos aplicables en su forma normal y no omiten ningún parámetro opcional (§12.6.4.2). Por lo tanto, los métodos candidatos se omiten si solo son aplicables en su forma expandida, o si uno o varios de sus parámetros opcionales no tienen un parámetro correspondiente en D.
  • Se considera que existe una conversión si el algoritmo de §12.8.10.2 genera un único método M que es compatible (§20.4) con D.
  • Si el método seleccionado es un método M de instancia, la expresión de instancia asociada a E determina el objeto de destino del delegado.
  • Si el método seleccionado es un método M de extensión que se indica mediante un acceso de miembro en una expresión de instancia, esa expresión de instancia determina el objeto de destino del delegado.
  • El resultado de la conversión es un valor de tipo D, es decir, un delegado que hace referencia al método seleccionado y al objeto de destino.

Ejemplo: a continuación se muestran las conversiones de grupo de métodos:

delegate string D1(object o);
delegate object D2(string s);
delegate object D3();
delegate string D4(object o, params object[] a);
delegate string D5(int i);
class Test
{
    static string F(object o) {...}

    static void G()
    {
        D1 d1 = F;         // Ok
        D2 d2 = F;         // Ok
        D3 d3 = F;         // Error – not applicable
        D4 d4 = F;         // Error – not applicable in normal form
        D5 d5 = F;         // Error – applicable but not compatible
    }
}

La asignación para d1 convertir implícitamente el grupo F de métodos en un valor de tipo D1.

La asignación para d2 mostrar cómo es posible crear un delegado en un método que tenga tipos de parámetros menos derivados (contravariantes) y un tipo de valor devuelto más derivado (covariante).

La asignación para d3 mostrar cómo no existe ninguna conversión si el método no es aplicable.

La asignación para d4 mostrar cómo debe aplicarse el método en su forma normal.

La asignación para d5 mostrar cómo se permiten los tipos de parámetro y valor devuelto del delegado y el método solo para los tipos de referencia.

ejemplo final

Al igual que con las demás conversiones implícitas y explícitas, el operador de conversión se puede usar para realizar explícitamente una conversión determinada.

Ejemplo: Por lo tanto, el ejemplo

object obj = new EventHandler(myDialog.OkClick);

podría escribirse en su lugar

object obj = (EventHandler)myDialog.OkClick;

ejemplo final

Una conversión de grupo de métodos puede hacer referencia a un método genérico, ya sea especificando explícitamente argumentos de tipo dentro Ede o a través de la inferencia de tipos (§12.6.3). Si se usa la inferencia de tipos, los tipos de parámetro del delegado se usan como tipos de argumento en el proceso de inferencia. El tipo de valor devuelto del delegado no se usa para la inferencia. Si se especifican o deducen los argumentos de tipo, forman parte del proceso de conversión del grupo de métodos; son los argumentos de tipo que se usan para invocar el método de destino cuando se invoca el delegado resultante.

Ejemplo:

delegate int D(string s, int i);
delegate int E();

class X
{
    public static T F<T>(string s, T t) {...}
    public static T G<T>() {...}

    static void Main()
    {
        D d1 = F<int>;        // Ok, type argument given explicitly
        D d2 = F;             // Ok, int inferred as type argument
        E e1 = G<int>;        // Ok, type argument given explicitly
        E e2 = G;             // Error, cannot infer from return type
    }
}

ejemplo final

Los grupos de métodos pueden influir en la resolución de sobrecargas y participar en la inferencia de tipos. Consulte §12.6 para obtener más información.

La evaluación en tiempo de ejecución de una conversión de grupo de métodos continúa de la siguiente manera:

  • Si el método seleccionado en tiempo de compilación es un método de instancia, o es un método de extensión al que se tiene acceso como método de instancia, el objeto de destino del delegado se determina a partir de la expresión de instancia asociada a E:
    • Se evalúa la expresión de instancia. Si esta evaluación provoca una excepción, no se ejecutan pasos adicionales.
    • Si la expresión de instancia es de un reference_type, el valor calculado por la expresión de instancia se convierte en el objeto de destino. Si el método seleccionado es un método de instancia y el objeto de destino es null, se inicia y System.NullReferenceException no se ejecutan más pasos.
    • Si la expresión de instancia es de un value_type, se realiza una operación de conversión boxing (§10.2.9) para convertir el valor en un objeto y este objeto se convierte en el objeto de destino.
  • De lo contrario, el método seleccionado forma parte de una llamada al método estático y el objeto de destino del delegado es null.
  • Se obtiene una instancia de delegado de tipo D delegado con una referencia al método que se determinó en tiempo de compilación y una referencia al objeto de destino calculado anteriormente, como se indica a continuación:
    • La conversión se permite (pero no necesaria) para usar una instancia de delegado existente que ya contiene estas referencias.
    • Si no se ha reutilizado una instancia existente, se crea una nueva (§20.5). Si no hay suficiente memoria disponible para asignar la nueva instancia, se produce una System.OutOfMemoryException excepción . De lo contrario, la instancia se inicializa con las referencias especificadas.