Convert.ChangeType (), Nullable Türlerinde başarısız oluyor


301

Bir dize adını bir dize olarak sahip bir nesne özellik değerine dönüştürmek istiyorum. Bunu böyle yapmaya çalışıyorum:

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

Sorun, bu başarısız oluyor ve özellik türü null olabilecek bir tür olduğunda Geçersiz Oyunculuk İstisnası atmaktır. Bu değerlerin dönüştürülememesi durumu değildir - bunu manuel olarak yaparsam çalışırlar (örneğin DateTime? d = Convert.ToDateTime(value);) Bazı benzer sorular gördüm ama yine de işe yaramaz.


1
PetaPoco 4.0.3 ile ExecuteScalar <int?> Kullanıyorum ve aynı nedenden dolayı başarısız: satır 554
Larry

Yanıtlar:


409

Test edilmedi, ancak belki böyle bir şey işe yarayacak:

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

12
Sadece bu kod parçasına ihtiyacım vardı. Nullable.GetUnderlyingType için teşekkürler! İhtiyacı olan bir proje için fakir bir adamın ModelBinder'ini kurduğumda bana çok yardımcı oldu. Sana bir bira borçluyum!
Maxime Rouiller

3
Belki (value == null) ? nullkullanmak yerine (value == null) ? default(t)?
threadster

String için uniqueidentifier için çalışmıyor gibi görünüyor.
Anders Lindén

safeValueDeğişkeni sadece yeniden atamanın aksine oluşturmak için belirli bir neden var mı value?
coloradocolby

@threadster 'Tür' türünde varsayılan operatör ona değişkeni kullanamazsınız. Bkz. Stackoverflow.com/questions/325426/…
andy250

75

Bunu yapmak için altta yatan türü almak zorunda ...

Bunu deneyin, jeneriklerle başarılı bir şekilde kullandım:

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

Benim kod birkaç yerde kullanın, bir örnek veritabanı değerlerini typesafe bir şekilde dönüştürmek için kullandığım yardımcı bir yöntemdir:

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

Şunu kullanarak aradı:

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

Http://www.endswithsaurus.com/2010_07_01_archive.html de dahil olmak üzere bir dizi blog yazısı yazdım (Addendum'a ilerleyin, @JohnMacintyre , orijinal kodumdaki hatayı benimle aynı yolda buldum. şimdi). Enum türlerinin dönüşümünü de içeren bu yazı, mülkünüz bir Enum ise yine de aynı yöntem çağrısını kullanabilirsiniz, çünkü birkaç küçük değişiklik var. Numaralandırma türlerini kontrol etmek için bir satır ekleyin ve yarışlara aşağıdaki gibi bir şey kullanarak çıkın:

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

Normalde Parse yerine hata kontrolü yapar veya TryParse kullanırsınız, ancak resmi alırsınız.


Teşekkürler - Hala bir adım eksik ya da bir şey anlamadım. Bir özellik değeri ayarlamaya çalışıyorum, neden onun altında yatan türü alıyorum? Ayrıca benim kod gibi sizin gibi bir uzantı yöntemi almak nasıl emin değilim. Değer gibi bir şey yapmak için türün ne olacağını bilmeyeceğim.Helper <Int32?> ().
iboeno

@iboeno - Üzgünüz, bir toplantıya çıkmıştım, bu yüzden noktaları birleştirmenize yardımcı olamadım. Gerçi bir çözüm buldun.
BenAlabaster

9

Bu, bir örnek için biraz uzun bir süreçtir, ancak bu nispeten sağlam bir yaklaşımdır ve döküm görevini bilinmeyen değerden bilinmeyen türe ayırır

Ben benzer bir şey yapar ve null olabilecek türleri dikkate alır bir TryCast yöntemi var.

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

Elbette TryCast, Type Parametreli bir Yöntemdir, bu nedenle dinamik olarak çağırmak için MethodInfo'yu kendiniz oluşturmanız gerekir:

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

Ardından gerçek özellik değerini ayarlamak için:

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

Ve property.CanAssignValue ile başa çıkmak için uzatma yöntemleri ...

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

6

Benzer bir ihtiyacım vardı ve LukeH'nin cevabı bana yön verdi. Bunu kolaylaştırmak için bu genel işlevi buldum.

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

Kullanımı şöyle:

        NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty);

İkinci parametrenin sadece dönüş değerinin nasıl kullanılacağını göstermek için bir prototip olarak kullanıldığını unutmayın, bu nedenle aslında target özelliği olmak zorunda değildir. Yani şu şekilde de yapabilirsiniz:

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

Bir çıkış kullanmak yerine bu şekilde yaptım çünkü özellikleri ile kullanamazsınız. Olduğu gibi, özellikleri ve değişkenleri ile çalışabilir. İsterseniz, türü geçmek için aşırı yük de oluşturabilirsiniz.


0

Teşekkürler @ Luke
Biraz değiştirdim:

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

0

Bu şekilde yaptım

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;
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.