Convierta una cadena a tipo anulable (int, double, etc.)

137

Estoy intentando hacer alguna conversión de datos. Desafortunadamente, gran parte de los datos están en cadenas, donde deberían ser int o dobles, etc.

Entonces, lo que tengo es algo como:

double? amount = Convert.ToDouble(strAmount);

El problema con este enfoque es que si strAmount está vacío, si está vacío, quiero que sea nulo, por lo que cuando lo agregue a la base de datos, la columna será nula. Así que terminé escribiendo esto:

double? amount = null;
if(strAmount.Trim().Length>0)
{
    amount = Convert.ToDouble(strAmount);
}

Ahora esto funciona bien, pero ahora tengo cinco líneas de código en lugar de una. Esto hace que las cosas sean un poco más difíciles de leer, especialmente cuando tengo una gran cantidad de columnas para convertir.

Pensé que usaría una extensión de la clase de cadena y genéricos para pasar el tipo, esto se debe a que podría ser doble, int o largo. Entonces intenté esto:

public static class GenericExtension
{
    public static Nullable<T> ConvertToNullable<T>(this string s, T type) where T: struct
    {
        if (s.Trim().Length > 0)
        {
            return (Nullable<T>)s;
        }
        return null;
    }
}

Pero recibo el error: ¿No se puede convertir el tipo 'string' a 'T?'

¿Hay alguna forma de evitar esto? No estoy muy familiarizado con la creación de métodos con genéricos.

Nathan Koop
fuente
1
Posible duplicado de TryParse genérico
Michael Freidgeim

Respuestas:

157

Otra cosa a tener en cuenta es que la cadena en sí misma puede ser nula.

public static Nullable<T> ToNullable<T>(this string s) where T: struct
{
    Nullable<T> result = new Nullable<T>();
    try
    {
        if (!string.IsNullOrEmpty(s) && s.Trim().Length > 0)
        {
            TypeConverter conv = TypeDescriptor.GetConverter(typeof(T));
            result = (T)conv.ConvertFrom(s);
        }
    }
    catch { } 
    return result;
}
Joel Coehoorn
fuente
2
Puede omitir el parámetro "T type" ya que no se usa.
Michael Meadows
1
+1, solo venceme. Un pequeño detalle: el valor convertido debe asignarse directamente al resultado, no al resultado. Valor. es decir, "resultado = (T) conv.ConvertFrom (s);".
LukeH
20
Esto se puede simplificar un poco con string.IsNullOrWhiteSpace () si usa .Net4
Sergej Andrejev el
1
@andrefadila - Para usar: string sampleVendorId = ""; ¿En t? vendorId = sampleVendorId.ToNullable <int> ();
minerva
1
La llamada conv.ConvertFrom no convierte un tipo de T anulable, lo que hace que esta función sea un pequeño contador intuitivo. No necesita un intento de captura en esta función. Estas tres líneas de código lo hacen todo: if (string.IsNullOrWhiteSpace (stringObject)) return null; var conv = TypeDescriptor.GetConverter (typeof (T)); return (T?) conv.ConvertFrom (stringObject);
David
54

Puede intentar usar el siguiente método de extensión:

public static T? GetValueOrNull<T>(this string valueAsString)
    where T : struct 
{
    if (string.IsNullOrEmpty(valueAsString))
        return null;
    return (T) Convert.ChangeType(valueAsString, typeof(T));
}

De esta manera puedes hacer esto:

double? amount = strAmount.GetValueOrNull<double>();
int? amount = strAmount.GetValueOrNull<int>();
decimal? amount = strAmount.GetValueOrNull<decimal>();
Michael Meadows
fuente
3
En mi humilde opinión, esta es la solución más elegante para el problema
Zaffiro 02 de
44
en realidad ... esta solución no funciona. changetype no se convierte en tipos anulables. en su lugar use typeconverter
AaronHS
Eso es lo que necesito saber ... Tengo que usar el tipo subyacente de un tipo Nullable cuando uso el método Convert.ChangeType-Method. Porque no funciona con un Nullable-Typ para el parámetro conversionType.
Marcus.D
27

¿Qué hay de esto?


double? amount = string.IsNullOrEmpty(strAmount) ? (double?)null : Convert.ToDouble(strAmount);

Por supuesto, esto no tiene en cuenta el error de conversión.

John Kraft
fuente
Si convierte cualquiera de los valores de retorno a un doble? (¿o int ?, etc.), entonces podrá convertirlos al doble final ?. Vea el cambio arriba.
bdukes
Lo siento por eso. Siempre olvide el elenco hasta que el compilador grite. :)
John Kraft
esto fallará si no es nulo e intenta la cantidad. Tiene un valor y declara la cantidad como var.
Steve
23

Escribí este convertidor de tipo genérico. Funciona con valores Nullable y estándar, convirtiendo entre todos los tipos convertibles, no solo cadenas. Maneja todo tipo de escenarios que usted esperaría (valores predeterminados, valores nulos, otros valores, etc.)

He estado usando esto durante aproximadamente un año en docenas de programas de producción, por lo que debería ser bastante sólido.

    public static T To<T>(this IConvertible obj)
    {
        Type t = typeof(T);

        if (t.IsGenericType
            && (t.GetGenericTypeDefinition() == typeof(Nullable<>)))
        {
            if (obj == null)
            {
                return (T)(object)null;
            }
            else
            {
                return (T)Convert.ChangeType(obj, Nullable.GetUnderlyingType(t));
            }
        }
        else
        {
            return (T)Convert.ChangeType(obj, t);
        }
    }

    public static T ToOrDefault<T>
                 (this IConvertible obj)
    {
        try
        {
            return To<T>(obj);
        }
        catch
        {
            return default(T);
        }
    }

    public static bool ToOrDefault<T>
                        (this IConvertible obj,
                         out T newObj)
    {
        try
        {
            newObj = To<T>(obj);
            return true;
        }
        catch
        {
            newObj = default(T);
            return false;
        }
    }

    public static T ToOrOther<T>
                           (this IConvertible obj,
                           T other)
    {
        try
        {
            return To<T>(obj);
        }
        catch
        {
            return other;
        }
    }

    public static bool ToOrOther<T>
                             (this IConvertible obj,
                             out T newObj,
                             T other)
    {
        try
        {
            newObj = To<T>(obj);
            return true;
        }
        catch
        {
            newObj = other;
            return false;
        }
    }

    public static T ToOrNull<T>
                          (this IConvertible obj)
                          where T : class
    {
        try
        {
            return To<T>(obj);
        }
        catch
        {
            return null;
        }
    }

    public static bool ToOrNull<T>
                      (this IConvertible obj,
                      out T newObj)
                      where T : class
    {
        try
        {
            newObj = To<T>(obj);
            return true;
        }
        catch
        {
            newObj = null;
            return false;
        }
    }
TheSoftwareJedi
fuente
2
No creo que ignorar todos los errores de conversión sea lo correcto. Además, probablemente no debas tragarte todo tipo de excepciones. Al menos vuelva a lanzar OutOfMemoryExceptionsi no puede reducirlo a un conjunto fijo de tipos de excepción.
Paul Groke
9

Es posible que desee probar:

TypeConverter conv = TypeDescriptor.GetConverter(typeof(int));
conv.ConvertFrom(mystring);

haga su propio cheque nulo y devuélvalo int?si es necesario. También querrás envolver eso en untry {}

Andrew Bullock
fuente
6

Dale una oportunidad a esto ...

public delegate bool TryParseDelegate<T>(string data, out T output);

public static T? ToNullablePrimitive<T>(this string data, 
    TryParseDelegate<T> func) where T:struct
{
    string.IsNullOrEmpty(data) return null;

    T output;

    if (func(data, out output))
    {
        return (T?)output;
    }

    return null;
}

Entonces llámalo así ...

void doStuff()
{
    string foo = "1.0";

    double? myDouble = foo.ToNullablePrimitive<double>(double.TryParse);

    foo = "1";

    int? myInt = foo.ToNullablePrimitive<int>(int.TryParse);

    foo = "haha";

    int? myInt2 = foo.ToNullablePrimitive<int>(int.TryParse);
}
Adam Robinson
fuente
6

Me gusta la respuesta de Joel, pero la he modificado ligeramente, ya que no soy fanático de comer excepciones.

    /// <summary>
    /// Converts a string to the specified nullable type.
    /// </summary>
    /// <typeparam name="T">The type to convert to</typeparam>
    /// <param name="s">The string to convert</param>
    /// <returns>The nullable output</returns>
    public static T? ToNullable<T>(this string s) where T : struct
    {
        if (string.IsNullOrWhiteSpace(s))
            return null;

        TypeConverter conv = TypeDescriptor.GetConverter(typeof (T));
        return (T) conv.ConvertFrom(s);
    }

    /// <summary>
    /// Attempts to convert a string to the specified nullable primative.
    /// </summary>
    /// <typeparam name="T">The primitive type to convert to</typeparam>
    /// <param name="data">The string to convert</param>
    /// <param name="output">The nullable output</param>
    /// <returns>
    /// True if conversion is successfull, false otherwise.  Null and whitespace will
    /// be converted to null and return true.
    /// </returns>
    public static bool TryParseNullable<T>(this string data, out T? output) where T : struct
    {
        try
        {
            output = data.ToNullable<T>();
            return true;
        }
        catch
        {
            output = null;
            return false;
        }
    }
Lugar de Colin
fuente
5

Puede usar lo siguiente con objetos, desafortunadamente esto no funciona con cadenas.

double? amount = (double?)someObject;

Lo uso para ajustar una variable de sesión en una propiedad (en una página base) ... por lo que mi uso real es (en mi página base):

public int? OrganisationID
{
    get { return (int?)Session[Constants.Session_Key_OrganisationID]; }
    set { Session[Constants.Session_Key_OrganisationID] = value; }
}

Puedo verificar nulo en la lógica de página:

if (base.OrganisationID == null)
    // do stuff
Scotty.NET
fuente
Hola, gracias, esto me lo resolvió. FYI estaba usando VB.NET, y el equivalente de VB CType(Object, Nullable(Of Double))funciona bien con cadenas
rayzinnz
¿Hay una versión de su primer ejemplo que pueda usarse con cadenas?
wazz
3

No hay forma de evitar esto. Nullable, así como su método, están limitados a usar solo tipos de valor como argumento. La cadena es un tipo de referencia y, por lo tanto, es incompatible con esta declaración.

JaredPar
fuente
3
public static class GenericExtension
{
    public static T? ConvertToNullable<T>(this String s) where T : struct 
    {
        try
        {
            return (T?)TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(s);
        }
        catch (Exception)
        {
            return null;
        }
    }
}
Daniel Brückner
fuente
3

Hay una solución genérica (para cualquier tipo). La usabilidad es buena, pero la implementación debe mejorarse: http://cleansharp.de/wordpress/2011/05/generischer-typeconverter/

Esto le permite escribir código muy limpio como este:

string value = null;
int? x = value.ConvertOrDefault<int?>();

y también:

object obj = 1;  

string value = null;
int x = 5;
if (value.TryConvert(out x))
    Console.WriteLine("TryConvert example: " + x); 

bool boolean = "false".ConvertOrDefault<bool>();
bool? nullableBoolean = "".ConvertOrDefault<bool?>();
int integer = obj.ConvertOrDefault<int>();
int negativeInteger = "-12123".ConvertOrDefault<int>();
int? nullableInteger = value.ConvertOrDefault<int?>();
MyEnum enumValue = "SecondValue".ConvertOrDefault<MyEnum>();

MyObjectBase myObject = new MyObjectClassA();
MyObjectClassA myObjectClassA = myObject.ConvertOrDefault<MyObjectClassA>();
Pavel Hodek
fuente
Quién votó por favor, por favor agregue un comentario sobre lo que está mal con esta solución universal.
Pavel Hodek
1
Bueno, primero hay algo muy mal con tu respuesta, y ese es el "puedes olvidar todas las demás respuestas". Lo cual estaría mal incluso si fuera cierto (que no lo es). Y lo que está mal con la "solución universal" es que está llena de un mal rendimiento ( typeName.IndexOf? ¿Realmente?) Y un comportamiento extraño (la TryConvertfunción mostrada ni siquiera maneja los valores nulos correctamente).
Paul Groke
3

Aquí hay algo basado en la respuesta aceptada. Eliminé el try / catch para asegurarme de que todas las excepciones no se traguen y no se traten. También se aseguró de que la variable de retorno (en la respuesta aceptada) nunca se inicialice dos veces por nada.

public static Nullable<T> ToNullable<T>(this string s) where T: struct
{
    if (!string.IsNullOrWhiteSpace(s))
    {
        TypeConverter conv = TypeDescriptor.GetConverter(typeof(T));

        return (T)conv.ConvertFrom(s);
    }

    return default(Nullable<T>);
}
PhilDulac
fuente
2

Mi ejemplo para tipos anónimos:

private object ConvertNullable(object value, Type nullableType)
{
    Type resultType = typeof(Nullable<>).MakeGenericType(nullableType.GetGenericArguments());
    return Activator.CreateInstance(resultType, Convert.ChangeType(value, nullableType.GetGenericArguments()[0]));
}

...

Type anonimousType = typeof(Nullable<int>);
object nullableInt1 = ConvertNullable("5", anonimousType);
// or evident Type
Nullable<int> nullableInt2 = (Nullable<int>)ConvertNullable("5", typeof(Nullable<int>));
ADMITIR
fuente
2

Otra variante. Éste

  • No traga excepciones
  • Lanza un NotSupportedExceptionsi no se puede convertir el tipo string. Por ejemplo, una estructura personalizada sin un convertidor de tipos.
  • De lo contrario, devuelve un (T?)nullsi la cadena no se analiza. No es necesario verificar si hay espacios en blanco o nulos.
using System.ComponentModel;

public static Nullable<T> ToNullable<T>(this string s) where T : struct
{
    var ret = new Nullable<T>();
    var conv = TypeDescriptor.GetConverter(typeof(T));

    if (!conv.CanConvertFrom(typeof(string)))
    {
        throw new NotSupportedException();
    }

    if (conv.IsValid(s))
    {
        ret = (T)conv.ConvertFrom(s);
    }

    return ret;
}
BurnsBA
fuente
1

Agreguemos una solución más similar a la pila. Este también analiza enumeraciones, y se ve bien. Muy seguro.

/// <summary>
    /// <para>More convenient than using T.TryParse(string, out T). 
    /// Works with primitive types, structs, and enums.
    /// Tries to parse the string to an instance of the type specified.
    /// If the input cannot be parsed, null will be returned.
    /// </para>
    /// <para>
    /// If the value of the caller is null, null will be returned.
    /// So if you have "string s = null;" and then you try "s.ToNullable...",
    /// null will be returned. No null exception will be thrown. 
    /// </para>
    /// <author>Contributed by Taylor Love (Pangamma)</author>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="p_self"></param>
    /// <returns></returns>
    public static T? ToNullable<T>(this string p_self) where T : struct
    {
        if (!string.IsNullOrEmpty(p_self))
        {
            var converter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(T));
            if (converter.IsValid(p_self)) return (T)converter.ConvertFromString(p_self);
            if (typeof(T).IsEnum) { T t; if (Enum.TryParse<T>(p_self, out t)) return t;}
        }

        return null;
    }

https://github.com/Pangamma/PangammaUtilities-CSharp/blob/master/PangammaUtilities/Extensions/ToNullableStringExtension.cs

Pangamma
fuente
0

La respuesta genérica proporcionada por " Joel Coehoorn " es buena.

Pero, esta es otra forma sin usar esos GetConverter...o try/catchbloques ... (no estoy seguro, pero esto puede tener un mejor rendimiento en algunos casos):

public static class StrToNumberExtensions
{
    public static short ToShort(this string s, short defaultValue = 0) => short.TryParse(s, out var v) ? v : defaultValue;
    public static int ToInt(this string s, int defaultValue = 0) => int.TryParse(s, out var v) ? v : defaultValue;
    public static long ToLong(this string s, long defaultValue = 0) => long.TryParse(s, out var v) ? v : defaultValue;
    public static decimal ToDecimal(this string s, decimal defaultValue = 0) => decimal.TryParse(s, out var v) ? v : defaultValue;
    public static float ToFloat(this string s, float defaultValue = 0) => float.TryParse(s, out var v) ? v : defaultValue;
    public static double ToDouble(this string s, double defaultValue = 0) => double.TryParse(s, out var v) ? v : defaultValue;

    public static short? ToshortNullable(this string s, short? defaultValue = null) => short.TryParse(s, out var v) ? v : defaultValue;
    public static int? ToIntNullable(this string s, int? defaultValue = null) => int.TryParse(s, out var v) ? v : defaultValue;
    public static long? ToLongNullable(this string s, long? defaultValue = null) => long.TryParse(s, out var v) ? v : defaultValue;
    public static decimal? ToDecimalNullable(this string s, decimal? defaultValue = null) => decimal.TryParse(s, out var v) ? v : defaultValue;
    public static float? ToFloatNullable(this string s, float? defaultValue = null) => float.TryParse(s, out var v) ? v : defaultValue;
    public static double? ToDoubleNullable(this string s, double? defaultValue = null) => double.TryParse(s, out var v) ? v : defaultValue;
}

El uso es el siguiente:

var x1 = "123".ToInt(); //123
var x2 = "abc".ToInt(); //0
var x3 = "abc".ToIntNullable(); // (int?)null 
int x4 = ((string)null).ToInt(-1); // -1
int x5 = "abc".ToInt(-1); // -1

var y = "19.50".ToDecimal(); //19.50

var z1 = "invalid number string".ToDoubleNullable(); // (double?)null
var z2 = "invalid number string".ToDoubleNullable(0); // (double?)0
S.Serpooshan
fuente
Puede ser @MassimilianoKraus, pero es un código simple de 12 líneas, escrito una vez, pero que se usa todas las veces. Y, como dije, debería / puede ser más rápido que usar esos TypeDescriptor.GetConverter... códigos. Esta es solo otra forma.
S.Serpooshan