Mengonversi string menjadi tipe nullable (int, double, dll…)

138

Saya mencoba melakukan beberapa konversi data. Sayangnya, banyak datanya dalam string, yang seharusnya int atau double, dll ...

Jadi yang saya punya adalah seperti:

double? amount = Convert.ToDouble(strAmount);

Masalah dengan pendekatan ini adalah jika strAmount kosong, jika kosong saya ingin nilainya menjadi nol, jadi ketika saya menambahkannya ke dalam database, kolom akan menjadi null. Jadi saya akhirnya menulis ini:

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

Sekarang ini berfungsi dengan baik, tetapi sekarang saya memiliki lima baris kode, bukan satu. Ini membuat hal-hal menjadi sedikit lebih sulit untuk dibaca, terutama ketika saya memiliki banyak kolom untuk diubah.

Saya pikir saya akan menggunakan ekstensi ke kelas string dan generik untuk meneruskan tipe, ini karena bisa jadi ganda, atau int, atau panjang. Jadi saya mencoba ini:

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

Tapi saya mendapatkan kesalahan: Tidak dapat mengubah jenis 'string' menjadi 'T?'

Apakah ada jalan keluarnya? Saya tidak terlalu terbiasa dengan membuat metode menggunakan obat generik.

Nathan Koop
sumber
1
Kemungkinan duplikat dari Generic TryParse
Michael Freidgeim

Jawaban:

160

Hal lain yang perlu diingat adalah bahwa string itu sendiri mungkin nol.

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
sumber
2
Anda dapat menghilangkan parameter "tipe T" karena tidak digunakan.
Michael Meadows
1
+1, Pukul saja aku untuk itu. Sebuah nitpick kecil: nilai yang dikonversi harus ditetapkan langsung ke hasil, bukan ke hasil. yaitu, "result = (T) conv.ConvertFrom (s);".
LukeH
20
Ini dapat disederhanakan sedikit dengan string.IsNullOrWhiteSpace () jika Anda menggunakan .Net4
Sergej Andrejev
1
@andrefadila - Untuk menggunakan: string sampleVendorId = ""; int? vendorId = sampleVendorId.ToNullable <int> ();
minerva
1
Panggilan konv.ConvertFrom tidak mengubah tipe T nullable, yang membuat fungsi ini sedikit berlawanan dengan intuisi. Anda tidak perlu mencoba menangkap fungsi ini. Ketiga baris kode ini membuat semuanya: if (string.IsNullOrWhiteSpace (stringObject)) return null; var conv = TypeDescriptor.GetConverter (typeof (T)); return (T?) conv.ConvertFrom (stringObject);
David
56

Anda dapat mencoba menggunakan metode ekstensi di bawah ini:

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

Dengan cara ini Anda dapat melakukan ini:

double? amount = strAmount.GetValueOrNull<double>();
int? amount = strAmount.GetValueOrNull<int>();
decimal? amount = strAmount.GetValueOrNull<decimal>();
Michael Meadows
sumber
3
IMHO ini adalah solusi paling elegan untuk masalah ini
Zaffiro
4
sebenarnya .. solusi ini tidak berhasil. changetype tidak mengonversi ke tipe nullable. sebagai gantinya gunakan typeconverter
AaronHS
Itulah yang perlu saya ketahui ... Saya harus menggunakan tipe dasar Nullable-Type saat menggunakan Convert.ChangeType-Method. Karena tidak berfungsi dengan Nullable-Typ untuk parameter conversionType.
Marcus.D
27

Bagaimana dengan ini:


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

Tentu saja, ini tidak memperhitungkan kegagalan konversi.

John Kraft
sumber
Jika Anda memasukkan salah satu dari nilai yang dikembalikan menjadi ganda? (atau int ?, dll), maka itu akan dapat mengubahnya menjadi ganda terakhir ?. Lihat perubahan di atas.
bdukes
Maaf soal itu. Selalu lupakan pemerannya sampai kompiler berteriak. :)
John Kraft
ini akan gagal jika Anda tidak null dan Anda mencoba jumlah.HasValue dan menyatakan jumlah sebagai var.
Steve
23

Saya menulis konverter tipe generik ini. Ia bekerja dengan nilai Nullable dan standar, mengonversi di antara semua jenis konversi - bukan hanya string. Ini menangani semua jenis skenario yang Anda harapkan (nilai default, nilai null, nilai lain, dll ...)

Saya telah menggunakan ini selama sekitar satu tahun di lusinan program produksi, jadi ini seharusnya cukup solid.

    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
sumber
2
Saya tidak berpikir mengabaikan semua kesalahan konversi adalah hal yang benar untuk dilakukan. Anda juga sebaiknya tidak menelan semua jenis pengecualian. Setidaknya lempar ulang OutOfMemoryExceptionjika Anda tidak dapat mempersempitnya menjadi satu set tipe pengecualian tetap.
Paul Groke
9

Anda mungkin ingin mencoba:

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

lakukan pemeriksaan nol Anda sendiri dan kembalikan int?jika perlu. Anda juga ingin membungkusnya dengan filetry {}

Andrew Bullock
sumber
6

Coba ini ...

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

Lalu sebut saja seperti ini ...

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

Saya suka jawaban Joel, tapi saya telah mengubahnya sedikit karena saya bukan penggemar makan pengecualian.

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

Anda dapat menggunakan berikut ini dengan objek, sayangnya ini tidak berfungsi dengan string.

double? amount = (double?)someObject;

Saya menggunakannya untuk membungkus variabel sesi di properti (di halaman dasar) .. jadi penggunaan aktual saya adalah (di halaman dasar saya):

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

Saya dapat memeriksa null di logika halaman:

if (base.OrganisationID == null)
    // do stuff
Scotty.NET
sumber
Hai terima kasih ini menyelesaikannya untuk saya. FYI saya menggunakan VB.NET, dan kesetaraan VB CType(Object, Nullable(Of Double))berfungsi dengan baik dengan string
rayzinnz
apakah ada versi dari contoh pertama Anda yang dapat digunakan dengan string?
wazz
3

Tidak ada jalan lain. Nullable, serta metode Anda, dibatasi untuk hanya menggunakan tipe nilai sebagai argumennya. String adalah tipe referensi dan karenanya tidak kompatibel dengan deklarasi ini.

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

Ada solusi umum (untuk semua jenis). Kegunaannya bagus, tetapi implementasi harus ditingkatkan: http://cleansharp.de/wordpress/2011/05/generischer-typeconverter/

Ini memungkinkan Anda untuk menulis kode yang sangat bersih seperti ini:

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

dan juga:

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
sumber
Siapa yang downvoting tolong tambahkan komentar ada apa dengan solusi universal ini.
Pavel Hodek
1
Pertama-tama, ada sesuatu yang salah dengan jawaban Anda, dan itu adalah "Anda bisa melupakan semua jawaban lainnya". Mana yang salah meskipun itu benar (yang sebenarnya tidak). Dan apa yang salah dengan "solusi universal" adalah bahwa ini penuh dengan kinerja buruk ( typeName.IndexOf? Benarkah?) Dan perilaku aneh ( TryConvertfungsi yang ditampilkan bahkan tidak menangani nilai null dengan benar).
Paul Groke
3

Ini sesuatu berdasarkan jawaban yang diterima. Saya menghapus coba / tangkap untuk memastikan semua pengecualian tidak tertelan dan tidak ditangani. Juga pastikan bahwa variabel return (dalam jawaban yang diterima) tidak pernah diinisialisasi dua kali secara gratis.

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
sumber
2

Contoh saya untuk tipe anonimous:

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>));
MENGAKUI
sumber
2

Variasi lainnya. Yang ini

  • Tidak menelan pengecualian
  • Melempar NotSupportedExceptionjika tipe tidak dapat dikonversi string. Misalnya, struct kustom tanpa pengonversi tipe.
  • Sebaliknya mengembalikan (T?)nulljika string gagal untuk mengurai. Tidak perlu memeriksa null atau whitespace.
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
sumber
1

Mari tambahkan satu lagi solusi serupa ke tumpukan. Yang ini juga mengurai enum, dan terlihat bagus. Sangat aman.

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

Jawaban umum yang diberikan oleh " Joel Coehoorn " itu bagus.

Tapi, ini adalah cara lain tanpa menggunakan itu GetConverter...atau try/catchblok ... (saya tidak yakin tetapi ini mungkin memiliki kinerja yang lebih baik dalam beberapa kasus):

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

Penggunaannya adalah sebagai berikut:

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
sumber
@MassimilianoKraus mungkin, tetapi ini adalah kode 12 baris sederhana, ditulis sekali, tetapi menggunakan sepanjang waktu. Dan, seperti yang saya katakan, seharusnya / mungkin lebih cepat daripada menggunakan TypeDescriptor.GetConverter... kode-kode itu. Ini hanyalah cara lain.
S. Serpooshan