Convert.ChangeType () không thành công trên các loại Nullable


301

Tôi muốn chuyển đổi một chuỗi thành một giá trị thuộc tính đối tượng, mà tôi có tên là một chuỗi. Tôi đang cố gắng để làm điều này như vậy:

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

Vấn đề là điều này không thành công và ném Ngoại lệ không hợp lệ khi loại thuộc tính là loại không thể. Đây không phải là trường hợp các giá trị không thể được chuyển đổi - chúng sẽ hoạt động nếu tôi thực hiện thủ công (ví dụ DateTime? d = Convert.ToDateTime(value);) Tôi đã thấy một số câu hỏi tương tự nhưng vẫn không thể làm cho nó hoạt động.


1
Tôi đang sử dụng ExecuteScalar <int?> Với PetaPoco 4.0.3 và nó không thành công vì cùng một lý do: return (T) Convert.ChangeType (val, typeof (T)) tại dòng 554
Larry

Câu trả lời:


409

Chưa được kiểm tra, nhưng có thể một cái gì đó như thế này sẽ hoạt động:

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
Chỉ cần đoạn mã đó cho mình. Cảm ơn Nullable.GetUnderellingType! Đã giúp tôi rất nhiều khi tôi xây dựng ModelBinder cho một người nghèo cho một dự án cần nó. Tôi nợ bạn một chầu bia!
Maxime Rouiller

3
Có lẽ thay vì (value == null) ? nullsử dụng (value == null) ? default(t)?
luồng

Dường như không hoạt động đối với định danh duy nhất cho chuỗi.
Anders Lindén

Có bất kỳ lý do cụ thể để tạo ra các safeValuebiến trái ngược với chỉ gán lại value?
coloradatioby

@threadster Bạn không thể sử dụng biến ona toán tử mặc định của loại 'Loại'. Xem stackoverflow.com/questions/325426/ trên
andy250

75

Bạn phải lấy loại cơ bản để làm điều đó ...

Hãy thử điều này, tôi đã sử dụng nó thành công với thuốc generic:

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

Tôi sử dụng nó ở một số vị trí trong mã của mình, một ví dụ là phương thức trợ giúp tôi sử dụng để chuyển đổi giá trị cơ sở dữ liệu theo cách an toàn:

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

Được gọi bằng cách sử dụng:

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

Tôi đã viết một loạt các bài đăng trên blog bao gồm điều này tại http://www.endswithsaurus.com/2010_07_01_archive.html (Cuộn xuống Phụ lục, @JohnMacintyre thực sự phát hiện ra lỗi trong mã ban đầu của tôi dẫn tôi đến con đường tương tự bạn ngay bây giờ). Tôi có một vài sửa đổi nhỏ kể từ bài đăng đó bao gồm chuyển đổi các loại enum vì vậy nếu thuộc tính của bạn là Enum, bạn vẫn có thể sử dụng lệnh gọi phương thức tương tự. Chỉ cần thêm một dòng vào để kiểm tra các loại enum và bạn đã tham gia vào các cuộc đua bằng cách sử dụng một cái gì đó như:

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

Thông thường, bạn sẽ có một số lỗi kiểm tra hoặc sử dụng TryPude thay vì Parse, nhưng bạn có được hình ảnh.


Cảm ơn - Tôi vẫn còn thiếu một bước hoặc không hiểu điều gì đó. Tôi đang cố gắng đặt giá trị thuộc tính, tại sao tôi lại nhận được đối tượng thuộc Loại dưới quyền? Tôi cũng không chắc chắn làm thế nào để chuyển từ mã của tôi sang một phương thức mở rộng như của bạn. Tôi sẽ không biết loại này sẽ là gì để làm một cái gì đó như value .elper <Int32?> ().
iboeno

@iboeno - Xin lỗi, đã tắt trong một cuộc họp vì vậy tôi không thể giúp bạn kết nối các dấu chấm. Vui mừng bạn đã có một giải pháp mặc dù.
BenAlabaster

9

Đây là một ví dụ hơi dài, nhưng đây là một cách tiếp cận tương đối mạnh mẽ và tách biệt nhiệm vụ truyền từ giá trị không xác định sang loại không xác định

Tôi có một phương thức TryCast thực hiện một cái gì đó tương tự và đưa các loại nullable vào tài khoản.

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

Tất nhiên, TryCast là một Phương thức có Tham số Loại, vì vậy để gọi nó một cách linh hoạt, bạn phải tự xây dựng Phương thức:

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

Sau đó, để đặt giá trị tài sản thực tế:

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

Và các phương thức mở rộng để đối phó với property.CanAssignValue ...

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

Tôi có một nhu cầu tương tự, và câu trả lời từ LukeH chỉ cho tôi hướng đi. Tôi đã đưa ra chức năng chung này để làm cho nó dễ dàng.

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

Cách sử dụng là như thế này:

        NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty);

Lưu ý tham số thứ hai chỉ được sử dụng làm nguyên mẫu để hiển thị chức năng làm thế nào để truyền giá trị trả về, vì vậy nó không thực sự phải là thuộc tính đích. Có nghĩa là bạn cũng có thể làm một cái gì đó như thế này:

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

Tôi đã làm theo cách này thay vì sử dụng ra vì bạn không thể sử dụng với các thuộc tính. Như là, nó có thể làm việc với các thuộc tính và các biến. Bạn cũng có thể tạo một quá tải để vượt qua loại thay vì nếu bạn muốn.


0

Cảm ơn @LukeH
tôi đã thay đổi một chút:

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

Tôi đã làm theo cách này

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;
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.