web-dev-qa-db-fra.com

Convert.ChangeType () échoue sur les types nullables

Je souhaite convertir une chaîne en une valeur de propriété d'objet, dont le nom est une chaîne. J'essaie de faire ça comme ça:

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

Le problème est qu’il échoue et génère une exception d’exception invalide lorsque le type de propriété est un type nullable. Ce n'est pas le cas des valeurs ne pouvant pas être converties - elles fonctionneront si je le fais manuellement (par exemple, DateTime? d = Convert.ToDateTime(value);). J'ai déjà vu des questions similaires, mais je n'arrive toujours pas à le faire fonctionner.

254
iboeno

Non testé, mais peut-être que quelque chose comme cela fonctionnera:

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

Vous devez obtenir le type sous-jacent pour pouvoir le faire ...

Essayez ceci, je l'ai utilisé avec succès avec des génériques:

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

Je l'utilise à plusieurs endroits de mon code, par exemple, une méthode d'assistance que j'utilise pour convertir les valeurs de base de données de manière typée:

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

Appelé en utilisant:

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

J'ai écrit une série de billets de blog y compris ceci sur http://www.endswithsaurus.com/2010_07_01_archive.html (Faites défiler jusqu'à l'addendum, @JohnMacintyre effectivement repéré le bogue dans mon code original qui a moi dans le même chemin que vous êtes maintenant). J'ai quelques petites modifications depuis cette publication qui inclut la conversion des types enum. Par conséquent, si votre propriété est une énumération, vous pouvez toujours utiliser le même appel de méthode. Ajoutez simplement une ligne pour vérifier les types d'énumération et vous partez pour les courses en utilisant quelque chose comme:

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

Normalement, vous rencontrez des erreurs lors de la vérification ou utilisez TryParse au lieu de Parse, mais vous obtenez l’image.

64
BenAlabaster

C'est un peu long pour un exemple, mais c'est une approche relativement robuste, qui sépare la tâche de transtypage d'une valeur inconnue à un type inconnu.

J'ai une méthode TryCast qui fait quelque chose de similaire et prend en compte les types nullables.

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

Bien entendu, TryCast est une méthode avec un paramètre de type. Par conséquent, pour l'appeler de manière dynamique, vous devez construire vous-même le MethodInfo:

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

Ensuite, pour définir la valeur réelle de la proprié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);
}

Et les méthodes d'extension pour traiter 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;
}
8
Eric Burcham

J'ai eu un besoin similaire, et la réponse de LukeH m'a orienté dans la direction. Je suis venu avec cette fonction générique pour le rendre facile.

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

L'utilisation est comme ceci:

        NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty);

Notez que le second paramètre est simplement utilisé comme prototype pour montrer à la fonction comment transtyper la valeur de retour, de sorte qu'il ne soit pas nécessaire que ce soit la propriété de destination. Cela signifie que vous pouvez faire quelque chose comme ceci:

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

Je l'ai fait de cette façon au lieu d'utiliser un out, car vous ne pouvez pas l'utiliser avec des propriétés. Tel quel, cela peut fonctionner avec des propriétés et des variables. Vous pouvez également créer une surcharge pour transmettre le type si vous le souhaitez.

6
Steve In CO

Je l'ai fait de cette façon 

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

Merci @LukeH
J'ai un peu changé: 

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
hs586sd46s