Variants in Delphi. Use of Variant Array. How to check a Variant is unassigned or empty or clear ?

Sometimes it is necessary to store and change data whose type varies or cannot be determined at compile time. In these cases, we can use variables and parameters of type Variant, which represents values that can change type at run time. Variants are used to store any data type value and can  perform numerous operations at run time. For variants type values the compiler automatically performs type conversions. Once you have the variant value, you can assign it to any compatible-or incompatible-data type. If you assign a value to an incompatible data type, Delphi performs a conversion, if it can. Otherwise it issues a run-time error. By default, Variants can hold values of any type except records, sets, static arrays, files, classes, class references, and pointers. Use System.Variants unit.

Example
We declare a variant variable such as the following:

var
  V: Variant;
  
You can assign to it values of several different types:

V := UnAssigned;
V := NULL;
V := 10;
V := 'Hello, World';
V := 45.55;

In Delphi all variants are initialized with value Unassigned on creation. If we  attempt to use an Unassigned value in an expression, Delphi reports run-time error 16 (EVariantError).  We can assign value Null which indicates unknown or missing data. Attempting to convert a Null value to a number raises run-time error EVariantError. Null is  equivalent to nil in C and C++. Once you have the variant value, you can copy it to any compatible-or incompatible-data type. If you assign a value to an incompatible data type, Delphi performs a conversion, if it can. Otherwise it issues a run-time error. 

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.

Where any operation on a Null variant produces a Null variant. 
For example:

V := Null + 3;

Even, you can use the variants to compute values, as you can see in the code related to the second button:

procedure TForm1.Button2Click(Sender: TObject);
var
  V: Variant;
  N: Integer;
begin
  V := Edit1.Text;
  N := Integer(V) * 2;
  V := N;
  Edit1.Text := V;
end;

How to check Variant is Empty/Unassigned/Clear?

It is recommended to use VarIsClear to check is Variant is Null, Empty or Unassigned. But I found function can return False while variable Unassigned 

• Check for unassigned with VarIsEmpty.
• Check for an undefined value with VarIsClear.
• Check for a null value with VarIsNull.

Example

uses 
  System, Variants;
…................

function IsEmptyOrNull(const Value: Variant): Boolean;
begin
  Result := VarIsClear(Value) or VarIsEmpty(Value) or VarIsNull(Value) or (VarCompareValue(Value, Unassigned) = vrEqual);
  
  if (not Result) and VarIsStr(Value) then
     Result := Value = '';
end;

Calling VarClear is the same as assigning Unassigned to a Variant variable.

Variant Types and Conversions

All integer, real, string, character, and Boolean types are assignment-compatible with Variant. . 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. Invalid variant operations, assignments or casts raise an Variants.EVariantError exception or an exception class.

Delphi provides some functions and procedure to check the type of variant or to convert a variant from one type to another.

Check variant type

VarType
The VarType function returns an integer representing the current data type for a Variant variable . 

The low order bits contain one of the following values: 

varEmpty
varNull
varSmallint
varInteger
varSingle
varDouble
varCurrency
varDate
varOleStr
varDispatch
varError
varBoolean
varVariant
varUnknown
varShortInt
varByte
varWord
varLongWord
varInt64
varStrArg
varString
varAny 

The varTypeMask constant is a bit mask used to extract the code from VarType's return value, so that, for example,

Var
  V: variant;
......……

If VarType(V) and (VarTypeMask = varDouble) then
    Showmessage(‘True’);

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.

Another example to Show the type of a variant

procedure TForm1.ShowBasicVariantType(varVar: Variant);
var
  typeString : string;
  basicType  : Integer;
begin
  // Get the Variant basic type :
  // this means excluding array or indirection modifiers
  basicType := VarType(varVar) and VarTypeMask;

  // Set a string to match the type
  case basicType of
    varEmpty     : typeString := 'varEmpty';
    varNull      : typeString := 'varNull';
    varSmallInt  : typeString := 'varSmallInt';
    varInteger   : typeString := 'varInteger';
    varSingle    : typeString := 'varSingle';
    varDouble    : typeString := 'varDouble';
    varCurrency  : typeString := 'varCurrency';
    varDate      : typeString := 'varDate';
    varOleStr    : typeString := 'varOleStr';
    varDispatch  : typeString := 'varDispatch';
    varError     : typeString := 'varError';
    varBoolean   : typeString := 'varBoolean';
    varVariant   : typeString := 'varVariant';
    varUnknown   : typeString := 'varUnknown';
    varByte      : typeString := 'varByte';
    varWord      : typeString := 'varWord';
    varLongWord  : typeString := 'varLongWord';
    varInt64     : typeString := 'varInt64';
    varStrArg    : typeString := 'varStrArg';
    varString    : typeString := 'varString';
    varAny       : typeString := 'varAny';
    varTypeMask  : typeString := 'varTypeMask';
  end;

  // Show the Variant type
  ShowMessage('Variant type  = '+typeString);
end;

Use of above function

var
  myVar : variant;
............
begin
  // Assign various values to a Variant
  // and then show the resulting Variant type
  ShowMessage('Variant value = not yet set');
  ShowBasicVariantType(myVar);

  // Simple value
  myVar := 123;
  ShowMessage('Variant value = 123');
  ShowBasicVariantType(myVar);

  // Calculated value using a Variant and a constant
  myVar := myVar + 456;
  ShowMessage('Variant value = 123 + 456');
  ShowBasicVariantType(myVar);

  myVar := 'String '+IntToStr(myVar);
  ShowMessage('Variant value = String 579');
  ShowBasicVariantType(myVar);
end;

Functions to Check if variant belongs to specific type

VarIsArray
Indicates whether the specified variant is an array.

var
  V: Variant;
begin
  if VarIsArray(V) then
    ShowMessage(‘True’);
end;

VarIsByRef
Indicates whether the specified variant's value is by reference.

var
  V: Variant;
begin
  if VarIsByRef(V) then
    ShowMessage(‘True’);
end;

VarIsClear
Indicates whether the specified variant has an undefined value.

var
  V: Variant;
begin
  if VarIsClear(V) then
    ShowMessage(‘True’);
end;

VarIsCustom
Indicates whether the specified variant is a custom variant.

var
  V: Variant;
begin
  if VarIsCustom(V) then
    ShowMessage(‘True’);
end;

VarIsEmpty
Indicates whether the specified variant is unassigned.

var
  V: Variant;
begin
  if VarIsEmpty(V) then
    ShowMessage(‘True’);
end;

VarIsEmptyParam
Indicates whether the specified variant represents an unassigned optional parameter.

var
  V: Variant;
begin
  if VarIsEmptyParam(V) then
    ShowMessage(‘True’);
end;

VarIsError
Indicates whether the specified variant is an error variant; optionally converts variant back to HRESULT.

var
  V: Variant;
begin
  if VarIsError(V) then
    ShowMessage(‘True’);
end;

VarIsFloat
Indicates whether the specified variant represents a floating-point value.

var
  V: Variant;
begin
  if VarIsFloat(V) then
    ShowMessage(‘True’);
end;

VarIsNull
Indicates whether the specified variant is Null.

var
  V: Variant;
begin
  if VarIsNull(V) then
    ShowMessage(‘True’);
end;

VarIsNumeric
Indicates whether the specified variant represents a numeric value.

var
  V: Variant;
begin
  if VarIsNumeric(V) then
    ShowMessage(‘True’);
end;

VarIsOrdinal
Indicates whether the specified variant represents an ordinal value.

var
  V: Variant;
begin
  if VarIsOrdinal(V) then
    ShowMessage(‘True’);
end;

VarIsStr
Indicates whether the specified variant represents a string value.

var
  V: Variant;
begin
  if VarIsStr(V) then
    ShowMessage(‘True’);
end;

VarIsType
Indicates whether the specified variant represents a specified type.

var
  V: Variant;
begin
  if VarIsType(V, varInteger or varDouble) then
    ShowMessage('Number');
end;

VarSameValue
Indicates whether two Variants have equivalent values.

var
  V1, V2: Variant;
begin
  if VarSameValue(V1, V2) then
    ShowMessage('Same value');
end;

Functions to convert a variant to a specific type.

By using following variant function we can convert a variant to a specific data type.

VarToDateTime
Converts a specified variant to a TDateTime value.

VarToStr
Converts a variant's value to a string.

VarToStrDef
Converts a variant's value to a string if error returns default value.

VarToWideStr
Converts a variant's value to a WideString.

VarToWideStrDef
Converts a variant's value to a WideString if error returns default value.

Delphi does not have function to convert a variant to Integer, Float or Boolean so here I have explained how to create custom functions to convert variant to Integer, Float or Boolean type.

Custom functions for variant type cast

  function VarToInt(vVar : Variant) : Integer;
  function VarToIntDef(vVar : Variant; Default : Integer) : Integer;
  function VarToDouble(vVar : Variant) : Double;
  function VarToDoubleDef(vVar : Variant ; Default : Double) : Double;
  function VarToFloat(const V: Variant; const ADefault: Extended): Extended;
  function VarToFloatDef(const V: Variant; const ADefault: Extended; const lFormatSettings: TFormatSettings): Extended;
  function VarToBool(vVar : Variant) : Boolean;
  function VarToBoolDef(vVar : Variant; Default : Boolean) : Boolean;
  function VarToDateTimeDef(vVar : Variant ; Default : TDateTime) : TDateTime;

.........................

function VarToIntDef(vVar : Variant; Default : Integer) : Integer;
begin
  if VarType(vVar) in [varEmpty or varNull] then
    Result := Default
  else
    try
      Result := vVar;
    except
      Result := Default;
    end;
end;

function VarToInt(vVar : Variant) : Integer;
begin
  Result := VarToIntDef(vVar, 0);
end;

function VarToDoubleDef(vVar : Variant ; Default : Double) : Double;
begin
  if VarType(vVar) in [varEmpty or varNull] then
    Result := Default
  else
    try
      Result := vVar;
    except
      Result := Default;
    end;
end;

function VarToDouble(vVar : Variant) : Double;
begin
  Result := VarToDoubleDef(vVar, 0);
end;

function VarToFloatDef(const vVar: Variant; const Default: Extended; const AFormatSettings: TFormatSettings): Extended;
begin
  if VarType(vVar) in [varEmpty or varNull] then
    Result := Default;
  else
    try
      Result := StrToFloatDef(vVar, Default, AFormatSettings);
    except
      Result := Default;
    end;
end;

function VarToFloat(const vVar: Variant; const Default: Extended): Extended;
begin
  Result := VarToFloatDef(vVar, Default, FormatSettings);
end;

function VarToBoolDef(vVar : Variant; Default : Boolean) : Boolean;
begin
  if VarType(vVar) in [varEmpty or varNull] then
    Result := Default;
  else
    try
      Result := StrToBoolDef(vVar, Default);
    except
      Result := Default;
    end;
end;

function VarToBool(vVar : Variant) : Boolean;
begin
   Result := VarToBoolDef(vVar, Default, FormatSettings);
end;

function VarToDateTimeDef(vVar : Variant ; Default : TDateTime) : TDateTime;
begin
  if VarType(vVar) in [varEmpty or varNull] then
    Result := Default
  else
    try
      Result := VarToDateTime(vVar);
    except
      Result := Default;
    end;
end;

Variant Arrays

We cannot declare array of variant like ordinary data types we do and We cannot assign an ordinary static array to a variant. Instead, we can create a variant array by calling either of the standard functions VarArrayCreate or VarArrayOf. 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.

VarArrayCreate 
The VarArrayCreate function creates a Variant array. The first argument specifies the number of dimensions and the bounds for each dimension. The second argument is the Variant type of each array element.

For example:

V: Variant;
   ...
V := VarArrayCreate([0, 2], varInteger);
V[0] := 111;
V[1] := 222;
V[2] := 333;

Here above it 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.

VarArrayOf
VarArrayOf returns a one-dimensional variant array with the elements given by the Values parameter.  The resulting array has a lower bound of zero and an upper bound of the array size minus 1.

Example

The following examples do the same thing.

V := VarArrayOf([111, 222, 333]);

Why not to use variant data type intensively in Delphi?

The Variant data type provides a flexible general purpose data type and it can hold anything but Variants are useful in specific circumstances, where data types and their content are determined at run time rather. We should not use variants unnecessarily because.

1. A variant stores type information along with the data, allowing a number of run-time operations; these operations can be handy but are both slow and unsafe. 
2. A variant is type-checked and computed at run time. The compiler won't warn you of possible errors in the code, which can be caught only with extensive testing. 
3. 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
4. Variants are slow. Codes that use Variant type is slow, not only when you convert data types, but also when you add two variant values holding an integer each. 
5. Variants offer greater flexibility but consume more memory than regular variables. 
6. 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 and performance wise slow.

OleVariant

To provide full OLE support, the 32-bit version of Delphi includes the Variant data type. 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. 


Comments

  1. You can definitely see your expertise in the work you write. The world hopes for even more passionate writers like you who are not afraid to say how they believe. Always go after your heart. apple hilfe berlin

    ReplyDelete

Post a Comment

Popular posts from this blog

MS Excel Automation in Delphi

ShellExecute in Delphi

Drawing Shapes in Delphi