Советы по Delphi

         

Работа оператора CASE со строками


В электронной справке Delphi по Borland Pascal на тему "Case" говорится следующее:
Селектор должен иметь перечислимый тип byte- или word-размера, поэтому строки и целочисленный тип Longint не могут быть использованы селектором.

Обратите внимание на то, что селекторные типы не накладывают ограничений на использование перечислимых типов, определяемых пользователем (логические типы здесь также работают). Следующий код будет верным:

    type
TMyType = ( mt1, mt2, mt3 ) ;
var
MyType : TMyType ;
begin
{ ... код, присваивающей значение MyType }

case MyType of mt1 : DoMT1Stuff ; mt2 : DoMT2Stuff ; mt3 : DoMT3Stuff ; end ;

Данная характеристика, вкупе с Delphi Run-Time Type Information (информация времени выполнения), позволяет вам эффективно использовать в селекторе даже строки. Кто хочет узнать как это можно сделать, советую обратиться к моей колонке "Tricks and Tips" в майском номере журнала _The Delphi Magazine_; или (если очень интересно), я могу опубликовать это на сервере.

Предыдущий оратор ввел читателей в состояние заинтригованности, но я думаю все гораздо проще, по всей видимости речь идет о простом преобразовании строки с числом в целочисленное значение:

    var
I : string ; begin
{ ... код, показывающий в строке диапазон условного перехода } Case StrToInt( I ) OF 10..20 : Showmessage('Тест 10-20'); 21..30 : Showmessage('Тест 21-30'); end ;

Или, как альтернатива:

    var
I : integer ; begin
I := StrToInt( { ...код, получающий от пользователя строковое значение } ) ; Case I of 10..20 : Showmessage('Тест 10-20'); 21..30 : Showmessage('Тест 21-30'); end ;

Отдельные значения в теле оператора CASE должны быть константами целочисленного типа.
Правильно, или константы перечислимого типа (логические или определенные пользователем); и снова из электронной справки:
Все используемые константы должны быть уникальными и иметь порядковый тип, совместимый с селекторным типом.

Нельзя использовать переменные (к сожалению). Если вы хотите использовать в качестве селекторов строки, то вам нужно воспользоваться конструкцией IF..THEN..ELSE.
Отступление: ограничение оператора "case", связанное с невозможностью работы со строками, имеется во всех современных процедурных языках (например, C, Basic, Ada), а не только в Паскале. Если ожидаемые строки определены заранее, упомянутый выше способ работы через RTTI осуществляется с помощью конструкций if-then-else.

Как можно воспользоваться Delphi Run Time type information (RTTI) для использования строк в конструкции CASE:

Возможности и характеристики Delphi RTTI не очень хорошо документированы, иногда приходится доставать информацию по крупицам; но я обнаружил замечательную возможность приведения строк к константам перечислимого типа посредством функции GetEnumValue(), которая слегка документирована в файле TYPINFO.INT. Данный файл можно обнаружить в каталоге \DELPHI\DOCS из стандартной поставки Delphi 1.0 (те, кто использует более поздние версии, должны подтвердить, или опровергнуть эту информацию, я же не проверял работу этой функции в других версиях Delphi, поэтому к моему коду нужно отнестись с осторожностью).

Предположим, мы определили собственный перечислимый тип, как показано ниже:

    ....
type

TMyEnumType = ( metItem1, metItem2, metItem3 ) ; ....

Я могу использовать некоторый строковый входной поток (от editbox, listbox или файла) для выбора в конструкции case одного из определенного выше значения:

    ....
{ не забудьте добавить TypInfo в список используемых модулей! }

var
S : string ; MyType : TMyEnumType ; begin
{
сначала получаем строку, которая дублирует нумерованную константу; например 'metItem1' из какого-то источника }
GetString( S ) ;
{
теперь преобразовываем строку в константу TMyEnumType }

MyType := TMyEnumType( GetEnumValue( TypeInfo( TMyEnumType ), S ) ;
{
Теперь используем MyType в качестве селектора }

case MyType of metItem1 : DoItem1Stuff ; metItem2 : DoItem2Stuff ; metItem3 : DoItem3Stuff ; end ; ....

Мы этого хотели, не так ли? Теперь попробуем с помощью выражения GetEnumValue() обратится к нашему значению:

    TMyEnumType( GetEnumValue( TypeInfo( TMyEnumType ), S ) ;

Вызов GetEnumValue() требует два параметра, первый - указатель на запись RTTI запрашиваемого типа, второй - простоя паскалевская строка. Функция TypeInfo() является системной функцией (наряду с TypeOf() и SizeOf()) и возвращает указатель RTTI на тип. GetEnumValue() возвращает целое, являющееся порядковым номером определенной константы набора; возвращается -1, если невозможно преобразовать строку в константу данного типа. (грамотно! программы более низкого уровня часто в этих случаях вызывают исключительную ситуацию или еще чего похуже). И, наконец, возвращаем целое, приведенное к перечислимой константе с помощью "обертки" TMyEnumType.

Конечно, вы могли бы и не работать со стоками, и не передавать их в GetEnumValue(), а просто использовать их так, чтобы они БЫЛИ ПОХОЖИ на нумерованные константы; скажем, мы могли бы вынести их в список ListBox: Item 1 Item 2 Item 3 Затем (предположим, у вас имеется функция Strip(), удаляющая пробелы из строки) вы можете сделать примерно так:

    GetString( S ) ;
S := 'met' + Strip( S ) ;

и передайте это GetEnumValue.

Кстати, из журнала New Orleans Style Lagniappe ("раздел хитростей"): я узнал о существовании обратной функции RTTI GetEnumName(), преобразующей нумерованную константу в представление строки. Например, следующий код

    var
S : string ;

begin
S := GetEnumName( TypeInfo( TMyEnumType ), Ord( metItem1 ))^ ;

должен возвратить в S значение 'metItem1'. Следует обратить внимание на то, что символ указателя "^" в конце функции GetEnumName() НЕ опечатка, GetEnumName() возвращает значение типа PString, к которому применено действие, превращающее его в строку.

Я надеюсь, что мое несколько путанное и несвязанное повествование кому-то поможет. Я также был бы благодарен людям, проверившим бы данную технологию в более старших версиях Delphi.

[001995]



Содержание раздела