Convert.ChangeType () ล้มเหลวใน Nullable Types


301

ฉันต้องการแปลงสตริงเป็นค่าคุณสมบัติวัตถุที่ชื่อฉันมีเป็นสตริง ฉันพยายามทำเช่นนี้:

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

ปัญหาคือว่านี่คือความล้มเหลวและการโยนการยกเว้นการโยนไม่ถูกต้องเมื่อประเภทคุณสมบัติเป็นประเภทที่ไม่สามารถใช้ได้ นี่ไม่ใช่กรณีของค่าที่ไม่สามารถแปลงได้ - จะใช้งานได้หากฉันทำด้วยตนเอง (เช่นDateTime? d = Convert.ToDateTime(value);) ฉันเคยเห็นคำถามที่คล้ายกัน แต่ยังไม่สามารถใช้งานได้


1
ฉันใช้ ExecuteScalar <int?> กับ PetaPoco 4.0.3 และล้มเหลวด้วยเหตุผลเดียวกัน: return (T) Convert.ChangeType (val, typeof (T)) ที่บรรทัด 554
Larry

คำตอบ:


409

ยังไม่ทดลอง แต่อาจเป็นเช่นนี้:

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
แค่ต้องการรหัสชิ้นนั้นเอง ขอบคุณสำหรับ Nullable.GetUnderlyingType! ช่วยฉันออกมามากมายเมื่อฉันสร้าง ModelBinder ของชายยากจนสำหรับโครงการที่ต้องการ ฉันเป็นหนี้คุณเบียร์!
Maxime Rouiller

3
อาจจะแทนที่จะ(value == null) ? nullใช้(value == null) ? default(t)?
กระทู้

ดูเหมือนจะไม่ทำงานสำหรับ Uniqueidentifier ให้เป็นสตริง
Anders Lindén

มีเหตุผลใดที่จะสร้างsafeValueตัวแปรเมื่อเทียบกับการมอบหมายใหม่ให้value?
coloradocolby

@threadter คุณไม่สามารถใช้โอเปอเรเตอร์เริ่มต้นกับตัวแปร 'ประเภท' ดูstackoverflow.com/questions/325426/…
andy250

75

คุณจะต้องได้รับประเภทพื้นฐานเพื่อที่จะทำเช่นนั้น ...

ลองนี้ฉันใช้กับยาชื่อสามัญสำเร็จแล้ว:

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

ฉันใช้มันในหลาย ๆ ที่ในรหัสของฉันตัวอย่างหนึ่งคือวิธีการช่วยเหลือที่ฉันใช้สำหรับการแปลงค่าฐานข้อมูลในลักษณะที่ปลอดภัย:

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

เรียกว่าใช้:

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 (เลื่อนลงไปที่ภาคผนวก@JohnMacintyreจริงเห็นข้อผิดพลาดในรหัสเดิมของฉันซึ่งทำให้ฉันลงเส้นทางเดียวกับที่คุณกำลัง ในตอนนี้) ฉันมีการแก้ไขเล็กน้อยตั้งแต่โพสต์ที่มีการแปลงประเภท enum ด้วยดังนั้นหากคุณสมบัติของคุณเป็น Enum คุณยังคงสามารถใช้วิธีการโทรเดียวกัน เพียงเพิ่มบรรทัดในเพื่อตรวจสอบประเภท enum และคุณออกไปแข่งโดยใช้สิ่งที่ชอบ:

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

โดยปกติคุณจะมีข้อผิดพลาดในการตรวจสอบหรือใช้ TryParse แทนการแยกวิเคราะห์ แต่คุณได้รับรูปภาพ


ขอบคุณ - ฉันยังขาดขั้นตอนหรือไม่เข้าใจบางสิ่ง ฉันกำลังพยายามตั้งค่าคุณสมบัติทำไมฉันถึงได้วัตถุที่อยู่ในประเภทอ้างอิง? ฉันยังไม่แน่ใจว่าจะได้รับจากรหัสของฉันไปยังวิธีการขยายเช่นเดียวกับคุณได้อย่างไร ฉันไม่ทราบว่าจะใช้ประเภทใดเพื่อทำบางสิ่งเช่นความคุ้มค่าความช่วยเหลือ <Int32?> ()
iboeno

@iboeno - ขออภัยถูกปิดการประชุมดังนั้นฉันไม่สามารถช่วยคุณเชื่อมต่อจุดต่าง ๆ ได้ ดีใจที่คุณมีทางออก
BenAlabaster

9

นี่เป็นตัวอย่างที่ยาวนิดหน่อย แต่นี่เป็นวิธีที่ค่อนข้างแข็งแกร่งและแยกงานการหล่อจากค่าที่ไม่รู้จักกับประเภทที่ไม่รู้จัก

ฉันมีวิธี TryCast ที่ทำสิ่งที่คล้ายกันและคำนึงถึงประเภท nullable

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

แน่นอนว่า TryCast เป็นวิธีการที่มีพารามิเตอร์ประเภทดังนั้นในการเรียกมันแบบไดนามิกคุณต้องสร้าง MethodInfo ด้วยตัวคุณเอง:

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

จากนั้นให้ตั้งค่าคุณสมบัติจริง:

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

และวิธีการขยายการจัดการกับคุณสมบัติ 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

ฉันมีความต้องการที่คล้ายกันและคำตอบจาก LukeH ชี้ให้ฉันไปในทิศทาง ฉันมากับฟังก์ชั่นทั่วไปนี้เพื่อให้ง่าย

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

การใช้งานเป็นเช่นนี้:

        NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty);

โปรดสังเกตว่าพารามิเตอร์ที่สองนั้นใช้เป็นแบบตัวอย่างเพื่อแสดงฟังก์ชันวิธีการส่งคืนค่าดังนั้นจึงไม่จำเป็นต้องเป็นคุณสมบัติปลายทาง หมายความว่าคุณสามารถทำอะไรเช่นนี้:

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

ฉันทำแบบนี้แทนที่จะใช้ออกเพราะคุณไม่สามารถใช้คุณสมบัติได้ ตามที่มันสามารถทำงานกับคุณสมบัติและตัวแปร คุณสามารถสร้างเกินพิกัดเพื่อส่งผ่านประเภทแทนได้หากต้องการ


0

ขอบคุณ @LukeH
ฉันเปลี่ยนไปเล็กน้อย:

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

ฉันทำอย่างนี้

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;
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.