Convert.ChangeType () gagal pada Jenis Nullable

301

Saya ingin mengonversi string ke nilai properti objek, yang namanya saya miliki sebagai string. Saya mencoba melakukan ini seperti ini:

string modelProperty = "Some Property Name";
string value = "SomeValue";
var property = entity.GetType().GetProperty(modelProperty);
if (property != null) {
    property.SetValue(entity, 
        Convert.ChangeType(value, property.PropertyType), null);
}

Masalahnya adalah ini gagal dan melempar Pengecualian Cast Tidak Valid ketika tipe properti adalah tipe nullable. Ini bukan kasus dari nilai-nilai yang tidak dapat Dikonversi - nilai-nilai itu akan berfungsi jika saya melakukan ini secara manual (misalnya DateTime? d = Convert.ToDateTime(value);) Saya telah melihat beberapa pertanyaan serupa tetapi masih tidak dapat membuatnya berfungsi.

iboeno
sumber
1
Saya menggunakan ExecuteScalar <int?> Dengan PetaPoco 4.0.3 dan gagal karena alasan yang sama: return (T) Convert. ChangeType (val, typeof (T)) pada baris 554
Larry

Jawaban:

409

Belum diuji, tetapi mungkin sesuatu seperti ini akan berfungsi:

string modelProperty = "Some Property Name";
string value = "Some Value";

var property = entity.GetType().GetProperty(modelProperty);
if (property != null)
{
    Type t = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;

    object safeValue = (value == null) ? null : Convert.ChangeType(value, t);

    property.SetValue(entity, safeValue, null);
}
LukeH
sumber
12
Aku hanya butuh sepotong kode itu sendiri. Terima kasih untuk Nullable.GetUnderlyingType! Banyak membantu saya ketika saya membangun ModelBinder orang miskin untuk proyek yang membutuhkannya. Aku berhutang bir padamu!
Maxime Rouiller
3
Mungkin bukannya (value == null) ? nulldigunakan (value == null) ? default(t)?
threadster
Tampaknya tidak berfungsi untuk pengenal unik ke string.
Anders Lindén
Apakah ada alasan khusus untuk membuat safeValuevariabel yang bertentangan dengan hanya menugaskan kembali value?
coloradocolby
@ pembaca angka Anda tidak dapat menggunakan operator default pada variabel tipe 'Tipe'. Lihat stackoverflow.com/questions/325426/…
andy250
75

Anda harus mendapatkan tipe yang mendasarinya untuk melakukan itu ...

Coba ini, saya berhasil menggunakannya dengan obat generik:

//Coalesce to get actual property type...
Type t = property.PropertyType();
t = Nullable.GetUnderlyingType(t) ?? t;

//Coalesce to set the safe value using default(t) or the safe type.
safeValue = value == null ? default(t) : Convert.ChangeType(value, t);

Saya menggunakannya di sejumlah tempat dalam kode saya, salah satu contohnya adalah metode pembantu yang saya gunakan untuk mengonversi nilai database dengan cara typesafe:

public static T GetValue<T>(this IDataReader dr, string fieldName)
{
    object value = dr[fieldName];

    Type t = typeof(T);
    t = Nullable.GetUnderlyingType(t) ?? t;

    return (value == null || DBNull.Value.Equals(value)) ? 
        default(T) : (T)Convert.ChangeType(value, t);
}

Disebut menggunakan:

string field1 = dr.GetValue<string>("field1");
int? field2 = dr.GetValue<int?>("field2");
DateTime field3 = dr.GetValue<DateTime>("field3");

Saya menulis serangkaian posting blog termasuk ini di http://www.endswithsaurus.com/2010_07_01_archive.html (Gulir ke bawah ke Addendum, @JohnMacintyre benar-benar melihat bug dalam kode asli saya yang membawa saya ke jalan yang sama dengan Anda sekarang). Saya memiliki beberapa modifikasi kecil karena posting yang mencakup konversi jenis enum juga jadi jika properti Anda adalah Enum Anda masih dapat menggunakan metode panggilan yang sama. Cukup tambahkan baris untuk memeriksa jenis enum dan Anda pergi ke balapan menggunakan sesuatu seperti:

if (t.IsEnum)
    return (T)Enum.Parse(t, value);

Biasanya Anda akan memiliki beberapa kesalahan memeriksa atau menggunakan TryParse bukan Parse, tetapi Anda mendapatkan gambarnya.

BenAlabaster
sumber
Terima kasih - Saya masih kehilangan langkah atau tidak memahami sesuatu. Saya mencoba menetapkan nilai properti, mengapa saya mendapatkan objek yang berada di bawahnya? Saya juga tidak yakin bagaimana cara mendapatkan dari kode saya ke metode ekstensi seperti milik Anda. Saya tidak akan tahu apa jenisnya untuk melakukan sesuatu seperti nilai. Bantuan <Int32?> ().
iboeno
@iboeno - Maaf, ada rapat sehingga saya tidak bisa membantu Anda menghubungkan titik-titik. Senang Anda punya solusinya.
BenAlabaster
9

Ini agak panjang-ish untuk contoh, tapi ini adalah pendekatan yang relatif kuat, dan memisahkan tugas casting dari nilai yang tidak diketahui ke tipe yang tidak diketahui

Saya memiliki metode TryCast yang melakukan sesuatu yang serupa, dan memperhitungkan jenis yang dapat dibatalkan.

public static bool TryCast<T>(this object value, out T result)
{
    var type = typeof (T);

    // If the type is nullable and the result should be null, set a null value.
    if (type.IsNullable() && (value == null || value == DBNull.Value))
    {
        result = default(T);
        return true;
    }

    // Convert.ChangeType fails on Nullable<T> types.  We want to try to cast to the underlying type anyway.
    var underlyingType = Nullable.GetUnderlyingType(type) ?? type;

    try
    {
        // Just one edge case you might want to handle.
        if (underlyingType == typeof(Guid))
        {
            if (value is string)
            {
                value = new Guid(value as string);
            }
            if (value is byte[])
            {
                value = new Guid(value as byte[]);
            }

            result = (T)Convert.ChangeType(value, underlyingType);
            return true;
        }

        result = (T)Convert.ChangeType(value, underlyingType);
        return true;
    }
    catch (Exception ex)
    {
        result = default(T);
        return false;
    }
}

Tentu saja TryCast adalah Metode dengan Tipe Parameter, jadi untuk menyebutnya secara dinamis, Anda harus membuat sendiri MethodInfo:

var constructedMethod = typeof (ObjectExtensions)
    .GetMethod("TryCast")
    .MakeGenericMethod(property.PropertyType);

Kemudian untuk menetapkan nilai properti aktual:

public static void SetCastedValue<T>(this PropertyInfo property, T instance, object value)
{
    if (property.DeclaringType != typeof(T))
    {
        throw new ArgumentException("property's declaring type must be equal to typeof(T).");
    }

    var constructedMethod = typeof (ObjectExtensions)
        .GetMethod("TryCast")
        .MakeGenericMethod(property.PropertyType);

    object valueToSet = null;
    var parameters = new[] {value, null};
    var tryCastSucceeded = Convert.ToBoolean(constructedMethod.Invoke(null, parameters));
    if (tryCastSucceeded)
    {
        valueToSet = parameters[1];
    }

    if (!property.CanAssignValue(valueToSet))
    {
        return;
    }
    property.SetValue(instance, valueToSet, null);
}

Dan metode ekstensi untuk berurusan dengan properti. Dapat Menandai Nilai ...

public static bool CanAssignValue(this PropertyInfo p, object value)
{
    return value == null ? p.IsNullable() : p.PropertyType.IsInstanceOfType(value);
}

public static bool IsNullable(this PropertyInfo p)
{
    return p.PropertyType.IsNullable();
}

public static bool IsNullable(this Type t)
{
    return !t.IsValueType || Nullable.GetUnderlyingType(t) != null;
}
bopapa_1979
sumber
6

Saya memiliki kebutuhan yang sama, dan jawaban dari LukeH menunjuk ke arah saya. Saya datang dengan fungsi generik ini untuk membuatnya mudah.

    public static Tout CopyValue<Tin, Tout>(Tin from, Tout toPrototype)
    {
        Type underlyingT = Nullable.GetUnderlyingType(typeof(Tout));
        if (underlyingT == null)
        { return (Tout)Convert.ChangeType(from, typeof(Tout)); }
        else
        { return (Tout)Convert.ChangeType(from, underlyingT); }
    }

Penggunaannya seperti ini:

        NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty);

Perhatikan bahwa parameter kedua hanya digunakan sebagai prototipe untuk menunjukkan fungsi cara melemparkan nilai kembali, sehingga sebenarnya tidak harus menjadi properti tujuan. Berarti Anda dapat melakukan juga melakukan sesuatu seperti ini:

        DateTime? source = new DateTime(2015, 1, 1);
        var dest = CopyValue(source, (string)null);

Saya melakukannya dengan cara ini daripada menggunakan keluar karena Anda tidak dapat menggunakan dengan properti. Seperti itu, ia dapat bekerja dengan properti dan variabel. Anda juga bisa membuat overload untuk meneruskan jenisnya jika Anda mau.

Steve In CO
sumber
0

Terima kasih @ Lukas
saya sedikit berubah:

public static object convertToPropType(PropertyInfo property, object value)
{
    object cstVal = null;
    if (property != null)
    {
        Type propType = Nullable.GetUnderlyingType(property.PropertyType);
        bool isNullable = (propType != null);
        if (!isNullable) { propType = property.PropertyType; }
        bool canAttrib = (value != null || isNullable);
        if (!canAttrib) { throw new Exception("Cant attrib null on non nullable. "); }
        cstVal = (value == null || Convert.IsDBNull(value)) ? null : Convert.ChangeType(value, propType);
    }
    return cstVal;
}
hs586sd46s
sumber
0

Saya melakukannya dengan cara ini

public static List<T> Convert<T>(this ExcelWorksheet worksheet) where T : new()
    {
        var result = new List<T>();
        int colCount = worksheet.Dimension.End.Column;  //get Column Count
        int rowCount = worksheet.Dimension.End.Row;

        for (int row = 2; row <= rowCount; row++)
        {
            var obj = new T();
            for (int col = 1; col <= colCount; col++)
            {

                var value = worksheet.Cells[row, col].Value?.ToString();
                PropertyInfo propertyInfo = obj.GetType().GetProperty(worksheet.Cells[1, col].Text);
                propertyInfo.SetValue(obj, Convert.ChangeType(value, Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType), null);

            }
            result.Add(obj);
        }

        return result;
    }
AnishJain87
sumber