sábado, 16 de abril de 2022

Curso avanzado de C#. Conversión de valores

La conversión permite a un programa convertir un valor de un tipo a otro tipo compatible, pero a veces es posible que deseemos convertir un valor de un tipo a un tipo incompatible. Por ejemplo, es posible que deseemos convertir el valor de cadena ‘123’ al valor int 123, o tal vez deseemos convertir el valor de cadena ‘True’ al valor booleano verdadero (true). En casos como estos, el casting no funcionará. Para convertir un valor de un tipo a un tipo incompatible, debemos utilizar algún tipo de clase auxiliar. El Framework de .NET nos proporciona tres métodos principales para este tipo de conversiones: 

-Métodos de análisis (parsing) 

-System.Convert

-System.BitConverter

Métodos de análisis (parsing)

Cada uno de los tipos de datos primitivos de C# (int, bool, double, etc.) tiene un método Parse que convierte una representación de cadena de un valor en ese tipo de datos. Por ejemplo, bool.Parse toma como argumento una cadena que representa un valor booleano como verdadero (true) y devuelve el valor booleano correspondiente verdadero (true). Estos métodos de análisis generan excepciones si su entrada está en un formato no reconocido. Por ejemplo, la declaración bool.Parse("yes") arroja una excepción del tipo FormatException porque ese método solo admite como entradas los valores “true” o “false” y devuelve sus homólogos en formato booleano.

Cuando utilizamos estos métodos para convertir la entrada del usuario, debemos tener en cuenta que se pueden generar excepciones si el usuario ingresa valores con un formato no válido. Si el usuario introduce ‘doce’ en una caja de texto donde el programa espera un entero (int), el método int.Parse devolverá una excepción. Si el usuario introduce 1E3 o 100000 donde el programa espera un entero corto, el método short.Parse devolverá un error de desbordamiento OverflowException. Podemos utilizar un bloque try-catch para proteger el programa de estas excepciones, pero para facilitar aún más la verificación de valores, cada una de estas clases también proporciona el método TryParse. Este método intenta analizar (parsear) una cadena y devuelve verdadero si tiene éxito o falso si falla. Si tiene éxito, el método también guarda el valor analizado (parseado) en una variable de salida que le pasa al método. La siguiente tabla enumera los tipos de datos más comunes que proporcionan métodos Parse y TryParse.

Curso avanzado de C#. Conversión de valores
El siguiente código muestra dos formas en que un programa puede analizar (parsear) valores enteros que se ingresan desde TextBox: 

int cantidad;

try

{

      cantidad = int.Parse(txt_Cantidad.Text);

}

catch

{

      cantidad = 1;

}

int peso;

if (!int.TryParse(txt_Peso.Text, out peso)) peso = 10;

El código declara la variable cantidad dentro de un bloque try-catch, y  utiliza int.Parse para intentar convertir el texto de la caja de texto Txt_Cantidad en un entero. Si la conversión falla, el código establece la cantidad en el valor predeterminado 1.

A continuación, el código declara la variable peso. Después utiliza int.TryParse para intentar analizar el texto de la caja de texto txt_Peso. Si el intento tiene éxito, la variable  peso contendrá el valor que ingresó el usuario. Si el intento falla, TryParse devuelve falso y el código establece el peso en el valor predeterminado 10. 

Se recomienda como mejores prácticas, evitar analizar (parsear) cuando sea posible A veces, podemos evitarlo en valores numéricos y tratar con entradas no válidas como ‘diez’ usando un control para permitir que el usuario seleccione un valor en lugar de teclearlo. Por ejemplo, podríamos utilizar un control NumericUpDown para permitir que el usuario seleccione la cantidad en lugar de teclearla en un TextBox. 

En general, los métodos de análisis funcionan bastante bien si la entrada tiene sentido. Por ejemplo, la instrucción int.Parse ("645") devuelve el valor 645 sin confusión. Incluso el método Parse del tipo de datos DateTime puede tener sentido con la mayoría de las entradas razonables. Por ejemplo, en castellano, las siguientes declaraciones se analizan como 12 de abril de 2022 a las 16:32.

DateTime.Parse ("16:32 12 de abril de 2022").ToString()

DateTime.Parse ("12 de abril de 2022 16:32").ToString() 

DateTime.Parse ("16:32 12/4/22").ToString()

DateTime.Parse ("16:32 12-4-22"). ToString() 

Los métodos de análisis no manejan bien los valores de las divisas por defecto. Por ejemplo, el código siguiente arroja un FormatException (en la configuración regional de España.): 

decimal amount = decimal.Parse("€132.422,55"); 

La razón por la que este código falla es que, por defecto, el método decimal.Parse habilita miles y separadores decimales pero no símbolos de moneda. Podemos hacer que decimal.Parse habilite los símbolos de moneda agregando otro parámetro que sea una combinación de valores definidos por la enumeración

System.Globalization.NumberStyles. Esta enumeración nos permite indicar los caracteres especiales que se deben permitir, como los símbolos de moneda, un signo inicial y paréntesis. 

La tabla muestra los valores definidos

Estilo

Descripción

None

No admite caracteres especiales. El valor debe ser un entero decimal.

AllowLeadingWhite

Admite un espacio en blanco al principio

AllowTrailingWhite

Admite un espacio en blanco al final

AllowLeadingSign

Admite un carácter de signo principal (+, -). Los caracteres válidos vienen definidos en

NumberFormatInfo.PositiveSign y NumberFormatInfo.NegativeSign.

AllowTrailingSign

Admite un carácter de signo (+, - ) al final. Los caracteres válidos vienen dados por las propiedades

NumberFormatInfo.PositiveSign y NumberFormatInfo.NegativeSign.

AllowParentheses

Admite que el valor esté entre paréntesis para indicar un valor negativo.

AllowDecimalPoint

Permite que el valor contenga un punto decimal. Si también se especifica  AllowCurrencySymbol, el símbolo de moneda admitido viene dado por la propiedad NumberFormatInfo.CurrencyDecimalSeparator. Si AllowCurrencySymbol no está especificado, el símbolo de moneda admitido vendrá dado por NumberFormatInfo.NumberDecimalSeparator.

AllowThousands

Admite separadores de miles. Si AllowCurrencySymbol también es

especificado, el separador viene dado por NumberFormatInfo.CurrencyGroupSeparator

y el número de dígitos por grupo viene dado por la propiedad  NumberFormatInfo.CurrencyGroupSizes. Si no se especifica AllowCurrencySymbol, el separador es dado por la propiedad NumberFormatInfo.NumberGroupSeparator

y el número de dígitos por grupo viene dado por la propiedad NumberFormatInfo

.NumberGroupSizes.

AllowExponent

Habilita el símbolo exponente e o E opcionalmente seguido de un signo positivo o negativo.

AllowCurrencySymbol

Habilita un símbolo de moneda. Los símbolos de moneda permitidos vienen dados por la propiedad NumberFormatInfo.CurrencySymbol.

AllowHexSpecifier

Indica que el valor está en hexadecimal. Esto no significa que la cadena de entrada

pueda comenzar con un especificador hexadecimal como 0x o & H. El valor

debe incluir solo dígitos hexadecimales.

Integer

Este es un estilo compuesto que incluye AllowLeadingWhite,

AllowTrailingWhite y AllowLeadingSign.

HexNumber

Este es un estilo compuesto que incluye AllowLeadingWhite,

AllowTrailingWhite y AllowHexSpecifier.

Number

Este es un estilo compuesto que incluye AllowLeadingWhite,

AllowTrailingWhite, AllowLeadingSign, AllowTrailingSign,

AllowDecimalPoint y AllowThousands.

Float

Este es un estilo compuesto que incluye AllowLeadingWhite,

AllowTrailingWhite, AllowLeadingSign, AllowDecimalPoint,

y AllowExponent.

Currency

Este es un estilo compuesto que incluye todos los estilos excepto AllowExponent

y AllowHexSpecifier

Any

Este es un estilo compuesto que incluye todos los estilos excepto

AllowHexSpecifier.

Si proporcionamos valores de NumberStyles, se eliminan los valores predeterminados. Por ejemplo, por defecto decimal.Parse habilita miles y separadores decimales. Si pasamos el valor NumberStyles.AllowCurrencySymbol al método, ya no habilita miles y separadores decimales. Para permitir los tres, debemos pasar al método los tres valores como en el siguiente código: 

decimal amount = decimal.Parse("€132.422,55", 

NumberStyles.AllowCurrencySymbol | 

NumberStyles.AllowThousands | 

NumberStyles.AllowDecimalPoint); 

Alternativamente, podemos pasar al método el estilo compuesto Moneda, como se muestra en el siguiente código: 

decimal amount = decimal.Parse("€132.422,55", 

NumberStyles.AllowCurrencySymbol);

Análisis relativo  al entorno local 

Los métodos de análisis son relativos al entorno local, por lo que intentan interpretar sus entradas para el entorno local en el que se ejecuta el programa. Podemos verlo en las descripciones de la tabla anterior que mencionan la clase NumberFormatInfo. Por ejemplo, el símbolo de moneda permitido está definido por la propiedad NumberFormatInfo.CurrencySymbol, y esa propiedad tendrá diferentes valores según la configuración regional del ordenador. Si el ordenador está con la configuración española, DateTime.Parse entiende la fecha de estilo español "1 de mayo de 2022", pero no comprende la versión en alemán "1. Mai 2022 ”. (Aunque si entiende la versión en inglés "May 1, 2022" en la configuración regional española o alemana). 

De forma similar, si la configuración regional del ordenador es francesa, el método int.Parse puede analizar el texto "132 422,55" pero no puede analizar el español "132.422,55". 

Todos los valores literales dentro del código C# deben usar formatos de inglés de EEUU. Por ejemplo, no importa qué configuración regional utilice nuestro ordenador, un programa de C# utilizará la variable double para "0.05"  pero no para  "0,05"  dentro de su código. 

Errores en análisis de valores de moneda

Es preciso darse cuenta de que es necesario parsear los valores de moneda. Si un TextBox debe contener valores de moneda, debemos parsearlo  correctamente para que no se le diga al usuario que "1.25 €" tiene un formato numérico no válido.

System.Convert 

La clase System.Convert proporciona más de 300 métodos (incluidas las versiones sobrecargadas) para convertir un tipo de datos en otro. Por ejemplo, el método ToInt32 convierte un valor en un entero de 32 bits (un entero int). Las diferentes versiones sobrecargadas de los métodos toman parámetros de diferentes tipos, como booleanos, bytes, DateTimes, dobles, cadenas, etc. La Tabla enumera los métodos de conversión de tipos de datos más útiles proporcionados por la clase System.Convert.

Redondeo bancario

Los métodos que se convierten en tipos integrales (ToByte, ToIntXX y ToUIntXX) usan "redondeo bancario", lo que significa que los valores se redondean al número entero más cercano, pero si hay un empate, con el valor que termina exactamente en ,5, el valor se redondea al número entero par más cercano. Por ejemplo, ToInt16(9.5) y ToInt16(10.5) ambos devuelven 10. Todos estos métodos lanzan una excepción si su resultado está fuera del rango de valores permitidos.

Por ejemplo, ToByte (255,5) devuelve 256, que es demasiado grande para caber en un byte, y ToUInt32 (–3,3) devuelve 3, porque que es menor que cero, y no cabe en un entero sin signo. El método Math.Round utiliza el redondeo bancario de forma predeterminada, pero también nos permite utilizar parámetros para indicar si debe redondearse hacia 0. Este método devuelve un resultado que es decimal o doble, por lo que a menudo el código debe usar una conversión para convertir el resultado en un tipo de datos integral como en el siguiente código:

float total = 100; 

int numItems = 7; 

int promedio = (int)Math.Round (total/numItems);

La clase System.Convert también proporciona un método ChangeType que convierte un valor en un nuevo tipo determinado por un parámetro en tiempo de ejecución. Por ejemplo

(int)Convert.ChangeType(5.5,typeof(int)) 

devuelve el entero 6. A menudo, es más fácil utilizar uno de los métodos más específicos como ToInt32 en lugar de ChangeType.

System.BitConverter 

La clase System.BitConverter proporciona métodos para convertir valores hacia y desde matrices de bytes. El método GetBytes devuelve una matriz de bytes que representa el valor que le pasa. Por ejemplo, si pasa un int (que ocupa 4 bytes de memoria) en el método, devuelve una matriz de 4 bytes que representa el valor. La clase System.BitConverter también proporciona métodos como ToInt32 y ToSingle para convertir los valores de bytes almacenados en matrices nuevamente en tipos de datos específicos. 

Por ejemplo, suponemos que una función API devuelve dos valores de 16 bits empaquetados en las mitades izquierda y derecha de un entero de 32 bits. Podemos utilizar el siguiente código para descomprimir los dos valores: 

int packedValue;

// La llamada a la función API establece packValue aquí. 

... 

// Convierte el valor empaquetado en una matriz de bytes. 

byte[]valueBytes = BitConverter.GetBytes(packedValue);

// Desempaqueta los dos valores. 

Short valor1, valor2;

valor1 = BitConverter.ToInt16 (valueBytes, 0); 

valor2 = BitConverter.ToInt16 (valueBytes, 2); 

Una vez que la función API establece el valor de packedValue, el código usa el método GetBytes de la clase BitConverter para convertir el valor en una matriz de 4 bytes. El orden de los bytes depende de si la arquitectura del ordenador big-endian (Gran indio)  o little-endian (Pequeño Indio). (Podemos utilizar el campo IsLittleEndian de BitConverter para determinar si el valor es big-endian o little-endian). Los métodos de la clase BitConverter son bastante especializados, por lo que no nos extenderemos aquí. Para obtener más información, consultar "Clase BitConverter"


No hay comentarios:

Publicar un comentario