Variant Types (Delphi)

From RAD Studio
Jump to: navigation, search

Go Up to Data Types, Variables, and Constants Index

This topic discusses the use of variant data types.

Variants Overview

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 run time. 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 run-time 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 (Delphi).) They can hold dynamic arrays, and they can hold a special kind of static array called a variant array. (See "Variant arrays" later in this chapter.) 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 run-time 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 System.Variants unit.

Note: Variant records are considered inherently "unsafe." A variant record is very similar to using the "absolute" directive because the variant field parts of the record are literally overlaid in memory atop each other. You can assign a value as one type and then read it out as a different type. If you are using variants, you might see compiler warnings about unsafe code, such as W1047 Unsafe code '%s' (Delphi).

On 32-bit platforms, a variant is stored as a 16-byte record. On 64-bit platforms, a variant is stored as a 24-byte record. A variant record consists of a type code and a value, or a pointer to a value, of the type specified by the type 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.

Variant Type Conversions

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:

var
   V1, V2, V3, V4, V5: Variant;
   I: Integer;
   D: Double;
   S: string;
 begin
    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) }
 end;

The compiler performs type conversions according to the following rules:

Variant type conversion rules

      Target:
Source:

integer

real

string

Boolean

integer

Converts integer formats.

Converts to real.

Converts to string representation.

Returns False if 0, True otherwise.

real

Rounds to nearest integer.

Converts real formats.

Converts to string representation using regional settings.

Returns False if 0, True otherwise.

string

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.

character

Same as string (above).

Same as string (above).

Same as string (above).

Same as string (above).

Boolean

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 System.Variants.BooleanToStringRule.

False = False, True = True

Unassigned

Returns 0.

Returns 0.

Returns empty string.

Returns False.

Null

Depends on global variable System.Variants.NullStrictConvert (raises an exception by default).

Depends on global variable System.Variants.NullStrictConvert (raises an exception by default).

Depends on global variables System.Variants.NullStrictConvert and System.Variants.NullAsStringValue (raises an exception by default).

Depends on global variable System.Variants.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 Variants.EVariantError exception or an exception class descending from Variants.EVariantError.

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

Variants in Expressions

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.

Variant Arrays

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. Do not perform such operations unnecessarily, since they are memory-inefficient.

OleVariant

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 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 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.

See Also