A blog entry from Raymond Chen today made me realize the elegant solution to a problem i'm having.
Various shell functions, rather than all taking ITEMIDLIST
structure, can be made to only accept:
ITEMID_CHILD
IDLIST_RELATIVE
IDLIST_ABSOLUTE
ITEMID_CHILD_ARRAY
structures. The structures are all identical, but now you can enforce conceptual types at the compiler level.
i have a set of functions:
C:\Users\Ian\Desktop\AllisonAngel.jpg
)AllisonAngel.jpg
)C:\Users\Ian\Desktop
)And right now they're all declared as string
, e.g.:
function GetFilenames(Folder: string; ...): ...
function IsValidBatchFilename(Path: string): ...
function GetReportType(Filename: string): ...
and i have to trust that i'm passing the right stuff; and i'm trusting that developers (e.g. me), know the difference between:
i want to change the functions to use "typed" strings:
function GetFilenames(Folder: TFolderOnly; ...): ...
function IsValidBatchFilename(Path: TFullPath): ...
function GetReportType(Filename: TFilenameOnly): ...
Where:
type
TFullPath = type string;
TFolderOnly = type string;
TFilenameOnly = type string;
Except that there's no actual typing happening:
var
dropFolder: string;
begin
dropFolder := GetDropFolderPath(LCT);
GetFilenames(dropFolder); <-- no compile error
end;
What i want is a "distinct" type of string; that is string
insofar that it is length prefixed, reference counted, null terminated.
You can use advanced records to accomplish this. For instance, you could do
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;
and similarly for TPath
and TFolder
. Advantages:
TPath
will not accept a TFileName
(or some other combination).TPath
to/from a regular string. If you cast from a string to a TPath
, you will automatically check the string to see if it contains a valid path.TPath
can be assigned to a TFileName
(or some other combination), by writing more Implicit
class operators.Create different record types for each string type. Distinct record types are not assignment-compatible, even though string types are.
type
TFullPath = record value: string end;
TFolderOnly = record value: string end;
Chen's article compares the new shell feature to the classic STRICT macro that makes distinct handle types, and as I recall, declaring distinct structs is exactly how the STRICT macro works.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With