Convert.ChangeType () schlägt bei nullbaren Typen fehl

301

Ich möchte eine Zeichenfolge in einen Objekteigenschaftswert konvertieren, dessen Namen ich als Zeichenfolge habe. Ich versuche das so zu machen:

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

Das Problem ist, dass dies fehlschlägt und eine ungültige Cast-Ausnahme auslöst, wenn der Eigenschaftstyp ein nullbarer Typ ist. Dies ist nicht der Fall, wenn die Werte nicht konvertiert werden können - sie funktionieren, wenn ich dies manuell mache (z. B. DateTime? d = Convert.ToDateTime(value);) Ich habe einige ähnliche Fragen gesehen, kann sie aber immer noch nicht zum Laufen bringen.

iboeno
quelle
1
Ich verwende ExecuteScalar <int?> Mit PetaPoco 4.0.3 und es schlägt aus demselben Grund fehl: return (T) Convert.ChangeType (val, typeof (T)) in Zeile 554
Larry

Antworten:

409

Ungetestet, aber vielleicht funktioniert so etwas:

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
quelle
12
Ich brauchte nur diesen Code. Vielen Dank für Nullable.GetUnderlyingType! Hat mir sehr geholfen, als ich den ModelBinder eines armen Mannes für ein Projekt gebaut habe, das ihn brauchte. Ich schulde dir ein Bier!
Maxime Rouiller
3
Vielleicht statt zu (value == null) ? nullbenutzen (value == null) ? default(t)?
Threadster
Scheint nicht zu funktionieren, wenn die eindeutige Kennung eine Zeichenfolge ist.
Anders Lindén
Gibt es einen bestimmten Grund, die safeValueVariable zu erstellen , anstatt sie nur neu zuzuweisen value?
Coloradocolby
@threadster Sie können den Standardoperator für eine Variable vom Typ 'Typ' nicht verwenden. Siehe stackoverflow.com/questions/325426/…
andy250
75

Sie müssen den zugrunde liegenden Typ erhalten, um das zu tun ...

Versuchen Sie dies, ich habe es erfolgreich mit Generika verwendet:

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

Ich verwende es an mehreren Stellen in meinem Code. Ein Beispiel ist eine Hilfsmethode, mit der ich Datenbankwerte typsicher konvertiere:

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

Aufgerufen mit:

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

Ich schrieb eine Reihe von Blog - Posts einschließlich diesem bei http://www.endswithsaurus.com/2010_07_01_archive.html (Scrollen Sie zum Addendum, @JohnMacintyre tatsächlich den Fehler in meinem ursprünglichen Code entdeckt , der mich auf dem gleichen Weg führten Sie sind bin jetzt online). Ich habe seit diesem Beitrag ein paar kleine Änderungen vorgenommen, die auch die Konvertierung von Aufzählungstypen beinhalten. Wenn Ihre Eigenschaft also eine Aufzählung ist, können Sie immer noch denselben Methodenaufruf verwenden. Fügen Sie einfach eine Zeile hinzu, um nach Aufzählungstypen zu suchen, und Sie können mit folgenden Methoden zu den Rennen gehen:

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

Normalerweise haben Sie eine Fehlerprüfung oder verwenden TryParse anstelle von Parse, aber Sie erhalten das Bild.

BenAlabaster
quelle
Danke - mir fehlt immer noch ein Schritt oder ich verstehe etwas nicht. Ich versuche, einen Eigenschaftswert festzulegen. Warum erhalte ich das Objekt, das sich im zugrunde liegenden Typ befindet? Ich bin mir auch nicht sicher, wie ich von meinem Code zu einer Erweiterungsmethode wie Ihrer gelangen soll. Ich werde nicht wissen, was der Typ sein wird, um so etwas wie value zu machen. Helfer <Int32?> ().
iboeno
@iboeno - Entschuldigung, war in einer Besprechung, also konnte ich dir nicht helfen, die Punkte zu verbinden. Ich bin froh, dass du eine Lösung hast.
BenAlabaster
9

Dies ist zum Beispiel etwas langwierig, aber dies ist ein relativ robuster Ansatz, der die Aufgabe des Castings vom unbekannten Wert zum unbekannten Typ trennt

Ich habe eine TryCast-Methode, die etwas Ähnliches tut und nullbare Typen berücksichtigt.

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

Natürlich ist TryCast eine Methode mit einem Typparameter. Um sie dynamisch aufzurufen, müssen Sie die MethodInfo selbst erstellen:

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

So legen Sie den tatsächlichen Eigenschaftswert fest:

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

Und die Erweiterungsmethoden für den Umgang mit 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;
}
bopapa_1979
quelle
6

Ich hatte ein ähnliches Bedürfnis und die Antwort von LukeH wies mich in die Richtung. Ich habe mir diese generische Funktion ausgedacht, um es einfach zu machen.

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

Die Verwendung ist wie folgt:

        NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty);

Beachten Sie, dass der zweite Parameter nur als Prototyp verwendet wird, um der Funktion zu zeigen, wie der Rückgabewert umgewandelt wird, sodass er nicht unbedingt die Zieleigenschaft sein muss. Das heißt, Sie können auch so etwas tun:

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

Ich habe es auf diese Weise gemacht, anstatt ein Out zu verwenden, weil Sie Out nicht mit Eigenschaften verwenden können. So wie es ist, kann es mit Eigenschaften und Variablen arbeiten. Sie können auch eine Überladung erstellen, um den Typ zu übergeben, wenn Sie möchten.

Steve In CO
quelle
0

Danke @LukeH
Ich habe mich ein wenig verändert:

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
quelle
0

Ich habe es so gemacht

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
quelle