Converti la stringa in tipo nullable (int, double, ecc ...)


137

Sto tentando di fare qualche conversione di dati. Sfortunatamente, molti dei dati sono in stringhe, dove dovrebbero essere int o double, ecc ...

Quindi quello che ho è qualcosa di simile:

double? amount = Convert.ToDouble(strAmount);

Il problema con questo approccio è se strAmount è vuoto, se è vuoto voglio che sia pari a null, quindi quando lo aggiungo al database la colonna sarà nulla. Quindi ho finito per scrivere questo:

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

Ora funziona bene, ma ora ho cinque righe di codice anziché una. Questo rende le cose un po 'più difficili da leggere, specialmente quando ho una grande quantità di colonne da convertire.

Ho pensato di usare un'estensione per la classe stringa e generici per passare il tipo, questo perché potrebbe essere un doppio, un int o un lungo. Quindi ho provato questo:

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;
    }
}

Ma viene visualizzato l'errore: Impossibile convertire il tipo 'stringa' in 'T?'

C'è un modo per aggirare questo? Non ho molta familiarità con la creazione di metodi usando i generici.


1
Possibile duplicato di TryParse generico
Michael Freidgeim,

Risposte:


157

Un'altra cosa da tenere a mente è che la stringa stessa potrebbe essere nulla.

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;
}

2
È possibile omettere il parametro "Tipo T" poiché non viene utilizzato.
Michael Meadows,

1
+1, battimi e basta. Un piccolo nitpick: il valore convertito deve essere assegnato direttamente al risultato, non al risultato. Valore. cioè, "risultato = (T) conv.ConvertFrom (s);".
Luca,

20
Questo può essere semplificato un po 'con string.IsNullOrWhiteSpace () se si utilizza .Net4
Sergej Andrejev

1
@andrefadila - Per usare: string sampleVendorId = ""; int? vendorId = sampleVendorId.ToNullable <int> ();
minerva,

1
La chiamata conv.ConvertFrom non converte un tipo nulla di T, il che rende intuitiva questa funzione un piccolo contatore. Non è necessario provare a catturare in questa funzione. Queste tre righe di codice rendono tutto: if (string.IsNullOrWhiteSpace (stringObject)) restituisce null; var conv = TypeDescriptor.GetConverter (typeof (T)); return (T?) conv.ConvertFrom (stringObject);
David

54

Puoi provare a utilizzare il seguente metodo di estensione:

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

In questo modo puoi farlo:

double? amount = strAmount.GetValueOrNull<double>();
int? amount = strAmount.GetValueOrNull<int>();
decimal? amount = strAmount.GetValueOrNull<decimal>();

3
IMHO questa è la soluzione più elegante al problema
Zaffiro,

4
in realtà .. questa soluzione non funziona. changetype non converte in tipi nullable. invece usa typeconverter
AaronHS il

Questo è quello che devo sapere ... Devo usare il tipo sottostante di un tipo Nullable quando uso il metodo Convert.ChangeType. Perché non funziona con un Nullable-Typ per il parametro conversionType.
Marcus.D,

27

Che dire di questo:


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

Naturalmente, questo non tiene conto del fallimento della conversione.


Se si cast di uno dei valori di ritorno in un doppio? (o int ?, ecc.), allora sarà in grado di convertirli nel doppio finale ?. Vedi la modifica sopra.
bdukes il

Mi dispiace per quello. Dimentica sempre il cast fino a quando il compilatore urla. :)
John Kraft,

questo fallirà se non sei nullo e provi la quantità.HasValue e dichiara la quantità come var.
Steve

23

Ho scritto questo convertitore di tipo generico. Funziona con valori Nullable e standard, convertendo tra tutti i tipi convertibili, non solo la stringa. Gestisce tutti i tipi di scenari che ti aspetteresti (valori predefiniti, valori null, altri valori, ecc ...)

Lo uso da circa un anno in dozzine di programmi di produzione, quindi dovrebbe essere piuttosto solido.

    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;
        }
    }

2
Non credo che ignorare tutti gli errori di conversione sia la cosa giusta da fare. Inoltre, probabilmente non dovresti ingoiare tutti i tipi di eccezioni. Almeno ripeti OutOfMemoryExceptionse non puoi restringerlo a un set fisso di tipi di eccezione.
Paul Groke,

9

Potresti provare:

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

fai il tuo controllo null e restituisci int?se necessario. Ti consigliamo anche di avvolgerlo in atry {}


6

Prova questo ...

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;
}

Quindi chiamalo così ...

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);
}

6

Mi piace la risposta di Joel, ma l'ho modificata leggermente perché non sono un fan delle eccezioni alimentari.

    /// <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;
        }
    }

5

Puoi usare quanto segue con gli oggetti, sfortunatamente questo non funziona con le stringhe.

double? amount = (double?)someObject;

Lo uso per racchiudere una variabile di sessione in una proprietà (su una pagina di base) .. quindi il mio utilizzo effettivo è (nella mia pagina di base):

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

Sono in grado di controllare null nella logica della pagina:

if (base.OrganisationID == null)
    // do stuff

Ciao grazie questo risolto per me. Cordiali saluti, stavo usando VB.NET e l'equivalente VB CType(Object, Nullable(Of Double))funziona bene con le stringhe
rayzinnz,

esiste una versione del tuo primo esempio che può essere utilizzata con le stringhe?
Wazz,

3

Non c'è modo di aggirarlo. Nullable, così come il tuo metodo, è costretto a usare solo tipi di valore come argomento. String è un tipo di riferimento e quindi non è compatibile con questa dichiarazione.


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;
        }
    }
}

3

Esiste una soluzione generica (per qualsiasi tipo). L'usabilità è buona, ma l'implementazione dovrebbe essere migliorata: http://cleansharp.de/wordpress/2011/05/generischer-typeconverter/

Questo ti permette di scrivere un codice molto pulito come questo:

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

e anche:

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>();

Chi ha effettuato il downvoting, aggiungi un commento che cosa non va in questa soluzione universale.
Pavel Hodek,

1
Bene, prima c'è qualcosa di molto sbagliato nella tua risposta, e questo è il "puoi dimenticare tutte le altre risposte". Il che sarebbe sbagliato anche se fosse vero (cosa che non lo è). E cosa c'è di sbagliato nella "soluzione universale" è che è pieno di cattive prestazioni ( typeName.IndexOf? Davvero?) E comportamento strano (la TryConvertfunzione mostrata non gestisce nemmeno correttamente i valori null).
Paul Groke,

3

Ecco qualcosa basato sulla risposta accettata. Ho rimosso il tentativo / cattura per assicurarmi che tutte le eccezioni non vengano ingoiate e non trattate. Assicurati anche che la variabile return (nella risposta accettata) non venga mai inizializzata due volte per niente.

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>);
}

2

Il mio esempio per i tipi anonimi:

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>));

2

Un'altra variazione. Questo

  • Non ingoia eccezioni
  • Genera a NotSupportedExceptionse il tipo non può essere convertito string. Ad esempio, una struttura personalizzata senza un convertitore di tipi.
  • Altrimenti restituisce a (T?)nullse la stringa non riesce ad analizzare. Non è necessario verificare la presenza di null o spazi bianchi.
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;
}

1

Aggiungiamo un'altra soluzione simile allo stack. Questo analizza anche enumerazioni e sembra carino. Molto sicuro.

/// <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


0

La risposta generica fornita da " Joel Coehoorn " è buona.

Ma questo è un altro modo senza usare questi GetConverter...o try/catchblocchi ... (non sono sicuro, ma in alcuni casi potrebbe avere prestazioni migliori):

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;
}

L'utilizzo è il seguente:

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

@MassimilianoKraus può essere, ma è un semplice codice di 12 righe, scritto una volta, ma che utilizza tutte le volte. E, come ho detto, dovrebbe / potrebbe essere più veloce dell'uso di quei TypeDescriptor.GetConverter... codici. Questo è solo un altro modo.
S. Serpooshan il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.