sábado, 2 de abril de 2022

Curso avanzado de C#. Casting

Un operador de conversión le dice explícitamente al compilador que desea convertir un valor en un tipo de datos en particular. Para convertir una variable en un tipo particular, colocamos el tipo entre paréntesis delante del valor que deseamos convertir. Por ejemplo, el siguiente código toma la variable valor1 de tipo double  y la convierte en el tipo de datos float, después la guarda en la variable de tipo float  valor2. 

double valor1 = 10;
float valor2 = (float)valor1;


Nota: La conversión de un valor de punto flotante (float) en un tipo de datos integer (entero) hace que el valor se trunque. Por ejemplo, la declaración (int) 10.9 devuelve el valor entero 10. Si deseamos redondear el valor al entero más cercano en lugar de truncarlo, debemos utilizar método ToInt32 de la clase System.Convert o el método Math.Round. 

Curso avanzado de C#. Casting



La conversión de un tipo de referencia a una clase o interfaz ancestral directa o indirecta es una conversión de ampliación, por lo que un programa puede realizar la conversión implícitamente
Por ejemplo, si la clase Empleado se deriva de la clase Persona, podemos convertir un objeto Empleado en un objeto Persona: 

Empleado empleado1 = new Empleado();
Persona persona1 = empleado1;

La conversión de un valor de referencia a una clase o interfaz ancestral no cambia realmente el valor; simplemente lo hace actuar como si fuera del nuevo tipo. En el ejemplo anterior, persona1 es una variable, pero hace referencia a un objeto Empleado. El código puede usar la variable persona1 para tratar el objeto como si fuera una persona, pero sigue siendo un empleado. Como persona1 es realmente un empleado, puede volver a convertirlo en una variable empleado: 

Empleado empleado1 = new Empleado();
Persona persona1 = empleado1;
Persona persona2 = new Empleado();
// Permitido porque persona1 es realmente un Empleado.
Empleado empleado2 = (Empleado)persona1;

La conversión de persona a empleado es una conversión restringida, por lo que el código necesita el operador de conversión (empleado). Este tipo de operador de conversión permite que el código se compile, pero el programa lanza una excepción InvalidCastException en tiempo de ejecución si el valor no es realmente del tipo apropiado. 

Por ejemplo, el siguiente código lanza una excepción cuando intentamos convertir un objeto Persona en un objeto de tipo Empleado: 

Person persona2 = new Persona();
// No permitido porque persona2 es una Persona pero no un Empleado.
Empleado empleado3 = (Empleado)persona2;

Debido a que los programas a menudo necesitan convertir los datos de referencia de una clase a una clase compatible, como se muestra en el código anterior, C # proporciona dos operadores para facilitar ese tipo de conversión: is y as. 

El operador is

El operador is, determina si un objeto es compatible con un tipo en particular. Por ejemplo,  la clase Empleado se deriva de Persona y la clase Jefe se deriva de Empleado. Ahora si el programa tiene una variable llamada usuario y debe realizar una acción especial si esa variable se refiere a un empleado pero no a una Persona. 

El siguiente código usa el operador is para determinar si la variable persona se refiere a un empleado y realiza una acción especial: 

if (usuario is Empleado)
{
            // Hacemos algo con el empleado... ...
}

Si el operador devuelve true  indica que la variable usuario se refiere a un empleado, y el código realiza cualquier acción que sea necesaria. El operador is devuelve true si el objeto es compatible con el tipo indicado, no solo si el objeto es de ese tipo. El código anterior devuelve verdadero si el usuario hace referencia a un empleado, pero también devuelve verdadero si el usuario hace referencia a un jefe porque un jefe es un tipo de empleado. ( jefe se derivó de Empleado). 

El operador as

El código anterior realiza una acción especial si la variable usuario hace referencia a un objeto que tiene un tipo compatible con la clase Empleado. (En este ejemplo, eso significa que el usuario es un empleado o administrador). 

A menudo, el siguiente paso es convertir la variable en una clase más específica antes de tratar el objeto como si fuera de esa clase. El siguiente código convierte al usuario en un Empleado, por lo que puede tratarlo como un Empleado: 

if (usuario is Empleado)
 {
   // El usuario es un empleado. Lo trata como uno.
   Empleado emp = (Empleado)usuario;
   // Hace algo con el Empleado... ...
}

La palabra clave as hace que esta conversión sea un poco más fácil. El objeto as Clase devuelve el objeto convertido en la clase indicada si el objeto es compatible con esa clase. Si el objeto no es compatible con la clase, la instrucción devuelve un valor nulo. 

El siguiente código muestra el ejemplo anterior reescrito para usar el operador as:

Empleado emp = usuario as Empleado;
if (emp! = null) {
// Hacemos algo con el Empleado
}

Se puede utilizar indistintamente el operador is o el as tal y como se ha mostrado en estos dos ejemplos 
Una situación en la que el operador is es particularmente útil es cuando se sabe que una variable se refiere a un objeto de un tipo específico. 

Por ejemplo, consideramos el siguiente controlador de eventos CheckedChanged del control RadioButton: 

// Pinta de rojo el RadioButton seleccionado.
private void MenuRadioButton_CheckedChanged(object sender, EventArgs e)
{
   RadioButton rad = sender as RadioButton;
   if (rad.Checked) rad.ForeColor = Color.Red;
   else rad.ForeColor = SystemColors.ControlText;
}

Este controlador de eventos está asignado a varios eventos CheckedChanged de RadioButtons, pero no importa en qué control se haga clic, nosotros sabemos que el parámetro remitente se refiere a RadioButton. El código utiliza el operador para convertir el remitente en RadioButton, de modo que luego podemos utilizar las propiedades Checked y ForeColor del RadioButton. 

Casting de matrices (arrays)

Las reglas de casting anteriormente descritas también se aplican a las matrices de valores de referencia. Incluso el operador is y el as trabajan con matrices de valores de referencia. Supongamos que la clase Empleado se deriva de la clase Persona, y la clase Jefe se deriva de la clase Empleado. El siguiente código muestra las reglas de conversión para las matrices de estas clases:

// Declara e inicializa un array de Empleados.
Empleado[] empleados = new Empleado[10];
for (int id = 0; id < empleados.Length; id++)
empleados[id] = new Empleado(id);
// Conversión implicita a un array de Personas.
// (Un Empleado es un tipo de Persona.)
Persona[] personas = empleados;
// Conversión explicita que devuelve un array of Empleados.
// (Las Personas del array son Empleados.)
empleados = (Empleado[])personas;
// Utiliza el operador is.
if (personas is Empleado[])
{
// Los trata como Empleados. ...
}
// Utiliza el operador as.
 empleados = personas as Empleado[];
// En esta instrucción, jefes es nulo.
 Jefe[] jefes = personas as Jefe[];
// Utiliza el operador is otra vez, esta vez para ver
// si personas es compatible con Jefe[].
 if (personas is Jefe[])
{
// Los trata como Jefes.
//...
}
// Esta conversión falla en tiempo de ejecución porque el array
// contiene Empleados no Jefes.
 jefes = (Jefe[])personas;

El código primero declara e inicializa una matriz de objetos de tipo Empleado llamada empleados. A continuación, define una matriz de objetos Persona llamada personas y la iguala a los empleados. Debido a que Empleado y Persona son tipos compatibles (uno es descendiente del otro), esta conversión es potencialmente válida. Debido a que es una conversión de ampliación (el Empleado es un tipo de Persona), esto puede ser una conversión implícita, por lo que no se necesita un operador y la conversión tendrá éxito. A continuación, el código convierte el array de personas de nuevo al tipo Empleado [] y guarda el resultado en empleados. De nuevo, estos son tipos compatibles, por lo que el reparto es potencialmente válido. Esta es una conversión restringida (los Empleados son Personas pero no todas las Personas son Empleados), por lo que debe ser una conversión explícita y se requiere el operador de conversión (Empleado []). El código luego utiliza el operador para determinar si la matriz de personas es compatible con el tipo Empleado []. En este ejemplo, las personas mantienen una referencia a una matriz de objetos Empleado, por lo que es compatible y el programa ejecuta cualquier código que se encuentre dentro de la sentencia if. (Esto tiene sentido ahora, pero hay un aspecto contrario a la intuición que se discutirá en breve). Después, el programa utiliza el operador as para convertir personas en empleados de la matriz Empleado []. Debido a que las personas se pueden convertir en una variedad de empleado, esta conversión funciona como se espera. 
El código luego usa el operador nuevamente para convertir personas en jefes del array Jefes[].  Porque la personas tiene  Empleados, que no se puede convertir en Jefes, esta conversión falla, por lo que la variable jefes es igual a null.

Luego, el programa usa el operador is para ver si las personas se pueden convertir en un array de jefes. Una vez más, esa conversión no funcionará, por lo que el código dentro de este bloque se omite. Del mismo modo, la conversión explícita que intenta convertir personas en un array de jefes, falla. Cuando intentamos ejecutar esta declaración, el programa lanza una excepción InvalidCastException. 

El Casting no genera un nuevo array. Es un error común pensar esto, existe un problema contraintuitivo relacionado con el casting de arrays. Cuando convertimos un array en un nuevo tipo de array, la nueva variable es en realidad una referencia al array existente, no un array nuevo. Eso es consistente con la forma en que funciona C# cuando se establecen dos variables de tiopo array iguales entre sí para los tipos por valor. 

Por ejemplo, el siguiente código hace que dos variables enteras de tipo Array se refieren al mismo array: 

int [] array1, array2;
array1 = new int [10];
array2 = array1;

Este código declara dos variables de array, inicializa array1 y luego hace que array2 se refiera al mismo array. Si cambiamos un valor en uno de los arrays, el otro contiene el mismo cambio porque array1 y array2 se refieren al mismo array. Esto puede causar confusión si no tenemos cuidado. Si deseamos crear un nuevo array en lugar de una nueva forma de referirse al  array existente, utilizaremos Array.Copy o algún otro método para copiar el array. 

Volviendo a las referencias a los arrays,  Cuando el código establece que el array de personas es igual al array de empleados, personas se refiere al mismo array de empleados. Trata los objetos dentro del array como Personas en lugar de Empleados, pero no es un array  nuevo. 

Sabiendo que personas es en realidad una referencia encubierta a un array de objetos de tipo Empleado, tiene sentido que la siguiente afirmación falle: 

personas [0] = new Persona (0);

Este código intenta guardar un nuevo objeto Persona en el array de personas. El array de personas se declara como Persona [], por lo que podríamos pensar que esto debería funcionar, pero en realidad las personas actualmente se refieren a una matriz de Empleados. No podemos almacenar una Persona en un array de Empleado (porque una Persona no es un tipo de Empleado), por lo que esta declaración lanza una excepción de tipo  ArrayTypeMismatchException en tiempo de ejecución. 

Hay que recordar que para convertir arrays por referencia en un nuevo tipo de array  en un nuevo tipo, el array en realidad no tiene ese tipo nuevo. Pero si podemos los objetos que contiene un array como si fueran del nuevo tipo. 

Podemos convertir arrays de referencias de una forma razonablemente intuitiva. Solo hay que tener en cuenta que los valores subyacentes aún tienen sus tipos originales, incluso si los estamos tratando como algo, En este ejemplo trata los objetos Empleado como objetos Persona.

No hay comentarios:

Publicar un comentario