Nieprawidłowe rzutowanie z „System.Int32” na „System.Nullable” 1 [[System.Int32, mscorlib]]

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

Otrzymuję InvalidCastException w powyższym kodzie. Powyżej mógłbym po prostu napisać int? nVal = val, ale powyższy kod jest wykonywany dynamicznie.

Otrzymuję wartość (typu nie dopuszczającego wartości null, takiej jak int, float itp.) Zawiniętą w obiekt (tutaj val) i muszę zapisać ją w innym obiekcie, rzutując ją na inny typ (który może lub nie może być wersją zerową tego). Gdy

Nieprawidłowe rzutowanie z „System.Int32” na „System.Nullable” 1 [[System.Int32, mscorlib, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089]] ”.

int, Powinny być wymienialne / typu rzutować na nullable intto, co jest tu problem?

Brij
źródło
Myślę, że może coz Nullable<T>nie implementujeIConvertible
V4Vendetta
2
To jest raczej fundamentalne. Dopuszczalna wartość null jest wyjątkowa, gdy umieścisz ją w obiekcie, wówczas albo stanie się pusta, albo stanie się opakowaną wartością typu wartości. Więc pytasz o int? przechowywane w obiekcie po prostu nie mają sensu. Po prostu zapytaj o int.
Hans Passant

Odpowiedzi:

143

Musisz użyć, Nullable.GetUnderlyingTypeaby uzyskać podstawowy typ Nullable.

To jest metoda, której używam, aby przezwyciężyć ograniczenie ChangeTypeforNullable

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

metoda nieogólna:

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);
}
gzaxx
źródło
aby użyć twojej metody, musiałbym zrobić coś takiego :, object nVal = ChangeType<int?>(val)tutaj muszę powiedzieć metodzie o ogólnym argumencie (T), ale mam do dyspozycji t(lub typeof (dataType)). Jak mogę wywołać metodę ChangeType w moim scenariuszu?
Brij
1
Dodano wersję inną niż ogólna. Zobacz, czy to pomaga.
gzaxx
Pojawienie się błędu kompilacji pod adresem default(conversion), wygląda na podobny problem.
Brij
Ostrożnie @gzaxx as return nullto nie to samo co default(T). Jeśli masz do czynienia ze strukturami, to są to zupełnie inne rzeczy.
Alex
Wersja nieogólna będzie zawierała typy strukturalne i pierwotne (ponieważ przyjmuje i zwraca obiekt), więc zwracanie wartości null jest prawidłowe. Każdy, kto wywołuje funkcje, będzie musiał sam sobie z tym poradzić.
gzaxx
9

Powyżej mógłbym po prostu napisać int? nVal = val

Właściwie tego też nie możesz zrobić. Nie ma niejawnej konwersji z objectna Nullable<int>. Ale nie jest niejawna konwersja z intdo Nullable<int>tak można napisać tak:

int? unVal = (int)val;

Możesz użyć Nullable.GetUnderlyingTypemetody.

Zwraca argument typu podstawowego określonego typu dopuszczającego wartość null.

Definicja typu ogólnego to deklaracja typu, taka jak Nullable, która zawiera listę parametrów typu, a lista parametrów typu deklaruje jeden lub więcej parametrów typu. Zamknięty typ ogólny to deklaracja typu, w której określony typ jest określony dla parametru typu.

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

Oto plik DEMO.

Soner Gönül
źródło
2

Myślę, że powinienem wyjaśnić, dlaczego funkcja nie działa:

1- Linia, która zgłasza wyjątek, wygląda następująco:

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

w rzeczywistości funkcja wyszukuje w tablicy Convert.ConvertTypes, po czym sprawdza, czy celem jest Enum, a gdy nic nie zostanie znalezione, zgłasza wyjątek powyżej.

2- plik Convert.ConvertTypes jest inicjowany jako:

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

Więc ponieważ int?nie ma w tablicy ConvertTypes i nie jest Enum, zgłaszany jest wyjątek.

Aby wznowić, aby funkcja Convert.ChnageType działała, masz:

  1. Obiekt do konwersji to IConvertible

  2. Typ docelowy znajduje się w ConvertTypes, a nie Emptynor DBNull(istnieje test jawny dla tych dwóch z wyjątkiem rzutu)

Dzieje się tak, ponieważ int(i wszystkie inne typy domyślne) używa Convert.DefaultToTypejako IConvertibale.ToType implementation. and here is the code of theDefaultToType extractedusingILSpy

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

z drugiej strony rzutowanie jest implementowane przez samą klasę Nullable, a definicja to:

public static implicit operator T?(T value)
{
    return new T?(value);
}
public static explicit operator T(T? value)
{
    return value.Value;
}
Szybki
źródło