Convertir.ChangeType () échoue sur les Types Nullable

Je veux convertir une chaîne en une valeur de propriété d'objet, dont j'ai le nom comme chaîne. J'essaie de le faire comme suit:

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 lance une Exception de conversion 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 vu des questions similaires mais je ne peux toujours pas le faire fonctionner.

244
demandé sur dtb 2010-08-20 17:36:15

5 réponses

Non testé, mais peut-être que quelque chose comme ça 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);
}
338
répondu LukeH 2015-02-03 13:29:27

, Vous devez obtenir le type sous-jacent pour 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 dans mon code, un exemple est une méthode d'aide que j'utilise pour convertir les valeurs de la base de données de manière typesafe:

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 à http://www.endswithsaurus.com/2010_07_01_archive.html (Faites défiler jusqu'à L'Addendum, @JohnMacintyre en fait repéré le bug dans mon code d'origine qui m'a conduit sur le même chemin que vous êtes maintenant). J'ai quelques petites modifications depuis ce post qui inclut la conversion des types enum aussi, donc 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 enum et vous partez pour les courses en utilisant quelque chose comme:

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

Normalement, vous auriez une vérification d'erreur ou utilisez TryParse au lieu de Parse, mais vous obtenez le image.

60
répondu BenAlabaster 2010-08-20 16:03:23

C'est un peu long pour un exemple, mais c'est une approche relativement robuste, et sépare la tâche de lancer de la valeur inconnue en type inconnu

J'ai une méthode TryCast qui fait quelque chose de similaire, et prend en compte les types 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;
    }
}

Bien sûr, TryCast est une méthode avec un paramètre de Type, donc pour l'appeler dynamiquement, vous devez construire le MethodInfo vous-même:

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

Puis 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 le méthodes d'extension pour traiter la propriété.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
répondu Eric Burcham 2012-05-31 18:40:53

J'avais un besoin similaire, et la réponse de LukeH m'a pointé 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);

Note le deuxième paramètre est simplement utilisé comme prototype pour montrer à la fonction Comment lancer la valeur de retour, donc il ne doit pas être la propriété destination. Ce qui signifie que vous pouvez aussi 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 parce que vous ne pouvez pas utiliser out avec propriété. Tel quel, il peut fonctionner avec des propriétés et des variables. Vous pouvez également créer une surcharge pour passer le type à la place si vous le souhaitez.

6
répondu Steve In CO 2015-01-06 20:02:28

Merci @ LukeH
J'ai changé un peu:

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
répondu hs586sd46s 2013-06-21 01:08:03