вторник, 10 мая 2011 г.

Вариантные типы

Перевод раздела справочной системы Delphi Variant Types

Обзор вариантов

Иногда бывает необходимо работать с данными, тип которых различается или не может быть определен на момент компиляции программы. В этих случаях, одним из решений может быть использование переменных и параметров типа Variant, который представляет собой значения, изменяемые при выполнении программы. Тип Variant предлагает большую гибкость, но требует больше памяти, чем обычные переменные, и операции с ним медленнее, чем со статическими типами. Более того, в отличие от обычных типов данных, при работе с которыми ошибки зачастую могут быть выявлены при компиляции, недопустимые операции над данными этого типа часто вызывают ошибки при выполнении программы (runtime errors). Вы можете создавать собственные вариантные типы.


По умолчанию переменные типа Variant могут хранить данные любого типа, кроме записей, множеств, статических массивов, файлов, классов, ссылок на классы и указателей. Другими словами, вариантный тип может хранить любые данные, за исключением структурированных типов данных и указателей. Они могут хранить интерфейсы и обеспечивать доступ к их методы и свойствам. Они могут хранить динамические массивы и особые виды статических массивов, называемых вариантными массивами. При работе с данными типа Variant (при присваивании и в выражениях) их можно смешивать с другими значениями этого типа, а также с целыми, вещественными числами, строками и булевскими значениями. При этом компилятор будет автоматически производить необходимые преобразования типов.

Переменные типа Variants, содержащие строки, не могут индексироваться. То есть если V – это значение типа variant, которое содержит строковое значение, то обращение к конструкции V[1] вызовет ошибку выполнения программы.

Вы можете определить собственные типы Variant для расширения этого типа и хранения произвольных значений. Например, вы можете определить строковый вариантный тип, который будет позволять индексирование строк или хранение каких-либо ссылок на класс, записей или статических массивов. Пользовательские вариантные типы определяются при помощи создания наследников класса TCustomVariantType.

Замечание:Эта и практически вся функциональность вариантного типа реализована в модуле Variants.

Значение типа variant занимает 16 байтов памяти и состоит из значения (либо указателя на значение) и кода типа этого значения. Значения типа variant инициализируются при создании особым значением Unassigned. Особое значение Null указывает на отсутствие или неизвестный тип данных.

Стандартная функция VarType возвращает код типа данных для значения variant. Константа varTypeMask – это битовая маска, используемая для получения кода из значения, возвращаемого VarType. Например:

VarType(V) and varTypeMask = varDouble

Возвращает True, в том случае, если V содержит значение типа Double или массив значений типа Double. (Маска просто прячет первый бит, который указывает, содержит ли переменная типа Variant массив или нет). Запись TVarData, определенный в модуле System может использоваться для преобразования значений типа variants и получения доступа к их внутреннему представлению.


Преобразование типа Variant

Все целочисленные, строковые и булевские типы совместимы по присваиванию с типом Variant. Выражения могут быть явно преобразованы к типу varaint, а стандартные подпрограммы VarAsType и VarCast могут использоваться для изменения внутреннего представления в переменной типа variant. Следующий пример демонстрирует применение типа Varaint и некоторые автоматические преобразования, производимые в случае смешивания типа Varaint с остальными типами данных:

var
   V1, V2, V3, V4, V5: Variant;
   I: Integer;
   D: Double;
   S: string;
 begin
    V1 := 1; { целочисленное значение }
    V2 := 1234.5678; { вещественное значение }
    V3 := 'Hello world!'; { строковое значение}
    V4 := '1000'; { строковое значение }
    V5 := V1 + V2 + V4; { вещественное значение 2235.5678}
    I  := V1; { I = 1 (целочисленное значение) }
    D  := V2; { D = 1234.5678 (вещественное значение) }
    S  := V3; { S = 'Hello world!' (строковое значение) }
    I  := V4; { I = 1000 (целочисленное значение) }
    S  := V5; { S = '2235.5678' (строковое значение) }
 end;


Компилятор производит преобразование по следующим правилам:
Целевой/Исходный:integerrealstringBoolean
IntegerПреобразование целочисленных форматовПреобразует в вещественный типПреобразует в строковое представлениеВозвращает False если 0, в остальных случаях True
realОкругляет до ближайшего целогоПреобразует вещественный форматПреобразует в строковое представление с использованием региональных настроекВозвращает False если 0, в остальных случаях True
stringПреобразует в целое число, выполняя "обрезку" значения , если это необходимо. Если строка не является представлением числа – возникает исключительная ситуацияПреобразует в вещественное число с использованием региональных настроек. Если строка не является представлением числа – возникает исключительная ситуация.Преобразует строковый/символьный форматыВозвращает False в том случае если строка 'false' (без учета регистра) или строка содержит число равное 0, возвращает True если строка 'true' или строка содержит число неравное 0; в остальных случаях возникает исключительная ситуация
characterАналогично строковому типу (в предыдущей строке)Аналогично строковому типу (в предыдущей строке)Аналогично строковому типу (в предыдущей строке)Аналогично строковому типу (в предыдущей строке)
BooleanFalse = 0, True: все биты установлены в 1 (-1 в случае с Integer, 255 в случае с Byte, и т.д.)False = 0, True = 1False = 'False', True = 'True' по умолчанию. Преобразование зависит от глобальной переменнойFalse = False, True = True
UnassignedВозвращает 0Возвращает 0Возвращает пустую строкуВозвращает False
NullЗависит от глобальной переменной (по умолчанию вызывает исключительную ситуацию) Зависит от глобальной переменной (по умолчанию вызывает исключительную ситуацию)Зависит от глобальной переменной (по умолчанию вызывает исключительную ситуацию)Зависит от глобальной переменной (по умолчанию вызывает исключительную ситуацию)

Присваивания значений, выходящие за рамки диапазона типа часто приводят к тому, что целевая переменная получает максимальное значение из своего диапазона значений. Неверные операции, присваивание или преобразования типов значений переменных Variant вызывают исключительную ситуацию класса Variants.EVariantError (или класса-наследника EvariantError).

Специальные правила действуют для преобразования типа System.TDateTime, объявленного в модуле System. When a System.TDateTime преобразуется в любой другой тип, при этом он обрабатывается как тип Double. Когда значения типов integer, real или Boolean преобразуются в System.TDateTime, они сначала преобразуются в Double, а затем читаются как значения типа TDateTime. При преобразовании строки в System.TDateTime, она интерпретируется как System.TDateTime с использованием региональных настроек. Когда значение Unassigned преобразовывается в System.TDateTime, оно обрабатывается как вещественное или целочисленное значение равное нулю. Преобразование значения Null в System.TDateTime вызывает исключительную ситуацию.

На платформе Win32, если значение типа variant ссылается на интерфейс COM, то любая попытка его преобразования вызовет чтение основного свойства объекта (default property) и преобразование его значения к целевому типу. Если объект не имеет основного свойства, - это вызовет исключительную ситуацию.



Значения типа Variant в выражениях

Все операторы, кроме ^, is и in принимают операнды типа variant. Кроме сравнений, которые всегда возвращают значение типа Boolean, все операции над значениями типа variant возвращают значение типа variant. Если выражение комбинирует значения типа variant со значениями статических типов, последние автоматически преобразовываются к значениям типа variant.

Это утверждение не верно для ситуации со сравнениями, где любая операция со значением Null возвращает Null. Например:

V := Null + 3;

Присваивает значение Null переменной V. По умолчанию сравнения обрабатывают значение Null как уникальное значение, которое меньше других значений. Например:

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

В этом примере секция else инструкции if никогда не будет выполнена. Такое поведение можно изменить установкой значений для глобальных переменных NullEqualityRule и NullMagnitudeRule.



Вариантные массивы

Вы не можете связать переменную типа variant с обычным статическим массивом. Вместо этого следует создавать вариантный массив, вызывая стандартные функции VarArrayCreate или VarArrayOf. Например:

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

Создает вариантный массив целых чисел с десятью элементами и связывает этот массив с переменной V.
Массив может индексироваться как V[0], V[1] и так далее, но передача элементов вариантного массива в подпрограммы по ссылке невозможна. Вариантные массивы всегда индексируются типом integer.

Второй параметр при вызове VarArrayCreate – это код базового типа для массива. Список кодов указан в описании функции VarType. Никогда не следует передавать код varString в VarArrayCreate. Для создания вариантного массива строк используйте varOleStr.

Переменные типа Variants могут хранить вариантные массивы различной длины, размерности и базовых типов. Элементы вариантного массива могут быть любого типа, разрешенного для типа Variant, кроме ShortString и AnsiString. Кроме того, если базовый тип массива – Variant, его элементы могут быть гетерогенными. Для изменения размеров вариантного массива пользуйтесь функцией VarArrayRedim. Прочие функции, которые работают с вариантными массивами: VarArrayDimCount, VarArrayLowBound, VarArrayHighBound, VarArrayRef, VarArrayLock и VarArrayUnlock.

Замечание: Вариантные массивы пользовательских вариантных типов не поддерживаются, поскольку экземпляры пользовательских вариантных типов могут быть добавлены в вариантный массив VarVariant.

Когда переменная типа variant, содержащая вариантный массив связывается с другим вариантным массивом или передается как параметр-значение в подпрограмму, выполняется копирование всего массива. Не следует выполнять такие операции без необходимости, поскольку они неэффективны с точки зрения расходования памяти.


OleVariant

Основная разница между типами Variant и OleVariant заключается в том, что тип Variant может содержать типы данных, о которых "знает" приложение. Тип OleVariant может содержать типы, объявленные как совместимые с OLE Automation. Это означает, что данные этих типов могут передаваться от программе к программе или по сети, при этом можно не заботиться о том, как принимающая сторона будет их обрабатывать.

При присваивании значения типа Variant, содержащего пользовательские данные (такие как строковый тип Delphi или другой пользовательский тип) переменной типа OleVariant, библиотека пытается сконвертировать это значение в один из стандартных типов OleVariant (например, строковый тип Delphi преобразуется в строку OLE BSTR). Если значение типа variant содержит AnsiString присваивается переменной типа OleVariant, значение AnsiString преобразуется в WideString. Последнее справедливо и для передачи значений типа Variant в функцию, принимающую параметры типа OleVariant.

4 комментария:

  1. "На платформе Win32, если значение типа variant ссылается на интерфейс COM, то любая попытка его преобразования вызовет чтение основного свойства объекта (default property) и преобразование его значения к целевому типу"

    Объясните это поподробней пожалуйста.

    ОтветитьУдалить
    Ответы
    1. интерфейсы используют для доступа к объектам. сам по себе (без объекта) интерфейс смысла большого не несет.
      допустим, некая переменная типа вариант содержит ссылку на интерфейс. через этот интерфейс осуществляется доступ к некоторому объекту. у объекта может быть объявлено (а может быть не объявлено) основное свойство. ну и так далее.
      как то так.

      Удалить
    2. Это понятно - не понятна вот эта строка
      "то любая попытка его преобразования вызовет чтение основного свойства объекта (default property)"
      Это в COM сервере вызовется default property ?
      И default property какого интерфейса, на который переменная сылается или интерфейса по умолчанию в COM сервере (если интерфейсов много в COM сервере )?

      Удалить
    3. смотрим оригинал: 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.
      я не вижу здесь упоминания com сервера. если у вас есть сомнения, - это хороший повод провести эксперимент.

      Удалить