RAD Studio (Common)
Variant Types

This topic discusses the use of variant data types.

Sometimes it is necessary to manipulate data whose type varies or cannot be determined at compile time. In these cases, one option is to use variables and parameters of type Variant, which represent values that can change type at runtime. Variants offer greater flexibility but consume more memory than regular variables, and operations on them are slower than on statically bound types. Moreover, illicit operations on variants often result in runtime errors, where similar mistakes with regular variables would have been caught at compile time. You can also create custom variant types. 

By default, Variants can hold values of any type except records, sets, static arrays, files, classes, class references, and pointers. In other words, variants can hold anything but structured types and pointers. They can hold interfaces, whose methods and properties can be accessed through them. (See Object interfaces.) They can hold dynamic arrays, and they can hold a special kind of static array called a variant array. (See Variant arrays.) Variants can mix with other variants and with integer, real, string, and Boolean values in expressions and assignments; the compiler automatically performs type conversions. 

Variants that contain strings cannot be indexed. That is, if V is a variant that holds a string value, the construction V[1] causes a runtime error. 

You can define custom Variants that extend the Variant type to hold arbitrary values. For example, you can define a Variant string type that allows indexing or that holds a particular class reference, record type, or static array. Custom Variant types are defined by creating descendants to the TCustomVariantType class.

Note: This, and almost all variant functionality, is implemented in the Variants unit.
A variant occupies 16 bytes of memory and consists of a type code and a value, or pointer to a value, of the type specified by the code. All variants are initialized on creation to the special value Unassigned. The special value Null indicates unknown or missing data. 

The standard function VarType returns a variant's type code. The varTypeMask constant is a bit mask used to extract the code from VarType's return value, so that, for example,

VarType(V) and varTypeMask = varDouble

returns True if V contains a Double or an array of Double. (The mask simply hides the first bit, which indicates whether the variant holds an array.) The TVarData record type defined in the System unit can be used to typecast variants and gain access to their internal representation.

All integer, real, string, character, and Boolean types are assignment-compatible with Variant. Expressions can be explicitly cast as variants, and the VarAsType and VarCast standard routines can be used to change the internal representation of a variant. The following code demonstrates the use of variants and some of the automatic conversions performed when variants are mixed with other types.

   V1, V2, V3, V4, V5: Variant;
   I: Integer;
   D: Double;
   S: string;
      V1 := 1;         { integer value }
      V2 := 1234.5678; { real value }
      V3 := 'Hello world!'; { string value }
      V4 := '1000';         { string value }
      V5 := V1 + V2 + V4;   { real value 2235.5678}
      I  := V1;             { I = 1 (integer value) }
      D  := V2;              { D = 1234.5678 (real value) }
      S  := V3;              { S = 'Hello world!' (string value) }
      I  := V4;              { I = 1000 (integer value) }
      S  := V5;              { S = '2235.5678' (string value) }

The compiler performs type conversions according to the following rules.  

Variant type conversion rules  

converts integer formats  
converts to real  
converts to string representation  
returns False if 0, True otherwise  
rounds to nearest integer  
converts real formats  
converts to string representation using regional settings  
returns False if 0, True otherwise  
converts to integer, truncating if necessary; raises exception if string is not numeric  
converts to real using regional settings; raises exception if string is not numeric  
converts string/character formats  
returns False if string is 'false' (noncase-sensitive) or a numeric string that evaluates to 0, True if string is 'true' or a nonzero numeric string; raises exception otherwise  
same as string (above)  
same as string (above)  
same as string (above)  
same as string (above)  
False = 0, True: all bits set to 1 (-1 if Integer, 255 if Byte, etc.)  
False = 0, True = 1  
False = 'False', True = 'True' by default; casing depends on global variable BooleanToStringRule  
False = False, True = True  
returns 0  
returns 0  
returns empty string  
returns False  
depends on global variable NullStrictConvert (raises an exception by default)  
depends on global variable NullStrictConvert (raises an exception by default)  
depends on global variables NullStrictConvert and NullAsStringValue (raises an exception by default)  
depends on global variable NullStrictConvert (raises an exception by default)  

Out-of-range assignments often result in the target variable getting the highest value in its range. Invalid variant operations, assignments or casts raise an EVariantError exception or an exception class decending from EVariantError. 

Special conversion rules apply to the TDateTime type declared in the System unit. When a TDateTime is converted to any other type, it treated as a normal Double. When an integer, real, or Boolean is converted to a TDateTime, it is first converted to a Double, then read as a date-time value. When a string is converted to a TDateTime, it is interpreted as a date-time value using the regional settings. When an Unassigned value is converted to TDateTime, it is treated like the real or integer value 0. Converting a Null value to TDateTime raises an exception. 

On the Win32 platform, if a variant references a COM interface, any attempt to convert it reads the object's default property and converts that value to the requested type. If the object has no default property, an exception is raised.

All operators except ^, is, and in take variant operands. Except for comparisons, which always return a Boolean result, any operation on a variant value returns a variant result. If an expression combines variants with statically-typed values, the statically-typed values are automatically converted to variants. 

This is not true for comparisons, where any operation on a Null variant produces a Null variant. For example:

V := Null + 3;

assigns a Null variant to V. By default, comparisons treat the Null variant as a unique value that is less than any other value. For example:

if Null > -3 then ... else ...;

In this example, the else part of the if statement will be executed. This behavior can be changed by setting the NullEqualityRule and NullMagnitudeRule global variables.

You cannot assign an ordinary static array to a variant. Instead, create a variant array by calling either of the standard functions VarArrayCreate or VarArrayOf. For example,

V: Variant;
V := VarArrayCreate([0,9], varInteger);

creates a variant array of integers (of length 10) and assigns it to the variant V. The array can be indexed using V[0], V[1], and so forth, but it is not possible to pass a variant array element as a var parameter. Variant arrays are always indexed with integers. 

The second parameter in the call to VarArrayCreate is the type code for the array's base type. For a list of these codes, see VarType. Never pass the code varString to VarArrayCreate; to create a variant array of strings, use varOleStr

Variants can hold variant arrays of different sizes, dimensions, and base types. The elements of a variant array can be of any type allowed in variants except ShortString and AnsiString, and if the base type of the array is Variant, its elements can even be heterogeneous. Use the VarArrayRedim function to resize a variant array. Other standard routines that operate on variant arrays include VarArrayDimCount, VarArrayLowBound, VarArrayHighBound, VarArrayRef, VarArrayLock, and VarArrayUnlock.

Note: Variant arrays of custom variants are not supported, as instances of custom variants can be added to a VarVariant variant array.
When a variant containing a variant array is assigned to another variant or passed as a value parameter, the entire array is copied. Don't perform such operations unnecessarily, since they are memory-inefficient. 

The OleVariant type exists on both the Windows and Linux platforms. The main difference between Variant and OleVariant is that Variant can contain data types that only the current application knows what to do with. OleVariant can only contain the data types defined as compatible with OLE Automation which means that the data types that can be passed between programs or across the network without worrying about whether the other end will know how to handle the data. 

When you assign a Variant that contains custom data (such as a Delphi string, or a one of the new custom variant types) to an OleVariant, the runtime library tries to convert the Variant into one of the OleVariant standard data types (such as a Delphi string converts to an OLE BSTR string). For example, if a variant containing an AnsiString is assigned to an OleVariant, the AnsiString becomes a WideString. The same is true when passing a Variant to an OleVariant function parameter.

Copyright(C) 2009 Embarcadero Technologies, Inc. All Rights Reserved.
What do you think about this topic? Send feedback!