Как создать тип, который является строкой?
A запись в блоге от Raymond Chen сегодня заставила меня понять элегантное решение проблемы, с которой я сталкиваюсь.
Различные shell функции, а не все, принимающие структуру ITEMIDLIST
, могут быть приняты только для принятия:
-
ITEMID_CHILD
-
IDLIST_RELATIVE
-
IDLIST_ABSOLUTE
-
ITEMID_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
, и должен все равно протестировать содержимое в ваших процедурах.