356 votes

Convert.ChangeType() échoue sur les types Nullables

Je veux convertir une chaîne en une valeur de propriété d'un objet, dont le nom est une chaîne. J'essaie de le faire de la manière suivante :

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 que cette opération échoue et génère une exception Invalid Cast lorsque le type de propriété est un type nullable. Il ne s'agit pas d'un cas où les valeurs ne peuvent pas être converties - elles fonctionnent si je le fais manuellement (par ex. DateTime? d = Convert.ToDateTime(value); ) J'ai vu des questions similaires mais je n'arrive toujours pas à le faire fonctionner.

1 votes

J'utilise ExecuteScalar<int?> avec PetaPoco 4.0.3 et cela échoue pour la même raison : return (T)Convert.ChangeType(val, typeof(T)) à la ligne 554

487voto

LukeH Points 110965

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

14 votes

J'avais besoin de ce bout de code moi-même. Merci pour Nullable.GetUnderlyingType ! Il m'a beaucoup aidé lorsque j'ai construit un ModelBinder du pauvre pour un projet qui en avait besoin. Je vous dois une bière !

3 votes

Peut-être qu'au lieu de (value == null) ? null utiliser (value == null) ? default(t) ?

0 votes

Ne semble pas fonctionner pour l'identifiant unique en chaîne de caractères.

83voto

BenAlabaster Points 20189

Vous devez obtenir le type sous-jacent afin de faire cela...

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, par exemple dans une méthode d'aide que j'utilise pour convertir les valeurs des bases de données d'une manière sûre :

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 d'articles de blog, dont celui-ci, à l'adresse suivante http://www.endswithsaurus.com/2010_07_01_archive.html (Faites défiler la page jusqu'à l'addendum, @JohnMacintyre a en fait repéré le bogue dans mon code original qui m'a conduit sur le même chemin que celui que vous empruntez maintenant). J'ai fait quelques petites modifications depuis ce post qui inclut la conversion des types enum aussi, donc si votre propriété est un Enum, vous pouvez toujours utiliser le même appel de méthode. Il suffit d'ajouter une ligne pour vérifier les types d'enum et vous êtes prêt pour la course en utilisant quelque chose comme :

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

Normalement, il faudrait vérifier les erreurs ou utiliser TryParse au lieu de Parse, mais vous voyez le tableau.

0 votes

Merci - je rate toujours une étape ou je ne comprends pas quelque chose. J'essaie de définir la valeur d'une propriété, pourquoi est-ce que je reçois le type sous-jacent de l'objet dans lequel elle se trouve ? Je ne sais pas non plus comment passer de mon code à une méthode d'extension comme la vôtre. Je ne saurai pas quel sera le type pour faire quelque chose comme value.Helper<Int32?>().

0 votes

@iboeno - Désolé, j'étais en réunion et je n'ai pas pu vous aider à relier les points. Je suis content que vous ayez trouvé une solution.

1 votes

Dans le premier bloc de code, où t est une variable (c'est-à-dire une valeur), comment pouvez-vous écrire default(t) ? Vous pouvez utiliser default() sólo avec un type (au moment de la compilation) (peut être un type générique).

12voto

Major Points 1319

Cela fonctionne parfaitement même pour les types Nullables :

TypeConverter conv = TypeDescriptor.GetConverter(type);
return conv.ConvertFrom(value);

Pour la sécurité du type, vous devez également appeler conv.CanConvertFrom(type) avant d'appeler la méthode ConvertFrom() . Dans le cas où il renvoie false, vous pouvez vous rabattre sur ChangeType ou autre chose.

10voto

Eric Burcham Points 2621

C'est un peu long pour un exemple, mais il s'agit d'une approche relativement robuste, qui sépare la tâche de couler une valeur inconnue vers 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 sûr, TryCast est une méthode avec un paramètre de type, donc pour l'appeler dynamiquement, vous devez construire vous-même l'InfoMéthode :

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 gérer 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;
}

7voto

Steve In CO Points 21

J'avais un besoin similaire, et la réponse de LukeH m'a mis sur la voie. J'ai créé cette fonction générique pour faciliter les choses.

    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 ça :

        NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty);

Notez que le deuxième paramètre est juste utilisé comme prototype pour montrer à la fonction comment convertir la valeur de retour, donc il n'a pas besoin d'être la propriété de destination. Ce qui signifie que vous pouvez également 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 les propriétés. Tel quel, il peut fonctionner avec les propriétés et les variables. Vous pouvez également créer une surcharge pour passer le type à la place si vous le souhaitez.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X