Как создать тип, который является строкой?

A запись в блоге от Raymond Chen сегодня заставила меня понять элегантное решение проблемы, с которой я сталкиваюсь.

Различные shell функции, а не все, принимающие структуру ITEM­ID­LIST, могут быть приняты только для принятия:

  • ITEM­ID_CHILD
  • ID­LIST_RELATIVE
  • ID­LIST_ABSOLUTE
  • ITEM­ID_CHILD_ARRAY

структуры. Структуры все идентичны, но теперь вы можете применять концептуальные типы на уровне компилятора.

Вернуться к Delphi

i имеет набор функций:

  • некоторые только берут путь: (например, C:\Users\Ian\Desktop\AllisonAngel.jpg)
  • некоторые только принимают имя файла: (например, AllisonAngel.jpg)
  • некоторые только берут папку: (например, C:\Users\Ian\Desktop)

И сейчас они все объявлены как string, например:

 function GetFilenames(Folder: string; ...): ...
 function IsValidBatchFilename(Path: string): ...
 function GetReportType(Filename: string): ...

и я должен верить, что я передаю нужные вещи; и я доверяю разработчикам (например, мне), знаю разницу между:

  • a путь
  • a имя_файла
  • и папку

Я хочу изменить функции, чтобы использовать "типизированные" строки:

 function GetFilenames(Folder: TFolderOnly; ...): ...
 function IsValidBatchFilename(Path: TFullPath): ...
 function GetReportType(Filename: TFilenameOnly): ...

Где:

type
   TFullPath = type string;
   TFolderOnly = type string;
   TFilenameOnly = type string;

За исключением того, что фактическое типирование не происходит:

var
   dropFolder: string;
begin
   dropFolder := GetDropFolderPath(LCT);

   GetFilenames(dropFolder); <-- no compile error

end;

То, что я хочу, это "отличный" тип строки; то есть string в той мере, в какой это префикс длины, отсчет ссылок, нуль завершен.

Ответ 1

Для этого вы можете использовать расширенные записи. Например, вы могли бы сделать

type
  TFileName = record
    FFileName: string;
  public
    class function IsValidFileName(const S: string): boolean; static;
    class operator Implicit(const S: string): TFileName;
    class operator Implicit(const S: TFileName): string;
  end;

implementation

class function TFileName.IsValidFileName(const S: string): boolean;
begin
  result := true {TODO};
end;

class operator TFileName.Implicit(const S: string): TFileName;
begin
  if IsValidFileName(S) then
    result.FFileName := S
  else
    raise Exception.CreateFmt('Invalid file name: "%s"', [S]);
end;

class operator TFileName.Implicit(const S: TFileName): string;
begin
  result := S.FFileName;
end;

и аналогично для TPath и TFolder. Преимущества:

  • Функция, ожидающая TPath, не примет TFileName (или некоторую другую комбинацию).
  • Вы все равно можете назначить TPath в/из обычной строки. Если вы отбросили строку до TPath, вы автоматически проверите строку, чтобы увидеть, содержит ли она допустимый путь.
  • Необязательно, вы можете указать, как TPath можно назначить TFileName (или некоторую другую комбинацию), написав больше операторов класса Implicit.

Ответ 2

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

type
  TFullPath = record value: string end;
  TFolderOnly = record value: string end;

В статье Чэнь сравнивается новая функция оболочки с классическим макросом STRICT, который создает различные типы дескрипторов, и, как я помню, объявление различных структур - это то, как работает макрос STRICT.

Ответ 3

С тем, как Delphi обрабатывает базовые типы, я довольно, это случай "вы не можете добраться отсюда".

Ваши объявления типа строки будут удовлетворять правилам Delphi для совместимости типов и совместимости присвоений. То, что они будут ограничивать, - это процедурные объявления с параметрами var. Если вы изменили объявления функций как параметры var вместо ссылочных или значений, вы получите ошибку компиляции в своем последнем примере.

Все, что сказал, все равно бесполезно. У вас все еще нет способа убедиться, что входной файл является только именем файла, даже с типом TFilenameOnly, и должен все равно протестировать содержимое в ваших процедурах.