Some Delphi types do not have RTTI. This is no fun. This happens when, and I quote:
whereas enumerated constants with a specific value, such as the following, do not have RTTI:
type SomeEnum = (e1 = 1, e2 = 2, e3 = 3);
In normal use, this will go unnoticed, and not cause you any grief, until you throw these enumerated types into a generic construct (or have any other need to use RTTI). As soon as you do that, you’ll start getting the unhelpful and misleading “Invalid Class Typecast” exception. (No it’s not a Class!)
To avoid this problem, you must wander into the dark world of pointer casting, because once you are pointing at some data, Delphi no longer cares what its actual type is.
Here’s an example of how to convert a Variant value into a generic type, including support for RTTI-free enums, in a reasonably type-safe way. This is part of a TNullable
record type, which mimics, in some ways, the .NET Nullable
type. The workings of this type are not all that important for the example, however. This example works with RTTI types, and with one byte non-RTTI enumerated types &mdash you’d need to extend it to support larger enumerated types. While I could reduce the number of steps in the edge case by spelunking directly into the Variant TVarData
, that would not serve to clarify the murk.
constructor TNullable<T>.Create(AValue: Variant); type PT = ^T; var v: Byte; begin if VarIsEmpty(AValue) or VarIsNull(AValue) then Clear else if (TypeInfo(T) = nil) and (SizeOf(T) = 1) and (VarType(AValue) = varByte) then begin { Assuming an enum type without typeinfo, have to do some cruel pointer magics here to avoid type cast errors, so am very careful to validate first! } v := AValue; FValue := PT(@v)^; end else Create(TValue.FromVariant(AValue).AsType<T>); end;
So what is going on here? Well, first if we are passed Null
or “Empty” variant values, then we just clear our TNullable
value.
Otherwise we test if (a) we have no RTTI for our generic, and (b) it’s one byte in size, and (c) our variant is also a Byte value. If all these prerequisites are met, we perform the casting, in which we hark back to the ancient incantations with a pointer typecast, taking the address of the value and dereferencing it, fooling the compiler along the way. (Ha ha!)
Finally, we find a modern TValue
incantation suffices to wreak the type change for civilised types such as Integer
or String
.
Related to this issue:
http://delphi.fosdal.com/2011/04/generics-enumerated-types-and-ordinal.html
http://www.thedelphigeek.com/2013/03/using-generics-to-manipulate-enumerable.html