Converter string em tipo anulável (int, duplo, etc ...)

137

Estou tentando fazer alguma conversão de dados. Infelizmente, muitos dos dados estão em strings, onde devem ser int's ou duplos, etc ...

Então, o que eu tenho é algo como:

double? amount = Convert.ToDouble(strAmount);

O problema com essa abordagem é que se strAmount estiver vazio, se estiver vazio, quero que o valor seja nulo; portanto, quando o adiciono ao banco de dados, a coluna será nula. Então acabei escrevendo isso:

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

Agora isso funciona bem, mas agora tenho cinco linhas de código em vez de uma. Isso torna as coisas um pouco mais difíceis de ler, especialmente quando tenho uma grande quantidade de colunas para converter.

Eu pensei que usaria uma extensão para a classe string e os genéricos para passar o tipo, isso ocorre porque poderia ser um duplo, um int ou um longo. Então eu tentei isso:

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

Mas recebo o erro: Não é possível converter o tipo 'string' para 'T?'

Existe uma maneira de contornar isso? Não estou muito familiarizado com a criação de métodos usando genéricos.

Nathan Koop
fonte
1
Possível duplicata do TryParse genérico
Michael Freidgeim

Respostas:

157

Outra coisa a ter em mente é que a própria string pode 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
fonte
2
Você pode omitir o parâmetro "tipo T", pois não é usado.
22615 Michael Meadows
1
+1, apenas me derrote. Uma pequena nitpick: o valor convertido precisa ser atribuído diretamente ao resultado, não ao resultado.Valor. ou seja, "resultado = (T) conv.ConvertFrom (s);".
LukeH
20
Isso pode ser simplificado um pouco com string.IsNullOrWhiteSpace () se você usar .Net4
Sergej Andrejev
1
@andrefadila - Para usar: string sampleVendorId = ""; int? vendorId = sampleVendorId.ToNullable <int> ();
Minerva
1
A chamada conv.ConvertFrom não converte um tipo anulável de T, o que torna essa função um pouco contra-intuitiva. Você não precisa tentar capturar nesta função. Essas três linhas de código fazem tudo: if (string.IsNullOrWhiteSpace (stringObject)) retorna null; var conv = TypeDescriptor.GetConverter (typeof (T)); return (T?) conv.ConvertFrom (stringObject);
David
54

Você pode tentar usar o método de extensão abaixo:

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

Dessa forma, você pode fazer isso:

double? amount = strAmount.GetValueOrNull<double>();
int? amount = strAmount.GetValueOrNull<int>();
decimal? amount = strAmount.GetValueOrNull<decimal>();
Michael Meadows
fonte
3
IMHO esta é a solução mais elegante para o problema
Zaffiro
4
na verdade .. esta solução não funciona. changetype não converte em tipos anuláveis. em vez disso, use typeconverter
AaronHS 27/11
É isso que eu preciso saber ... Preciso usar o tipo subjacente de um Tipo Nulo ao usar o Método Convert.ChangeType. Porque ele não funciona com um Nullable-Typ para o parâmetro conversionType.
Marcus.D
27

Que tal isso:


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

Obviamente, isso não leva em consideração a falha na conversão.

John Kraft
fonte
Se você converter um dos valores de retorno para um dobro? (ou int ?, etc), será possível convertê-los para o dobro final ?. Veja a alteração acima.
bdukes
Me desculpe por isso. Sempre esqueça o elenco até o compilador gritar. :)
John Kraft
isso falhará se você não for nulo e tentar a quantidade. Tem valor e declara a quantidade como var.
Steve
23

Eu escrevi este conversor de tipo genérico. Ele funciona com valores nulos e padrão, convertendo entre todos os tipos conversíveis - não apenas com strings. Ele lida com todos os tipos de cenários que você esperaria (valores padrão, valores nulos, outros valores, etc ...)

Uso isso há cerca de um ano em dezenas de programas de produção, portanto deve ser bem 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
fonte
2
Não acho que ignorar todos os erros de conversão seja a coisa certa a fazer. Além disso, você provavelmente não deve engolir todos os tipos de exceções. Pelo menos lance novamente OutOfMemoryExceptionse você não puder reduzi-lo a um conjunto fixo de tipos de exceção.
Paul Groke
9

Você pode tentar:

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

faça sua própria verificação nula e retorne, int?se necessário. Você também deseja agrupar isso em umtry {}

Andrew Bullock
fonte
6

Dê a isso uma 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;
}

Então chame assim ...

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
fonte
6

Gosto da resposta de Joel, mas a modifiquei um pouco, pois não sou fã de comer exceções.

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

Você pode usar o seguinte com objetos, infelizmente isso não funciona com cadeias de caracteres.

double? amount = (double?)someObject;

Eu o uso para agrupar uma variável de sessão em uma propriedade (em uma página base). Portanto, meu uso real é (na minha página base):

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

Eu sou capaz de verificar se há nulo na lógica da página:

if (base.OrganisationID == null)
    // do stuff
Scotty.NET
fonte
Oi obrigado isso resolveu para mim. FYI eu estava usando VB.NET, eo equivilant VB CType(Object, Nullable(Of Double))funciona bem com cordas
rayzinnz
existe uma versão do seu primeiro exemplo que pode ser usada com strings?
wazz 8/04
3

Não há como contornar isso. Anulável, assim como seu método, é restrito a usar apenas tipos de valor como argumento. String é um tipo de referência e, portanto, é incompatível com esta declaração.

JaredPar
fonte
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
fonte
3

Existe uma solução genérica (para qualquer tipo). A usabilidade é boa, mas a implementação deve ser aprimorada: http://cleansharp.de/wordpress/2011/05/generischer-typeconverter/

Isso permite que você escreva um código muito limpo como este:

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

e também:

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
fonte
Quem fez voto negativo, por favor, adicione um comentário sobre o que há de errado com esta solução universal.
Pavel Hodek
1
Bem, primeiro há algo muito errado com sua resposta, e esse é o "você pode esquecer todas as outras respostas". O que seria errado, mesmo que fosse verdade (o que não é). E o que há de errado com a "solução universal" é que ela apresenta desempenho ruim ( typeName.IndexOf? Sério?) E comportamento estranho (a TryConvertfunção mostrada nem sequer manipula valores nulos corretamente).
Paul Groke
3

Aqui está algo baseado na resposta aceita. Eu removi o try / catch para garantir que todas as exceções não sejam engolidas e não sejam tratadas. Também garantiu que a variável de retorno (na resposta aceita) nunca seja inicializada duas vezes 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
fonte
2

Meu exemplo 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>));
ADMITEM
fonte
2

Outra variação. Este

  • Não engole exceções
  • Lança a NotSupportedExceptionse o tipo não puder ser convertido string. Por exemplo, uma estrutura personalizada sem um conversor de tipos.
  • Caso contrário, retorna a (T?)nullse a sequência falhar ao analisar. Não é necessário verificar se há espaço nulo ou em branco.
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
fonte
1

Vamos adicionar mais uma solução semelhante à pilha. Este também analisa enums, e parece bom. Muito 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
fonte
0

A resposta genérica fornecida por " Joel Coehoorn " é boa.

Mas, essa é outra maneira sem usar esses GetConverter...ou try/catchblocos ... (não tenho certeza, mas isso pode ter um desempenho melhor em alguns 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;
}

O uso é o seguinte:

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
fonte
O @MassimilianoKraus pode ser, mas é um código simples de 12 linhas, escrito uma vez, mas usando o tempo todo. E, como eu disse, deveria / pode ser mais rápido do que usar esses TypeDescriptor.GetConverter... códigos. Esta é apenas outra maneira.
S.Serpooshan 17/03