Konwertuj ciąg na typ dopuszczający wartość null (int, double itp…)

137

Próbuję dokonać konwersji danych. Niestety, większość danych jest w łańcuchach, gdzie powinny być int lub double, itd ...

Więc mam coś takiego:

double? amount = Convert.ToDouble(strAmount);

Problem z tym podejściem polega na tym, że jeśli strAmount jest puste, jeśli jest puste, chcę, aby kwota była zerowa, więc kiedy dodam go do bazy danych, kolumna będzie pusta. Więc napisałem to:

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

Teraz to działa dobrze, ale mam teraz pięć wierszy kodu zamiast jednego. To sprawia, że ​​czytanie jest trochę trudniejsze, zwłaszcza gdy mam do konwersji dużą liczbę kolumn.

Pomyślałem, że użyję rozszerzenia klasy string i klasy generycznej, aby przekazać typ, ponieważ może to być double, int lub long. Więc spróbowałem tego:

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

Ale pojawia się błąd: nie można przekonwertować typu „string” na „T”?

Czy jest na to sposób? Nie jestem zaznajomiony z tworzeniem metod przy użyciu typów ogólnych.

Nathan Koop
źródło
1
Możliwy duplikat Generic TryParse
Michael Freidgeim

Odpowiedzi:

157

Inną rzeczą, o której należy pamiętać, jest to, że sam łańcuch może być pusty.

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
źródło
2
Możesz pominąć parametr „T type”, ponieważ nie jest używany.
Michael Meadows
1
+1, po prostu mnie pokonaj. Mały chwytak: przekonwertowana wartość musi być przypisana bezpośrednio do wyniku, a nie do wyniku. tj. „wynik = (T) konw.ConvertFrom (s);”.
LukeH
20
To może być uproszczone nieco z string.IsNullOrWhiteSpace (), jeśli używasz .Net4
Sergej Andrejev
1
@andrefadila - Do użycia: string sampleVendorId = ""; int? vendorId = sampleVendorId.ToNullable <int> ();
minerva
1
Wywołanie conv.ConvertFrom nie konwertuje typu T dopuszczającego wartość null, co sprawia, że ​​ta funkcja jest nieco intuicyjna. W tej funkcji nie potrzebujesz próby złapania. Te trzy linie kodu tworzą wszystko: if (string.IsNullOrWhiteSpace (stringObject)) return null; var conv = TypeDescriptor.GetConverter (typeof (T)); return (T?) conv.ConvertFrom (stringObject);
David,
54

Możesz spróbować użyć poniższej metody rozszerzenia:

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

W ten sposób możesz to zrobić:

double? amount = strAmount.GetValueOrNull<double>();
int? amount = strAmount.GetValueOrNull<int>();
decimal? amount = strAmount.GetValueOrNull<decimal>();
Michael Meadows
źródło
3
IMHO to najbardziej eleganckie rozwiązanie problemu
Zaffiro
4
właściwie… to rozwiązanie nie działa. changetype nie konwertuje na typy dopuszczające wartość null. zamiast tego użyj typeconverter
AaronHS
To jest to, co muszę wiedzieć ... Podczas korzystania z metody Convert.ChangeType muszę użyć bazowego typu typu Nullable. Ponieważ nie działa z wartością Nullable-Typ dla parametru conversionType.
Marcus.D
27

A co z tym:


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

Oczywiście nie uwzględnia to niepowodzenia konwersji.

John Kraft
źródło
Jeśli rzutujesz jedną ze zwracanych wartości na podwójną? (lub int ?, itp.), to będzie w stanie przekonwertować je na końcowe podwójne ?. Zobacz zmianę powyżej.
bdukes
Przepraszam za to. Zawsze zapominaj o obsadzie, dopóki kompilator nie zacznie krzyczeć. :)
John Kraft
to się nie powiedzie, jeśli nie jesteś null i spróbujesz amount.HasValue i zadeklarujesz kwotę jako zmienną.
Steve
23

Napisałem ten konwerter typów ogólnych. Działa z wartościami Nullable i standardowymi, konwertując między wszystkimi konwertowalnymi typami - nie tylko ciągiem. Obsługuje wszelkiego rodzaju scenariusze, których można się spodziewać (wartości domyślne, wartości null, inne wartości itp.)

Używam tego od około roku w dziesiątkach programów produkcyjnych, więc powinno być całkiem solidne.

    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
źródło
2
Uważam, że ignorowanie wszystkich błędów konwersji nie jest właściwe. Nie powinieneś też prawdopodobnie połykać wszelkiego rodzaju wyjątków. Przynajmniej przerzuć ponownie, OutOfMemoryExceptionjeśli nie możesz zawęzić go do ustalonego zestawu typów wyjątków.
Paul Groke
9

Możesz spróbować:

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

wykonaj własne sprawdzenie zerowe i w int?razie potrzeby zwróć . Będziesz także chciał to opakować w pliktry {}

Andrew Bullock
źródło
6

Spróbuj tego ...

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

Więc nazwij to tak ...

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
źródło
6

Podoba mi się odpowiedź Joela, ale nieco ją zmodyfikowałem, ponieważ nie jestem fanem jedzenia wyjątków.

    /// <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
źródło
5

Możesz użyć następujących z obiektami, niestety nie działa to jednak w przypadku łańcuchów.

double? amount = (double?)someObject;

Używam go do zawijania zmiennej sesji we właściwości (na stronie bazowej) .. więc moje rzeczywiste użycie to (na mojej stronie bazowej):

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

Mogę sprawdzić wartość null w logice strony:

if (base.OrganisationID == null)
    // do stuff
Scotty.NET
źródło
Cześć, dzięki, rozwiązałem to dla mnie. FYI Używałem VB.NET, a ekwiwalent VB CType(Object, Nullable(Of Double))działa dobrze ze stringami
rayzinnz
czy istnieje wersja Twojego pierwszego przykładu, której można użyć z napisami?
wazz
3

Nie da się tego obejść. Wartość null, podobnie jak metoda, jest ograniczona do używania tylko typów wartości jako argumentu. Ciąg jest typem referencyjnym i dlatego jest niezgodny z tą deklaracją.

JaredPar
źródło
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
źródło
3

Istnieje rozwiązanie ogólne (dla każdego typu). Użyteczność jest dobra, ale implementację należy poprawić: http://cleansharp.de/wordpress/2011/05/generischer-typeconverter/

Pozwala to na pisanie bardzo czystego kodu, takiego jak ten:

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

i również:

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
źródło
Kto głosował w dół, dodaj komentarz, co jest nie tak z tym uniwersalnym rozwiązaniem.
Pavel Hodek
1
Cóż, po pierwsze, w Twojej odpowiedzi jest coś bardzo nie tak, a mianowicie „możesz zapomnieć o wszystkich innych odpowiedziach”. Co byłoby błędne, nawet gdyby było prawdą (a tak nie jest). A co złego w „rozwiązaniu uniwersalnym” polega na tym, że jest pełne złej wydajności ( typeName.IndexOf? Naprawdę?) I dziwnego zachowania (pokazana TryConvertfunkcja nawet nie obsługuje poprawnie wartości null).
Paul Groke
3

Oto coś na podstawie zaakceptowanej odpowiedzi. Usunąłem try / catch, aby upewnić się, że wszystkie wyjątki nie zostaną połknięte i nie zostaną rozwiązane. Upewnij się również, że zmienna zwracana (w zaakceptowanej odpowiedzi) nigdy nie jest inicjowana dwukrotnie na nic.

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
źródło
2

Mój przykład dla typów anonimowych:

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>));
PRZYZNAĆ
źródło
2

Kolejna odmiana. Ten

  • Nie połyka wyjątków
  • Zgłasza, NotSupportedExceptionjeśli nie można przekonwertować typu string. Na przykład niestandardowa struktura bez konwertera typów.
  • W przeciwnym razie zwraca a, (T?)nulljeśli analiza ciągu nie powiedzie się. Nie ma potrzeby sprawdzania wartości null ani białych znaków.
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
źródło
1

Dodajmy jeszcze jedno podobne rozwiązanie do stosu. Ten również analizuje wyliczenia i wygląda ładnie. Bardzo bezpieczny.

/// <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
źródło
0

Ogólna odpowiedź udzielona przez „ Joela Coehoorna ” jest dobra.

Ale to jest inny sposób bez użycia tych GetConverter...lub try/catchbloków ... (nie jestem pewien, ale w niektórych przypadkach może to mieć lepszą wydajność):

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

Sposób użycia jest następujący:

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
źródło
@MassimilianoKraus może być, ale jest to prosty 12-liniowy kod, napisany raz, ale używany przez cały czas. I, jak powiedziałem, powinno / może być szybsze niż używanie tych TypeDescriptor.GetConverter... kodów. To tylko inny sposób.
S.Serpooshan