Преобразование строки в тип, допускающий значение NULL (int, double и т. Д.)

138

Я пытаюсь преобразовать данные. К сожалению, большая часть данных находится в строках, где они должны быть int или double и т. Д.

Итак, у меня есть что-то вроде:

double? amount = Convert.ToDouble(strAmount);

Проблема с этим подходом заключается в том, что если strAmount пуст, если он пуст, я хочу, чтобы сумма была равна нулю, поэтому, когда я добавляю его в базу данных, столбец будет нулевым. В итоге я написал следующее:

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

Теперь это работает нормально, но теперь у меня пять строк кода вместо одной. Это затрудняет чтение, особенно когда мне нужно преобразовать большое количество столбцов.

Я думал, что использую расширение для строкового и универсального классов для передачи типа, потому что это может быть double, int или long. Итак, я попробовал это:

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

Но я получаю сообщение об ошибке: не удается преобразовать строку типа в T?

Есть ли способ обойти это? Я не очень знаком с созданием методов с использованием дженериков.

Натан Куп
источник
1
Возможный дубликат Generic TryParse
Майкл Фрейджейм

Ответы:

160

Также следует иметь в виду, что сама строка может быть нулевой.

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
Параметр «Тип Т» можно не указывать, поскольку он не используется.
Майкл Медоуз
1
+1, Просто опередите меня. Небольшая придирка: преобразованное значение нужно напрямую присвоить результату, а не результату. т.е. «результат = (T) conv.ConvertFrom (s);».
LukeH
20
Это можно немного упростить с помощью string.IsNullOrWhiteSpace (), если вы используете .Net4
Сергей Андреев
1
@andrefadila - Для использования: string sampleVendorId = ""; int? vendorId = sampleVendorId.ToNullable <int> ();
Минерва
1
Вызов conv.ConvertFrom не преобразует тип T, допускающий значение NULL, что делает эту функцию интуитивно понятной. Вам не нужно пытаться поймать эту функцию. Эти три строки кода делают все: if (string.IsNullOrWhiteSpace (stringObject)) return null; var conv = TypeDescriptor.GetConverter (typeof (T)); return (T?) conv.ConvertFrom (stringObject);
Дэвид
56

Вы можете попробовать использовать следующий метод расширения:

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

Таким образом вы можете сделать это:

double? amount = strAmount.GetValueOrNull<double>();
int? amount = strAmount.GetValueOrNull<int>();
decimal? amount = strAmount.GetValueOrNull<decimal>();
Майкл Медоуз
источник
3
ИМХО, это наиболее элегантное решение проблемы
Заффиро 02
4
на самом деле .. это решение не работает. changetype не преобразуется в типы, допускающие значение NULL. вместо этого используйте typeconverter
AaronHS
Это то, что мне нужно знать ... Мне нужно использовать базовый тип Nullable-Type при использовании Convert.ChangeType-Method. Потому что он не работает с Nullable-Typ для параметра conversionType.
Marcus.D
27

Как насчет этого:


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

Конечно, при этом не учитывается сбой конвертации.

Джон Крафт
источник
Если вы приведете любое из возвращаемых значений к двойному? (или int? и т. д.), тогда он сможет преобразовать их в окончательный double ?. См. Изменение выше.
bdukes
Прости за это. Всегда забывайте приведение, пока компилятор не закричит. :)
Джон Крафт
это не удастся, если вы не равны нулю и попробуете amount.HasValue и объявите сумму как var.
Стив
23

Я написал этот универсальный преобразователь типов. Он работает с значениями Nullable и стандартными значениями, конвертируя между всеми конвертируемыми типами, а не только строкой. Он обрабатывает все возможные сценарии (значения по умолчанию, нулевые значения, другие значения и т. Д.)

Я использую его около года в десятках производственных программ, так что он должен быть довольно надежным.

    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
Я не думаю, что игнорирование всех ошибок преобразования является правильным решением. Также, вероятно, не стоит проглатывать все виды исключений. По крайней мере, повторно выбросьте, OutOfMemoryExceptionесли вы не можете сузить его до фиксированного набора типов исключений.
Paul Groke
9

Вы можете попробовать:

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

сделайте свою собственную нулевую проверку и верните int?при необходимости. Вы также захотите обернуть это вtry {}

Эндрю Баллок
источник
6

Попробуйте это ...

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

Тогда назовите это так ...

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

Мне нравится ответ Джоэла, но я немного изменил его, так как не люблю есть исключения.

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

Вы можете использовать следующее с объектами, но, к сожалению, это не работает со строками.

double? amount = (double?)someObject;

Я использую его для обертывания переменной сеанса в свойстве (на базовой странице) .. так что мое фактическое использование (на моей базовой странице):

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

Я могу проверить наличие null в логике страницы:

if (base.OrganisationID == null)
    // do stuff
Scotty.NET
источник
Привет, спасибо, это решило это для меня. К вашему сведению, я использовал VB.NET, и эквивалент VB CType(Object, Nullable(Of Double))отлично работает со строками
rayzinnz
есть ли версия вашего первого примера, которую можно использовать со строками?
wazz
3

Нет никакого способа обойти это. Nullable, как и ваш метод, ограничивается использованием только типов значений в качестве аргумента. String является ссылочным типом и поэтому несовместим с этим объявлением.

ДжаредПар
источник
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

Есть универсальное решение (на любой тип). Удобство использования хорошее, но реализация должна быть улучшена: http://cleansharp.de/wordpress/2011/05/generischer-typeconverter/

Это позволяет вам писать очень чистый код, например:

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

а также:

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>();
Павел Ходек
источник
Кто голосовал против, пожалуйста, прокомментируйте, что не так с этим универсальным решением.
Павел Ходек
1
Ну, во-первых, с вашим ответом что-то не так, и это «вы можете забыть все остальные ответы». Что было бы неправильно, даже если бы это было правдой (а это не так). И что не так с «универсальным решением», так это то, что оно полно плохой производительности ( typeName.IndexOf? Действительно?) И странного поведения (показанная TryConvertфункция даже не обрабатывает нулевые значения правильно).
Пол Грок
3

Вот что-то, основанное на принятом ответе. Я удалил команду try / catch, чтобы убедиться, что все исключения не проглочены и не обработаны. Также убедитесь, что возвращаемая переменная (в принятом ответе) никогда не инициализируется дважды даром.

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

Мой пример для анонимных типов:

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

Еще одна вариация. Вот этот

  • Не глотает исключения
  • Выбрасывает, NotSupportedExceptionесли тип не может быть преобразован из string. Например, настраиваемая структура без преобразователя типов.
  • В противном случае возвращает a, (T?)nullесли строку не удалось проанализировать. Нет необходимости проверять нуль или пробел.
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

Добавим в стек еще одно подобное решение. Этот также анализирует перечисления, и это выглядит красиво. Очень безопасно.

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

Общий ответ Джоэла Кохорна хорош.

Но это другой способ без использования тех GetConverter...или иных try/catchблоков ... (я не уверен, но в некоторых случаях это может иметь лучшую производительность):

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

Использование следующее:

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 может быть, но это простой 12-строчный код, написанный один раз, но использующийся постоянно. И, как я уже сказал, это должно / может быть быстрее, чем использование этих TypeDescriptor.GetConverter... кодов. Это просто другой способ.
С.Серпушан