web-dev-qa-db-fra.com

Conversion incorrecte de 'System.Int32' à 'System.Nullable`1 [[System.Int32, mscorlib]]

Type t = typeof(int?); //will get this dynamically
object val = 5; //will get this dynamically
object nVal = Convert.ChangeType(val, t);//getting exception here

Je reçois InvalidCastException dans le code ci-dessus. Pour ce qui précède, je pourrais simplement écrire int? nVal = val, mais le code ci-dessus s’exécute de manière dynamique.

J'obtiens une valeur (de type non nullable comme int, float, etc.) encapsulée dans un objet (ici val), et je dois l'enregistrer dans un autre objet en le convertissant en un autre type (qui peut ou non être une version nullable de celui-ci). Quand

Conversion non valide de 'System.Int32' à 'System.Nullable`1 [[System.Int32, mscorlib, version = 4.0.0.0, Culture = neutre, PublicKeyToken = b77a5c561934e089]]'.

Un int devrait être convertible/transtypé en nullable int, Quel est le problème ici ?

74
Brij

Vous devez utiliser Nullable.GetUnderlyingType pour obtenir le type sous-jacent de Nullable.

C'est la méthode que j'utilise pour surmonter la limitation de ChangeType pour Nullable

public static T ChangeType<T>(object value) 
{
   var t = typeof(T);

   if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) 
   {
       if (value == null) 
       { 
           return default(T); 
       }

       t = Nullable.GetUnderlyingType(t);
   }

   return (T)Convert.ChangeType(value, t);
}

méthode non générique:

public static object ChangeType(object value, Type conversion) 
{
   var t = conversion;

   if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) 
   {
       if (value == null) 
       { 
           return null; 
       }

       t = Nullable.GetUnderlyingType(t);
   }

   return Convert.ChangeType(value, t);
}
118
gzaxx

Pour ci-dessus, je pourrais simplement écrire int? nVal = val

En fait, vous ne pouvez pas faire ça non plus. Il n'y a pas de conversion implicite de object en Nullable<int>. Mais il y a est une conversion implicite de int en Nullable<int> afin que vous puissiez écrire ceci:

int? unVal = (int)val;

Vous pouvez utiliser Nullable.GetUnderlyingType méthode.

Renvoie l'argument du type sous-jacent du type nullable spécifié.

Une définition de type générique est une déclaration de type, telle que Nullable, qui contient une liste de paramètres de type et la liste de paramètres de type déclare un ou plusieurs paramètres de type. Un type générique fermé est une déclaration de type dans laquelle un type particulier est spécifié pour un paramètre de type.

Type t = typeof(int?); //will get this dynamically
Type u = Nullable.GetUnderlyingType(t);
object val = 5; //will get this dynamically
object nVal = Convert.ChangeType(val, u);// nVal will be 5

Voici un DEMO .

8
Soner Gönül

Je pense que je devrais expliquer pourquoi la fonction ne fonctionne pas:

1- La ligne qui lève l'exception est la suivante:

throw new InvalidCastException(Environment.GetResourceString("InvalidCast_FromTo", new object[]
  {
    value.GetType().FullName, 
    targetType.FullName
    }));

en fait, la fonction recherche dans le tableau Convert.ConvertTypes après quoi elle voit si la cible est un Enum et quand rien n’est trouvé, elle lève l’exception ci-dessus.

2- Le Convert.ConvertTypes est initialisé en tant que:

Convert.ConvertTypes = new RuntimeType[]
   {
      (RuntimeType)typeof(Empty), 
      (RuntimeType)typeof(object), 
      (RuntimeType)typeof(DBNull), 
      (RuntimeType)typeof(bool), 
      (RuntimeType)typeof(char), 
      (RuntimeType)typeof(sbyte), 
      (RuntimeType)typeof(byte), 
      (RuntimeType)typeof(short), 
      (RuntimeType)typeof(ushort), 
      (RuntimeType)typeof(int), 
      (RuntimeType)typeof(uint), 
      (RuntimeType)typeof(long), 
      (RuntimeType)typeof(ulong), 
      (RuntimeType)typeof(float), 
      (RuntimeType)typeof(double), 
      (RuntimeType)typeof(decimal), 
      (RuntimeType)typeof(DateTime), 
      (RuntimeType)typeof(object), 
      (RuntimeType)typeof(string)
   };

Donc, depuis le int? n'est pas dans le tableau ConvertTypes et n'est pas une exception Enum est levée.

Pour résumer, pour que la fonction Convert.ChnageType fonctionne, vous avez:

  1. L'objet à convertir est IConvertible

  2. Le type de cible est dans les ConvertTypes et non pas Empty ni DBNull (il existe un test explicite sur ces deux-là avec une exception throw)

Ce comportement est dû au fait que int (et tous les autres types par défaut) utilise Convert.DefaultToType comme IConvertibale.ToType implementation. and here is the code of theDefaultToType extracted utilisant ILSpy

internal static object DefaultToType(IConvertible value, Type targetType, IFormatProvider provider)
{
    if (targetType == null)
    {
        throw new ArgumentNullException("targetType");
    }
    RuntimeType left = targetType as RuntimeType;
    if (left != null)
    {
        if (value.GetType() == targetType)
        {
            return value;
        }
        if (left == Convert.ConvertTypes[3])
        {
            return value.ToBoolean(provider);
        }
        if (left == Convert.ConvertTypes[4])
        {
            return value.ToChar(provider);
        }
        if (left == Convert.ConvertTypes[5])
        {
            return value.ToSByte(provider);
        }
        if (left == Convert.ConvertTypes[6])
        {
            return value.ToByte(provider);
        }
        if (left == Convert.ConvertTypes[7])
        {
            return value.ToInt16(provider);
        }
        if (left == Convert.ConvertTypes[8])
        {
            return value.ToUInt16(provider);
        }
        if (left == Convert.ConvertTypes[9])
        {
            return value.ToInt32(provider);
        }
        if (left == Convert.ConvertTypes[10])
        {
            return value.ToUInt32(provider);
        }
        if (left == Convert.ConvertTypes[11])
        {
            return value.ToInt64(provider);
        }
        if (left == Convert.ConvertTypes[12])
        {
            return value.ToUInt64(provider);
        }
        if (left == Convert.ConvertTypes[13])
        {
            return value.ToSingle(provider);
        }
        if (left == Convert.ConvertTypes[14])
        {
            return value.ToDouble(provider);
        }
        if (left == Convert.ConvertTypes[15])
        {
            return value.ToDecimal(provider);
        }
        if (left == Convert.ConvertTypes[16])
        {
            return value.ToDateTime(provider);
        }
        if (left == Convert.ConvertTypes[18])
        {
            return value.ToString(provider);
        }
        if (left == Convert.ConvertTypes[1])
        {
            return value;
        }
        if (left == Convert.EnumType)
        {
            return (Enum)value;
        }
        if (left == Convert.ConvertTypes[2])
        {
            throw new InvalidCastException(Environment.GetResourceString("InvalidCast_DBNull"));
        }
        if (left == Convert.ConvertTypes[0])
        {
            throw new InvalidCastException(Environment.GetResourceString("InvalidCast_Empty"));
        }
    }
    throw new InvalidCastException(Environment.GetResourceString("InvalidCast_FromTo", new object[]
    {
        value.GetType().FullName, 
        targetType.FullName
    }));
}

d'autre part, le casting est implémenté par la classe Nullable elle-même et la définition est:

public static implicit operator T?(T value)
{
    return new T?(value);
}
public static explicit operator T(T? value)
{
    return value.Value;
}
2
Swift