# Commons — Usage Guide
🌍 Language: English | Español
Table of Contents
- Daf.MemUtils — Smart pointers
- Daf.Threading — Cancellation tokens and shutdown
- Daf.Enumerable
- Daf.Rtti
- Daf.Activator
- Daf.Arrays
- Daf.Nullable
- Daf.Comparers
- Daf.Application.Version
- Daf.Types
Daf.SystemProcess lives in a package of its own — see SystemProcess. Daf.CmdLn.Parser was withdrawn in 2.0; the command line is Daf.CommandLine.
Daf.MemUtils — Smart pointers
ARC<T>
ARC<T> is a smart pointer implemented as reference to function: T. Invoke it as a function to obtain the object; when all references go out of scope the object is freed automatically.
uses Daf.MemUtils;
// Create from an existing instance
var Doc := ARC.From<TDocument>(TDocument.Create);
// Create and auto-instantiate (requires parameterless constructor)
var Doc2 := ARC.From<TDocument>;
// Access the object — invoke as a function
Doc().Load('report.pdf');
WriteLn(Doc().Title);
// Pass around — ref count increases; freed when last copy exits scope
var Copy := Doc;Key rules
ARC<T>is areference to function: T(interface closure), not a record.- Access the object by calling the variable as a function:
MyArc(), not.Value. ARC.From<T>(nil)returnsnil.
Daf.Threading — Cancellation tokens and shutdown
ICancellationToken / ICancellationTokenSource
Use the factory functions to create a cancellation source — TCancellationTokenSource is an internal implementation class and is not instantiated directly.
uses Daf.Threading;
// Create via factory functions
var Cts := CreateCancellationTokenSource; // standard
// var Cts := CreateCancellationTokenSourceWithTimeout(5000); // auto-cancel after 5 s
// var Cts := CreateCanceledCancellationTokenSource; // already canceled
var Token := Cts.Token; // ICancellationToken
// In worker:
while not Token.IsCancellationRequested do
ProcessNextItem;
// Notify when canceled:
Token.Register(procedure begin WriteLn('Canceled!'); end);
// Cancel from caller:
Cts.Cancel;Note:
IFuture<T>is part of the Delphi RTL (System.Threading), not DAF. UseTTask.Future<T>(RTL) for background computations, orTSystemProcess.ExecuteAsync(DAF) for external process execution.
TShutdownHook
Connects the console’s shutdown signals to the application. The host subscribes on its own when it starts; assign the listener by hand only in a program without a host:
uses Daf.Threading;
TShutdownHook.OnShutdownRequested := procedure
begin
Logger.LogInformation('Shutdown requested, stopping…', []);
App.Stop;
end;The console handler is installed while there is a listener, as ConsoleLifetime does in .NET:
| Event | With a listener | Without one |
|---|---|---|
| Ctrl+C, Ctrl+Break; SIGINT, SIGQUIT and SIGTERM outside Windows | asks for the shutdown, each time it arrives | left to the system, which ends the process |
| Closing the console window, logging off | left to the system | left to the system |
TShutdownHook.HandleConsoleSignal(Signal) is what the handler does with each event, and answers whether it takes it. What the listener raises goes to the error output and does not leave the handler. RequestShutdown asks for the shutdown from code; without a listener, the request waits for the next one that subscribes. A Windows system shutdown does not reach a Delphi console application, which loads user32.dll.
Daf.Enumerable
ToIEnumerable<T>
Bridge record that adapts any TEnumerable<T> (class) to the IEnumerable<T> interface. Implicit conversion operators make usage transparent.
uses Daf.Enumerable;
var List := TList<Integer>.Create;
List.Add(1); List.Add(2); List.Add(3);
// Implicit conversion to IEnumerable<T>
var Seq: IEnumerable<Integer> := ToIEnumerable<Integer>(List);
for var Item in Seq do
Process(Item);
// Convert back to a plain array
var Arr := ToIEnumerable<Integer>(List).ToArray;TDeferredEnumerable<T>
IEnumerable<T> built from a closure that returns the enumerator — the deferred sequence primitive. Nothing runs until GetEnumerator is called, and every iteration invokes the factory again, so the same sequence can be walked more than once.
uses Daf.Enumerable;
var Calls := 0;
var Seq: IEnumerable<Integer> := TDeferredEnumerable<Integer>.Create(
function: IEnumerator<Integer>
var
N: Integer;
begin
Inc(Calls);
N := 0;
Result := TGeneratorEnumerator<Integer>.Create(
function(var Current: Integer): Boolean
begin
Result := N < 3;
if Result then
begin
Inc(N);
Current := N;
end;
end);
end);
// Calls is still 0: declaring the sequence enumerated nothing
for var I in Seq do // Calls = 1, yields 1, 2, 3
Process(I);
for var I in Seq do // Calls = 2 — a new enumerator, walked from the start
Process(I);This is the primitive Daf.Linq is built on: Where, Select, Take and the rest each return one of these wrapping the previous sequence, which is what makes a chain of operators traverse the source once, and only when a terminal asks for an element. See Daf.Linq.
TGeneratorEnumerator<T>
The enumerator of the pair above, built from an advance-and-yield closure: a TGeneratorFunc<T> is a function(var Current: T): Boolean that returns False when the sequence is exhausted and otherwise writes the next element into Current. The state of the walk lives in the closure, so a sequence with no collection behind it — a counter, a file being read, a computed series — needs no enumerator class of its own.
Reset raises ENotImplemented: a closure that has already advanced cannot be rewound. Walking a sequence a second time is what TDeferredEnumerable<T> is for — it asks its factory for a new enumerator, with its own state.
IInterfaceList<T>
The RTL’s TInterfaceList with a typed GetEnumerator on top, which is all it adds: the loop variable is a T, not an IInterface.
var List := TInterfaceList<IMyService>.Create;
List.Add(ServiceA);
List.Add(ServiceB);
for var Svc in List do // Svc is IMyService
Svc.Execute;IsEmpty, TryFind, First, Last and ItemType are on the non-generic TInterfaceList this unit also declares, not on the generic one:
var Any := TInterfaceList.Create;
Any.Add(ServiceA);
var Found: IMyService;
if not Any.IsEmpty and Any.TryFind(
function(I: IInterface): Boolean
begin Result := Supports(I, IMyService); end,
Found) then
Found.Execute;Daf.Rtti
Helpers for Delphi’s Extended RTTI, centralised in the _T utility class.
uses Daf.Rtti;
// Type names and GUIDs
var Name := _T.NameOf<IMyService>; // 'IMyService'
var Guid := _T.GUID<IMyService>; // TGUID
var UnitName := _T.UnitNameOf<IMyService>; // 'MyUnit'
// Check interface hierarchy (PTypeInfo overloads)
if _T.Extends(TypeInfo(IChildService), TypeInfo(IMyService)) then
WriteLn('IChildService extends IMyService');
// Check whether a class has a property
if _T.HasProperty<TMyClass>('Name') then
WriteLn('Name property exists');
// Create default (zero) value for any type
var Zero := _T.Default<Integer>; // 0TRttiTypeHelper / TRttiPackageHelper
var RC := TRttiContext.Create;
var T := RC.GetType(TypeInfo(TMyClass));
if T.IsCollection then WriteLn('Is a collection');
if T.Implements<IMyService> then WriteLn('Implements IMyService');
// Discover all implementations of an interface in a package
RC.FindType('MyUnit.TMyClass').AsInstance.DeclaringUnitPackage
.DiscoverImpl<IMyService>(False, nil,
procedure(Intf: TRttiInterfaceType; Impl: TRttiInstanceType)
begin WriteLn(Impl.Name); end);Daf.Activator
Create class instances by class reference or type info via RTTI.
uses Daf.Activator;
var Instance := TActivator.CreateInstance(TMyServiceClass) as IMyService;Daf.Arrays
TArrayHelper
Class helper for the stock TArray — adds Map (projection) and Trim (strip whitespace / remove empty strings).
uses Daf.Arrays;
// Map: project [string] -> [Integer]
var Lengths := TArray.Map<string, Integer>(Names,
function(S: string): Integer begin Result := S.Length; end);
// Trim: strip whitespace and remove empty elements from a string array
var Clean := TArray.Trim([' hello ', '', ' world ']);
// Clean = ['hello', 'world']TSmartArray<T>
Value-type wrapper around TArray<T> with a rich LINQ-style API. Supports operators in, +, =, implicit conversion to/from TArray<T>, and CSV-string implicit conversion.
var Items: TSmartArray<string>;
Items.Concat('alpha');
Items.Concat('beta');
Items.Concat('gamma');
// Membership
if 'beta' in Items then WriteLn('Found');
// Filter
var Long := Items.Where(
function(S: string): Boolean begin Result := Length(S) > 4; end);
// Project
var Upper := Items.Select<string>(
function(S: string): string begin Result := S.ToUpper; end);
// Concatenation via operator +
var More: TSmartArray<string> := Items + ['delta', 'epsilon'];
// Sort
Items.Sort;Where / Any take a TFilterSpec<T> and Select a TFunc<T, TTarget> — the very types the lazy Daf.Linq operators take, so a filter or a mapper written once works with both APIs. Inline closures convert implicitly, as above; build the value separately when you want to reuse or compose it:
uses Daf.FilterSpec;
var Long: TFilterSpec<string> := TPredicate<string>(
function(S: string): Boolean begin Result := Length(S) > 4; end);
var Loud: TFilterSpec<string> := TPredicate<string>(
function(S: string): Boolean begin Result := S = S.ToUpper; end);
Items.Where(Long and not Loud); // composed with and / or / notDaf.Nullable
TNullable<T>
An optional value: either an absent value — SQL NULL — or a present value of T. What it buys is the distinction between no value and a present default: 0, '' and False are values, and a T alone cannot say which of the two it is.
uses Daf.Nullable;
var Age: TNullable<Integer> := TNullable<Integer>.Null;
Age.IsNull; // True
Age.GetValueOrDefault(-1); // -1
Age := 42; // implicit conversion from T
Age.HasValue; // True
Age.Value; // 42
var Plain: Integer := Age; // implicit conversion back to T| Member | Answers |
|---|---|
Null |
An explicitly absent nullable |
From(Value) |
Wraps a present value; the implicit conversion from T does the same |
IsNull |
True when no value is present |
HasValue |
True when one is. HasValue = not IsNull |
GetValueOrDefault(Fallback) |
The value when present, otherwise the fallback given |
Value |
The present value. When NULL it raises EInvalidOpException, and so does the implicit conversion to T |
Both presence predicates ship on purpose. IsNull is the data-domain idiom, the one that matches Delphi’s own TField.IsNull and SQLCute’s IS NULL; HasValue is the .NET one. Whichever reads better where it is written is the right one.
A newly declared TNullable<T> starts absent: Initialize sets it so, and that is a managed record operator, so a field of a class or an element of an array starts absent too without anybody initializing it.
Daf.Comparers
NULL-aware comparison, in two coordinated policies: ordering puts NULL below every non-NULL value — nulls-first ascending, nulls-last descending — and two NULLs compare equal; equality makes NULLs equal, which is what collapses duplicate NULLs under DISTINCT.
uses Daf.Comparers;
// Over TValue — the value currency of the in-memory query engine
var Order := TValueComparers.Ordinal;
var Same := TValueComparers.Equality;
// Over TNullable<T>
var ByAge := TNullableComparers.Ordinal<Integer>;| Entry point | Answers |
|---|---|
TValueComparers.Ordinal |
IComparer<TValue>: NULL below every non-NULL value, NULLs equal; two values of different families raise EArgumentException |
TValueComparers.Equality |
IEqualityComparer<TValue>: values of different families are never equal, NULLs are equal, and equal values share their hash |
TNullableComparers.Ordinal<T> |
The same order over TNullable<T> |
TNullableComparers.Equality<T> |
Two absent values are equal |
IsNullValue(V) |
Whether a TValue represents SQL NULL: empty, a Null/Unassigned Variant, or an absent TNullable |
CompareValuesNullSmallest(L, R) |
The comparison itself, without going through a comparer |
Every non-NULL value belongs to a family: Boolean, number, date, text or other. A value of one family is never equal to one of another: the text '1' is not the number 1, as in .NET. Two numbers are equal when they are worth the same, whatever their type: 1 and 1.0 are equal, as in SQL; in .NET, 1.Equals(1.0) is false. Ordering two values of different families raises EArgumentException, like .NET’s Comparer<object>. Within a family, text orders ordinally, False comes before True, and dates and numbers order by value.
Over TValue, SQL NULL is modelled as an empty TValue, a Variant-kind one holding Null or Unassigned, or an absent TNullable; a present TNullable counts as the value it holds. This is what SQLCute.InMemory builds its ANSI NULL semantics on — see SQLCute.InMemory.
Daf.Application.Version
TVersionInfo / [Version]
A class declares its version with the [Version] attribute — SemVer tag, application name, company and, optionally, whether the binary is a debug build — and TVersionInfo.GetFrom reads it back by RTTI:
uses
Daf.Application.Version;
type
[Version('1.2.3-rc.2+build.7', 'Invoicer', 'Acme')]
TInvoicerApp = class
end;
var Info := TVersionInfo.GetFrom(TInvoicerApp);
// Info.Major = 1, Info.Minor = 2, Info.Patch = 3
// Info.PreRelease = 'rc.2', Info.Metadata = 'build.7'
// Info.ApplicationName = 'Invoicer', Info.Company = 'Acme'| Member | What it answers |
|---|---|
Tag |
the string the attribute declared, verbatim |
VersionTag(Template) |
the version formatted with a template of ${...} markers; VersionTemplate is the default, VersionTemplateWithBuild adds the compiled metadata |
IsEmpty |
whether a version was set at all |
IsSemVer |
whether the declared tag follows the SemVer grammar |
Major / Minor / Patch / PreRelease / Metadata |
the SemVer pieces |
ApplicationName / Company |
the other two arguments of the attribute |
ArchBits / Platform |
what the compiler saw: 32 or 64, and the target platform |
Debug |
whether the binary declared itself a debug build; False when it declared nothing |
Debug is declared by the binary. Only the binary’s own code sees its build configuration — the framework is compiled apart —, and the attribute’s arguments are evaluated when that code compiles:
[Version('1.2.3', 'Invoicer', 'Acme', {$IFDEF DEBUG}True{$ELSE}False{$ENDIF})]
TInvoicerApp = class
end;TVersionInfo.Create takes the same fourth argument. The information is fixed when it is built: Debug has no setter.
A class with no attribute gives an empty record, not an error: IsEmpty answers True, Tag and the two names come back as empty strings, the numeric pieces as zero, and VersionTag as the empty string.
A tag that is not SemVer keeps its text. IsSemVer answers False, and VersionTag reports the declared string rather than rebuilding it from pieces that were never filled.
The markers a template can name:
| Marker | Emits |
|---|---|
${Major} ${Minor} ${Patch} ${Metadata} ${Tag} |
the SemVer pieces and the declared string, raw |
${PreRelease} |
the pre-release with its own -, or nothing |
${Platform} ${ArchBits} |
the target platform and the word size |
${CompiledMeta} |
both together — Win32 —, followed by .Dbg when the binary declared a debug build |
${Build} / ${BuildFull} |
the build section, opened with +. The first carries what the tag declared; the second joins it with the compiled metadata inside that single section. They are alternatives: a template names one |
SemVer admits one build section, so ${BuildFull} separates the two sources with . rather than opening a second +.
The attribute is read by RTTI, so the linker has to keep the class: a program that relies on this declares STRONGLINKTYPES and puts the class in its interface section.
Daf.Types
TDeferred<T>
A value given either directly or as a thunk, and re-evaluated on every read. The implicit conversions are what make the two forms interchangeable at the call site:
uses Daf.Types;
var Source := 'before';
var Fixed: TDeferred<string> := 'literal';
var Live: TDeferred<string> := TFunc<string>(
function: string
begin
Result := Source;
end);
Source := 'after';
var A: string := Fixed; // 'literal'
var B: string := Live; // 'after' — read now, not when it was registeredIt is not Lazy<T>. That one memoizes: it runs the thunk once and keeps the answer. This one runs it again on each read, which is what macro resolution needs — a macro whose value tracks something that moves.
Nor is it .NET’s Expression<TDelegate>. An expression there is an inspectable tree; a TDeferred<T> is a closure and nothing can look inside it. The inspectable tree is IExpr and TSpecification<T> in Daf.Specification.
TMacroExpression is TDeferred<string> under the name the macro list uses.
TMacrosList
A named set of TMacroExpression, and the substitution of ${name} inside a string.
var Macros := TMacrosList.Create;
try
Macros['product'] := 'DAF';
Macros['edition'] := TFunc<string>(
function: string begin Result := CurrentEdition; end);
Macros.ResolveMacros('${product} ${edition}'); // 'DAF Community'
finally
Macros.Free;
end;| Member | Answers |
|---|---|
Value[Name] (the default property) |
Reads and writes a macro. Reading resolves the thunk |
ResolveMacros(Source, MaxRecursionLevel) |
Substitutes every ${name} in Source. A macro whose value names another macro is resolved too, up to MaxRecursionLevel — 100 by default, which is what stops a pair that names each other |
Contains(Key) Count |
What is registered |
Keys[I] ValueFromIndex[I] |
Registration order is kept: the list is an ordered dictionary |
A macro that is not registered
Out of the box, a reference to a macro that is not registered resolves to the empty string. MissingMacroPolicy changes that, and OnMissingMacro is asked first:
Macros.MissingMacroPolicy := mmpError;
Macros.ResolveMacros('${root}/charts');
// EMacroNotFound: No macro is registered with the name "root"| Member | Answers |
|---|---|
MissingMacroPolicy |
mmpReturnDefault, the default, substitutes MissingMacroValue, which is empty unless set · mmpKeepToken leaves ${name} in the result · mmpError raises EMacroNotFound |
OnMissingMacro |
Called for each reference to a macro that is not registered, before the policy. It sets Value and Handled to resolve it, or leaves Handled False to hand it to the policy |
MissingMacroError(Name) |
The exception mmpError raises. A hook that has to fail raises this one: raise TMacrosList(Sender).MissingMacroError(Name) |
The policy applies to ResolveMacros only: reading Macros['name'] of a macro that is not registered answers empty whatever the policy. Contains asks whether it is there.
TEnvVars
The process’s environment variables read into a dictionary, and written back out as the environment block a child process takes.
var Env := TEnvVars.Create;
try
for var Pair in Env.Vars do
Writeln(Pair.Key, '=', Pair.Value);
Env.Refresh; // read the system again, in case it moved
finally
Env.Free;
end;| Member | Answers |
|---|---|
Vars |
The dictionary collected at construction. Refresh reads the system again |
Buffer |
The same set as the null-separated block CreateProcess expects |
WriteTo(Buffer) |
Writes that block into a caller’s pointer — how a child process is given an environment with entries added to the parent’s |
ClearBuffer |
Releases the block |
Daf.Configuration reads Vars to turn the environment into a configuration source, and Daf.SystemProcess uses WriteTo to hand a child its own environment.
Coalesce
The first of several interface references that is not nil.
var Chosen := Coalesce.From<ILogger>(Explicit, FromContainer);
var First := Coalesce.From<ILogger>([Explicit, FromContainer, Fallback]);Both arguments are evaluated before the call, as in any function: this picks between references already held, and it is not a short-circuit.
Debugger
Debugger.Write('cache primed with %d entries', [Count]);
if Debugger.Enabled then
Debugger.Write(Report);| Member | Answers |
|---|---|
Write(Text) / Write(FmtStr, Args) |
Writes to the system debug output. On Windows that is OutputDebugString; outside Windows it writes nothing — for a destination that works there, see TPlatformLogBootstrapSink in Daf.FMX |
Enabled |
Whether a debugger is attached (System.DebugHook). Not whether the build is a Debug one |
CurrentProcessId CurrentThreadId |
The ids, for a report that has to say which process or thread wrote it |