Convertir une chaîne en type Nullable (int, double, etc…)

137

J'essaye d'effectuer une conversion de données. Malheureusement, la plupart des données sont dans des chaînes, où elles devraient être des int ou double, etc ...

Donc, ce que j'ai, c'est quelque chose comme:

double? amount = Convert.ToDouble(strAmount);

Le problème avec cette approche est que si strAmount est vide, s'il est vide, je veux qu'il soit nul, donc lorsque je l'ajoute dans la base de données, la colonne sera nulle. Alors j'ai fini par écrire ceci:

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

Maintenant, cela fonctionne bien, mais j'ai maintenant cinq lignes de code au lieu d'une. Cela rend les choses un peu plus difficiles à lire, surtout lorsque j'ai un grand nombre de colonnes à convertir.

Je pensais que j'utiliserais une extension de la classe de chaîne et des génériques pour passer le type, c'est parce que cela pourrait être un double, ou un int, ou un long. Alors j'ai essayé ceci:

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

Mais j'obtiens l'erreur: impossible de convertir le type «chaîne» en «T?»

Y a-t-il un moyen de contourner ceci? Je ne suis pas très familier avec la création de méthodes utilisant des génériques.

Nathan Koop
la source
1
Reproduction
Michael Freidgeim

Réponses:

157

Une autre chose à garder à l'esprit est que la chaîne elle-même peut être nulle.

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;
}
Joël Coehoorn
la source
2
Vous pouvez omettre le paramètre "T type" car il n'est pas utilisé.
Michael Meadows
1
+1, il suffit de me battre. Un petit détail: la valeur convertie doit être affectée directement au résultat, pas au résultat. c'est-à-dire "résultat = (T) conv.ConvertFrom (s);".
LukeH
20
Cela peut être un peu simplifié avec string.IsNullOrWhiteSpace () si vous utilisez .Net4
Sergej Andrejev
1
@andrefadila - À utiliser: string sampleVendorId = ""; int? vendorId = sampleVendorId.ToNullable <int> ();
minerva le
1
L'appel conv.ConvertFrom ne convertit pas un type Nullable de T, ce qui rend cette fonction un peu contre-intuitive. Vous n'avez pas besoin d'un essai dans cette fonction. Ces trois lignes de code font tout: if (string.IsNullOrWhiteSpace (stringObject)) return null; var conv = TypeDescriptor.GetConverter (typeof (T)); return (T?) conv.ConvertFrom (stringObject);
David
54

Vous pouvez essayer d'utiliser la méthode d'extension ci-dessous:

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 cette façon, vous pouvez faire ceci:

double? amount = strAmount.GetValueOrNull<double>();
int? amount = strAmount.GetValueOrNull<int>();
decimal? amount = strAmount.GetValueOrNull<decimal>();
Michael Meadows
la source
3
À
mon humble avis,
4
en fait .. cette solution ne fonctionne pas. changetype ne se convertit pas en types Nullable. à la place, utilisez typeconverter
AaronHS
C'est ce que j'ai besoin de savoir ... Je dois utiliser le type sous-jacent d'un Nullable-Type lors de l'utilisation de la méthode Convert.ChangeType. Parce qu'il ne fonctionne pas avec un Nullable-Typ pour le paramètre conversionType.
Marcus.D
27

Et ça:


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

Bien sûr, cela ne prend pas en compte l'échec de la conversion.

John Kraft
la source
Si vous transtypez l'une des valeurs de retour en double? (ou int ?, etc.), il pourra alors les convertir en double final ?. Voir le changement ci-dessus.
bdukes
Désolé pour ça. Oubliez toujours le casting jusqu'à ce que le compilateur hurle. :)
John Kraft
cela échouera si vous n'êtes pas nul et que vous essayez amount.HasValue et déclarez montant comme var.
Steve
23

J'ai écrit ce convertisseur de type générique. Il fonctionne avec les valeurs Nullable et standard, convertissant entre tous les types convertibles - pas seulement une chaîne. Il gère toutes sortes de scénarios auxquels vous vous attendez (valeurs par défaut, valeurs nulles, autres valeurs, etc.)

Je l'utilise depuis environ un an dans des dizaines de programmes de production, donc ça devrait être assez solide.

    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
la source
2
Je ne pense pas qu'ignorer toutes les erreurs de conversion soit la bonne chose à faire. Aussi, vous ne devriez probablement pas avaler toutes sortes d'exceptions. Au moins, relancez OutOfMemoryExceptionsi vous ne pouvez pas le réduire à un ensemble fixe de types d'exceptions.
Paul Groke
9

Vous voudrez peut-être essayer:

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

faites votre propre contrôle nul et retournez int?si nécessaire. Vous voudrez également envelopper cela dans untry {}

Andrew Bullock
la source
6

Donnez-moi une chance ...

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

Alors appelez-le comme ça ...

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
la source
6

J'aime la réponse de Joel, mais je l'ai légèrement modifiée car je ne suis pas fan des exceptions.

    /// <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;
        }
    }
Colin Place
la source
5

Vous pouvez utiliser ce qui suit avec des objets, mais cela ne fonctionne malheureusement pas avec des chaînes.

double? amount = (double?)someObject;

Je l'utilise pour envelopper une variable de session dans une propriété (sur une page de base) .. donc mon utilisation réelle est (dans ma page de base):

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

Je suis capable de vérifier la valeur null dans la logique de la page:

if (base.OrganisationID == null)
    // do stuff
Scotty.NET
la source
Salut merci, cela a résolu le problème pour moi. Pour info, j'utilisais VB.NET, et l'équivilant VB CType(Object, Nullable(Of Double))fonctionne bien avec les chaînes
rayzinnz
existe-t-il une version de votre premier exemple qui peut être utilisée avec des chaînes?
wazz
3

Il n'y a aucun moyen de contourner cela. Nullable, ainsi que votre méthode, est contrainte d'utiliser uniquement des types valeur comme argument. String est un type de référence et est donc incompatible avec cette déclaration.

JaredPar
la source
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
la source
3

Il existe une solution générique (pour tout type). La convivialité est bonne, mais la mise en œuvre doit être améliorée: http://cleansharp.de/wordpress/2011/05/generischer-typeconverter/

Cela vous permet d'écrire du code très propre comme celui-ci:

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

et aussi:

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
la source
Qui a voté contre, veuillez ajouter un commentaire sur ce qui ne va pas avec cette solution universelle.
Pavel Hodek
1
Eh bien, d'abord il y a quelque chose qui ne va pas dans votre réponse, et c'est le "vous pouvez oublier toutes les autres réponses". Ce qui serait faux même si c'était vrai (ce qui n'est pas le cas). Et ce qui ne va pas avec la «solution universelle», c'est qu'elle est pleine de mauvaises performances ( typeName.IndexOf? Vraiment?) Et d'un comportement étrange (la TryConvertfonction montrée ne gère même pas correctement les valeurs nulles).
Paul Groke
3

Voici quelque chose basé sur une réponse acceptée. J'ai supprimé le try / catch pour m'assurer que toutes les exceptions ne sont ni avalées ni traitées. Assurez-vous également que la variable de retour (dans la réponse acceptée) n'est jamais initialisée deux fois pour rien.

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
la source
2

Mon exemple pour les types anonymes:

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>));
ADMETTRE
la source
2

Une autre variante. Celui-là

  • N'avale pas les exceptions
  • Lance un NotSupportedExceptionsi le type ne peut pas être converti à partir de string. Par exemple, une structure personnalisée sans convertisseur de type.
  • Sinon, retourne a (T?)nullsi l'analyse de la chaîne échoue. Pas besoin de vérifier la valeur nulle ou les espaces.
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
la source
1

Ajoutons une autre solution similaire à la pile. Celui-ci analyse également les énumérations et il a l'air bien. Très sûr.

/// <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
la source
0

La réponse générique fournie par " Joel Coehoorn " est bonne.

Mais, c'est une autre façon sans utiliser ceux-ci GetConverter...ou les try/catchblocs ... (je ne suis pas sûr mais cela peut avoir de meilleures performances dans certains cas):

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'utilisation est la suivante:

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
la source
@MassimilianoKraus peut être, mais c'est un simple code de 12 lignes, écrit une fois, mais en utilisant tout le temps. Et, comme je l'ai dit, cela devrait / pourrait être plus rapide que d'utiliser ces TypeDescriptor.GetConverter... codes. C'est juste une autre façon.
S.Serpooshan