2

Why is CType complaining (InvalidCastException) about taking an object (that is really an Int32?) and converting it to an Int64??

I've found that CTypeDynamic isn't having an issue (tangential though, as I'm focused on Ctype).

Here is a code sample to reproduce the scenario.

Module Module1

  Sub Main()

    Dim i As Int32? = 1234567891

    'manual nullable -> non nullable -> non nullable -> nullable
    'per https://stackoverflow.com/a/10065482/392175
    Dim iNotNullable As Int32 = i.Value
    Dim biNotNullable As Int64 = iNotNullable
    Dim bi As Int64? = biNotNullable

    Console.WriteLine($"---Manual results---")
    Console.WriteLine($"i={i}")
    Console.WriteLine($"bi={bi}")

    'CType investigation
    bi = Module1.xCType(Of Int64?)(i)

    Console.WriteLine($"---CType results---")
    Console.WriteLine($"i={i}")
    Console.WriteLine($"bi={bi}")

    Console.ReadLine()
  End Sub

  Public Function [xCType](Of T)(ByVal obj As Object) As T
    If obj Is Nothing Then Return Nothing
    If IsDBNull(obj) Then Return Nothing

    Return obj  'fails

    Return CType(obj, T)  'fails

    Return CTypeDynamic(Of T)(obj)  'succeeds
  End Function

End Module
Jimi
  • 29,621
  • 8
  • 43
  • 61
DaveWilliamson
  • 370
  • 1
  • 11
  • 1
    An interesting question, reading over the docs and this thread https://stackoverflow.com/questions/10065452/c-sharp-casting-to-nullable-type seems to me that nullables aren't all that clear cut when it comes to type casting. – Hursey Jul 19 '21 at 20:49
  • @Hursey Seems like this statement from that other thread "Otherwise, the conversion is evaluated as an unwrapping from S? to S, followed by the underlying conversion from S to T, followed by a wrapping (§4.1.10) from T to T?" would be the path and successful. – DaveWilliamson Jul 19 '21 at 21:23
  • @Hursey I've updated the question to include the manual path described in the stackoverflow post you mentioned to contrast that the manual path works. i.e. Int32? -> Int32 -> Int64 -> Int64? – DaveWilliamson Jul 19 '21 at 21:31
  • @Hursey: `vb.net` has its own rules, it is not just C#-with-a-more-verbose-syntax – Ben Voigt Jul 19 '21 at 21:38
  • @Hursey C# exhibits the same on the return (T)obj; – DaveWilliamson Jul 19 '21 at 21:50
  • "CTypeDymanic isn't having an issue (tangential though, as I'm focused on Ctype)" Because you are doing this inside a generic method, I think it is very relevant. – Ben Voigt Jul 19 '21 at 22:00
  • Did you try `bi = CType(i, Int64?)` **in Main()** where you tested the manual conversion sequence? – Ben Voigt Jul 19 '21 at 22:04
  • @BenVoigt Yes. That will succeed. It bypasses the use of the generics. – DaveWilliamson Jul 19 '21 at 22:09
  • @BenVoigt Still with you. Isn't a specific method generated by the compiler for each caller of the generic method? – DaveWilliamson Jul 19 '21 at 22:22
  • @DaveWilliamson: No. There is only one set of MSIL generated by the VB.NET compiler. From that MSIL, the JIT will generate one machine code function shared by all reference types, plus a separate machine code function for each value type. All callers hit these same machine code functions. Even though Nullable is a value type, function overload selection is controlled by the MSIL. Whichever conversion instruction is placed in the MSIL (box, unbox, upcast/downcast, call user-defined conversion, or numeric conversion) has to work for all types. – Ben Voigt Jul 20 '21 at 16:21
  • In particular, generics do NOT work like C++ templates where name lookup and overload resolution are separately performed after the template arguments are known. – Ben Voigt Jul 20 '21 at 16:21

1 Answers1

3

You have two problems, both related to insufficient type information available at compile time.

  1. .NET doesn't specialize generic methods. One compilation, based on the constraints, has to work for every run-time value of the generic parameter.

    At the time the compiler sees xCType it doesn't know that T is a nullable type, so it can't choose the rule for nullable cast (S? to S to T to T?) and even if you constrain to a generic nullable the middle conversion (S to T) will still fail because that's type-specific not generic.

  2. obj has static type Object, so again the compiler doesn't know that the actual value passed in will be a nullable and cannot select the nullable conversion sequence. And again the middle conversion in the sequence (S to T) cannot be found when the compile-time type information is missing.

CTypeDynamic overcomes both these problems, by looking at the runtime type instead of the compile-time type.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720