Saltar al contenido principal

# Commons — Guía de uso

🌍 Idioma: English | Español


Tabla de contenidos

  1. Daf.MemUtils — Punteros inteligentes
  2. Daf.Threading — Tokens de cancelación y apagado
  3. Daf.Enumerable
  4. Daf.Rtti
  5. Daf.Activator
  6. Daf.Arrays
  7. Daf.Nullable
  8. Daf.Comparers
  9. Daf.Application.Version
  10. Daf.Types

Daf.SystemProcess vive en un paquete propio —ver SystemProcess—. Daf.CmdLn.Parser se retiró en 2.0; la línea de comandos es Daf.CommandLine.


Daf.MemUtils — Punteros inteligentes

ARC<T>

ARC<T> es un «smart pointer» implementado como reference to function: T. Al invocarlo como función obtienes el objeto; cuando todas las referencias desaparecen del scope el objeto se libera automáticamente.

uses Daf.MemUtils;

// Crear desde una instancia existente
var Doc := ARC.From<TDocument>(TDocument.Create);

// Crear + instanciar automáticamente (requiere constructor sin argumentos)
var Doc2 := ARC.From<TDocument>;

// Acceder al objeto — se invoca como función
Doc().Load('informe.pdf');
WriteLn(Doc().Title);

// Pasar entre variables — el refcount aumenta; se libera al salir del scope
var Copia := Doc;

Reglas clave

  • ARC<T> es un reference to function: T (cierre de interfaz), no un record.
  • Se accede al objeto invocando la variable como función: MyArc(), no .Value.
  • ARC.From<T>(nil) devuelve nil.

Daf.Threading — Tokens de cancelación y apagado

ICancellationToken / ICancellationTokenSource

Usar las funciones de fábrica para crear un origen de cancelación — TCancellationTokenSource es una clase de implementación interna y no se instancia directamente.

uses Daf.Threading;

// Crear mediante funciones de fábrica
var Cts := CreateCancellationTokenSource;           // estándar
// var Cts := CreateCancellationTokenSourceWithTimeout(5000); // auto-cancela tras 5 s
// var Cts := CreateCanceledCancellationTokenSource;          // ya cancelado

var Token := Cts.Token;  // ICancellationToken

// En el worker:
while not Token.IsCancellationRequested do
  ProcesarSiguienteElemento;

// Notificar cuando se cancela:
Token.Register(procedure begin WriteLn('Cancelado!'); end);

// Cancelar desde el llamador:
Cts.Cancel;

Nota: IFuture<T> forma parte del RTL de Delphi (System.Threading), no de DAF. Usa TTask.Future<T> (RTL) para cálculos en segundo plano, o TSystemProcess.ExecuteAsync (DAF) para ejecución de procesos externos.

TShutdownHook

Conecta las señales de parada de la consola con la aplicación. El host se suscribe solo al arrancar; el oyente se asigna a mano únicamente en un programa sin host:

uses Daf.Threading;

TShutdownHook.OnShutdownRequested := procedure
begin
  Logger.LogInformation('Apagado solicitado, deteniendo…', []);
  App.Stop;
end;

El manejador de consola está instalado mientras hay un oyente, como hace ConsoleLifetime en .NET:

Evento Con oyente Sin oyente
Ctrl+C, Ctrl+Break; SIGINT, SIGQUIT y SIGTERM fuera de Windows pide la parada, cada vez que llega se deja al sistema, que termina el proceso
Cierre de la ventana de consola, cierre de sesión se deja al sistema se deja al sistema

TShutdownHook.HandleConsoleSignal(Signal) es lo que el manejador hace con cada evento, y responde si lo da por atendido. Lo que lanza el oyente se escribe en la salida de error y no sale del manejador. RequestShutdown pide la parada desde código; sin oyente, la petición espera al siguiente que se suscriba. El apagado de Windows no llega a una aplicación de consola Delphi, que carga user32.dll.


Daf.Enumerable

ToIEnumerable<T>

Record-puente que adapta cualquier TEnumerable<T> (clase) a la interfaz IEnumerable<T>. Los operadores de conversión implícita hacen el uso transparente.

uses Daf.Enumerable;

var Lista := TList<Integer>.Create;
Lista.Add(1); Lista.Add(2); Lista.Add(3);

// Conversión implícita a IEnumerable<T>
var Seq: IEnumerable<Integer> := ToIEnumerable<Integer>(Lista);
for var Item in Seq do
  Procesar(Item);

// Convertir a array plano
var Arr := ToIEnumerable<Integer>(Lista).ToArray;

TDeferredEnumerable<T>

IEnumerable<T> construido a partir de un closure que devuelve el enumerador — la primitiva de secuencia diferida. No se ejecuta nada hasta que se llama a GetEnumerator, y cada iteración vuelve a invocar la factoría, así que la misma secuencia se puede recorrer más de una vez.

uses Daf.Enumerable;

var Llamadas := 0;
var Seq: IEnumerable<Integer> := TDeferredEnumerable<Integer>.Create(
  function: IEnumerator<Integer>
  var
    N: Integer;
  begin
    Inc(Llamadas);
    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);

// Llamadas sigue en 0: declarar la secuencia no enumeró nada

for var I in Seq do    // Llamadas = 1, entrega 1, 2, 3
  Procesar(I);

for var I in Seq do    // Llamadas = 2 — un enumerador nuevo, recorrido desde el principio
  Procesar(I);

Es la primitiva sobre la que está construido Daf.Linq: Where, Select, Take y los demás devuelven uno de estos envolviendo la secuencia anterior, y eso es lo que hace que una cadena de operadores recorra la fuente una sola vez, y solo cuando un terminal pide un elemento. Ver Daf.Linq.

TGeneratorEnumerator<T>

El enumerador de la pareja anterior, construido con un closure de avanzar y entregar: un TGeneratorFunc<T> es una function(var Current: T): Boolean que devuelve False cuando la secuencia se agotó y, si no, escribe el elemento siguiente en Current. El estado del recorrido vive en el closure, así que una secuencia sin colección detrás — un contador, un fichero que se lee, una serie calculada — no necesita una clase enumeradora propia.

Reset lanza ENotImplemented: un closure que ya avanzó no se puede rebobinar. Recorrer una secuencia por segunda vez es para lo que está TDeferredEnumerable<T> — le pide a su factoría un enumerador nuevo, con su propio estado.

IInterfaceList<T>

La TInterfaceList de la RTL con un GetEnumerator tipado encima, que es todo lo que añade: la variable del bucle es una T y no una IInterface.

var Lista := TInterfaceList<IMyService>.Create;
Lista.Add(ServicioA);
Lista.Add(ServicioB);

for var Svc in Lista do         // Svc es IMyService
  Svc.Execute;

IsEmpty, TryFind, First, Last e ItemType están en la TInterfaceList no genérica que esta unit también declara, no en la genérica:

var Cualquiera := TInterfaceList.Create;
Cualquiera.Add(ServicioA);

var Encontrado: IMyService;
if not Cualquiera.IsEmpty and Cualquiera.TryFind(
    function(I: IInterface): Boolean
    begin Result := Supports(I, IMyService); end,
    Encontrado) then
  Encontrado.Execute;

Daf.Rtti

Helpers para el Extended RTTI de Delphi, centralizados en la clase utilitaria _T.

uses Daf.Rtti;

// Nombres de tipo y GUIDs
var Nombre := _T.NameOf<IMyService>;          // 'IMyService'
var Guid   := _T.GUID<IMyService>;            // TGUID
var Unidad := _T.UnitNameOf<IMyService>;      // 'MiUnidad'

// Comprobar jerarquía de interfaces (sobrecargas con PTypeInfo)
if _T.Extends(TypeInfo(IServicioHijo), TypeInfo(IMyService)) then
  WriteLn('IServicioHijo extiende IMyService');

// Comprobar si una clase tiene una propiedad
if _T.HasProperty<TMiClase>('Nombre') then
  WriteLn('La propiedad Nombre existe');

// Crear valor por defecto (cero) para cualquier tipo
var Cero := _T.Default<Integer>;             // 0

TRttiTypeHelper / TRttiPackageHelper

var RC := TRttiContext.Create;
var T  := RC.GetType(TypeInfo(TMiClase));

if T.IsCollection then WriteLn('Es una colección');
if T.Implements<IMyService> then WriteLn('Implementa IMyService');

// Descubrir todas las implementaciones de una interfaz en un paquete
RC.FindType('MiUnidad.TMiClase').AsInstance.DeclaringUnitPackage
  .DiscoverImpl<IMyService>(False, nil,
    procedure(Intf: TRttiInterfaceType; Impl: TRttiInstanceType)
    begin WriteLn(Impl.Name); end);

Daf.Activator

Crea instancias de clases por referencia de clase o typeinfo mediante RTTI.

uses Daf.Activator;

var Instancia := TActivator.CreateInstance(TMyServiceClass) as IMyService;

Daf.Arrays

TArrayHelper

Class helper para el TArray estándar — añade Map (proyección) y Trim (elimina espacios / cadenas vacías).

uses Daf.Arrays;

// Map: proyectar [string] -> [Integer]
var Longitudes := TArray.Map<string, Integer>(Nombres,
  function(S: string): Integer begin Result := S.Length; end);

// Trim: eliminar espacios y elementos vacíos de un array de strings
var Limpio := TArray.Trim(['  hola  ', '', '  mundo  ']);
// Limpio = ['hola', 'mundo']

TSmartArray<T>

Wrapper de tipo valor para TArray<T> con API rica estilo LINQ. Soporta operadores in, +, =, conversión implícita a/desde TArray<T>, y conversión implícita desde string CSV.

var Items: TSmartArray<string>;
Items.Concat('alfa');
Items.Concat('beta');
Items.Concat('gamma');

// Membresía
if 'beta' in Items then WriteLn('Encontrado');

// Filtrar
var Largos := Items.Where(
  function(S: string): Boolean begin Result := Length(S) > 4; end);

// Proyectar
var Mayus := Items.Select<string>(
  function(S: string): string begin Result := S.ToUpper; end);

// Concatenación mediante operador +
var Más: TSmartArray<string> := Items + ['delta', 'epsilon'];

// Ordenar
Items.Sort;

Where / Any toman un TFilterSpec<T> y Select un TFunc<T, TTarget> — los mismos tipos que toman los operadores perezosos de Daf.Linq, así que un filtro o un mapper escrito una vez sirve para las dos APIs. Los closures en línea convierten implícitamente, como arriba; construye el valor por separado cuando quieras reutilizarlo o componerlo:

uses Daf.FilterSpec;

var Largos: TFilterSpec<string> := TPredicate<string>(
  function(S: string): Boolean begin Result := Length(S) > 4; end);
var Gritos: TFilterSpec<string> := TPredicate<string>(
  function(S: string): Boolean begin Result := S = S.ToUpper; end);

Items.Where(Largos and not Gritos);      // compuesto con and / or / not

Daf.Nullable

TNullable<T>

Un valor opcional: o un valor ausente —el NULL de SQL— o un valor presente de T. Lo que aporta es la distinción entre ningún valor y un valor por defecto presente: 0, '' y False son valores, y un T a solas no puede decir cuál de las dos cosas es.

uses Daf.Nullable;

var Edad: TNullable<Integer> := TNullable<Integer>.Null;
Edad.IsNull;                     // True
Edad.GetValueOrDefault(-1);      // -1

Edad := 42;                      // conversión implícita desde T
Edad.HasValue;                   // True
Edad.Value;                      // 42

var Plano: Integer := Edad;       // conversión implícita de vuelta a T
Miembro Responde
Null Un nullable explícitamente ausente
From(Value) Envuelve un valor presente; la conversión implícita desde T hace lo mismo
IsNull True cuando no hay valor presente
HasValue True cuando sí lo hay. HasValue = not IsNull
GetValueOrDefault(Fallback) El valor si está presente y, si no, el que se le pase
Value El valor presente. Cuando es NULL lanza EInvalidOpException, y también la conversión implícita a T

Los dos predicados de presencia se publican a propósito. IsNull es el idioma del dominio de datos, el que casa con el TField.IsNull de Delphi y con el IS NULL de SQLCute; HasValue es el de .NET. El correcto es el que se lea mejor donde se escribe.

Un TNullable<T> recién declarado arranca ausente: Initialize lo deja así, y es un operador de record gestionado, así que un campo de una clase o un elemento de un array arrancan ausentes también sin que nadie los inicialice.

Daf.Comparers

Comparación consciente de NULL, en dos políticas coordinadas: el orden pone NULL por debajo de todo valor no NULL —nulls primero ascendiendo, nulls al final descendiendo— y dos NULL comparan iguales; la igualdad hace iguales los NULL, que es lo que colapsa los NULL duplicados bajo DISTINCT.

uses Daf.Comparers;

// Sobre TValue — la moneda de valor del motor de consultas en memoria
var Orden := TValueComparers.Ordinal;
var Igual := TValueComparers.Equality;

// Sobre TNullable<T>
var PorEdad := TNullableComparers.Ordinal<Integer>;
Punto de entrada Responde
TValueComparers.Ordinal IComparer<TValue>: NULL por debajo de todo valor no NULL, NULL iguales; dos valores de familias distintas lanzan EArgumentException
TValueComparers.Equality IEqualityComparer<TValue>: dos valores de familias distintas nunca son iguales, los NULL sí, y los valores iguales comparten hash
TNullableComparers.Ordinal<T> El mismo orden sobre TNullable<T>
TNullableComparers.Equality<T> Dos valores ausentes son iguales
IsNullValue(V) Si un TValue representa el NULL de SQL: vacío, un Variant con Null/Unassigned, o un TNullable ausente
CompareValuesNullSmallest(L, R) La comparación en sí, sin pasar por un comparador

Todo valor no NULL pertenece a una familia: lógico, número, fecha, texto u otro. Un valor de una familia nunca es igual a uno de otra: el texto '1' no es el número 1, como en .NET. Dos números son iguales cuando valen lo mismo, sea cual sea su tipo: 1 y 1.0 son iguales, como en SQL; en .NET, 1.Equals(1.0) es falso. Ordenar dos valores de familias distintas lanza EArgumentException, como el Comparer<object> de .NET. Dentro de una familia, el texto se ordena por ordinal, False va antes que True, y las fechas y los números, por su valor.

Sobre TValue, el NULL de SQL se modela como un TValue vacío, uno de clase Variant con Null o Unassigned, o un TNullable ausente; un TNullable presente cuenta como el valor que guarda. Es sobre esto que SQLCute.InMemory construye su semántica ANSI de NULL — ver SQLCute.InMemory.


Daf.Application.Version

TVersionInfo / [Version]

Una clase declara su versión con el atributo [Version] —tag SemVer, nombre de aplicación, compañía y, si se quiere, si el binario es una build de depuración— y TVersionInfo.GetFrom la lee de vuelta por 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'
Miembro Qué contesta
Tag la cadena que declaró el atributo, tal cual
VersionTag(Plantilla) la versión formateada con una plantilla de marcadores ${...}; VersionTemplate es la de por defecto y VersionTemplateWithBuild añade los datos de compilación
IsEmpty si se fijó alguna versión
IsSemVer si el tag declarado sigue la gramática SemVer
Major / Minor / Patch / PreRelease / Metadata las piezas del SemVer
ApplicationName / Company los otros dos argumentos del atributo
ArchBits / Platform lo que vio el compilador: 32 o 64, y la plataforma destino
Debug si el binario se declaró build de depuración; False si no declaró nada

Debug lo declara el binario. Solo el código del propio binario ve su configuración de compilación —el framework se compila aparte—, y los argumentos del atributo se evalúan al compilar ese código:

[Version('1.2.3', 'Invoicer', 'Acme', {$IFDEF DEBUG}True{$ELSE}False{$ENDIF})]
TInvoicerApp = class
end;

TVersionInfo.Create recibe el mismo cuarto argumento. La información queda fijada al construirse: Debug no tiene setter.

Una clase sin atributo da un record vacío, no un error: IsEmpty responde True, Tag y los dos nombres vuelven como cadena vacía, las piezas numéricas como cero y VersionTag como cadena vacía.

Un tag que no es SemVer conserva su texto. IsSemVer responde False y VersionTag informa la cadena declarada en vez de rearmarla desde piezas que nunca se llenaron.

Los marcadores que una plantilla puede nombrar:

Marcador Emite
${Major} ${Minor} ${Patch} ${Metadata} ${Tag} las piezas del SemVer y la cadena declarada, crudas
${PreRelease} el pre-release con su propio -, o nada
${Platform} ${ArchBits} la plataforma destino y el tamaño de palabra
${CompiledMeta} los dos juntos —Win32—, seguidos de .Dbg si el binario se declaró build de depuración
${Build} / ${BuildFull} la sección de build, abierta con +. El primero lleva lo que declaró el tag; el segundo lo une con los datos de compilación dentro de esa única sección. Son alternativas: una plantilla nombra uno

SemVer admite una sola sección de build, así que ${BuildFull} separa las dos fuentes con . en lugar de abrir un segundo +.

El atributo se lee por RTTI, así que el enlazador tiene que conservar la clase: un programa que se apoye en esto declara STRONGLINKTYPES y pone la clase en su sección interface.


Daf.Types

TDeferred<T>

Un valor que se da directamente o como thunk, y que se reevalúa en cada lectura. Las conversiones implícitas son lo que hace intercambiables las dos formas en el punto de llamada:

uses Daf.Types;

var Origen := 'antes';

var Fijo: TDeferred<string> := 'literal';
var Vivo: TDeferred<string> := TFunc<string>(
  function: string
  begin
    Result := Origen;
  end);

Origen := 'después';

var A: string := Fijo;   // 'literal'
var B: string := Vivo;   // 'después' — leído ahora, no cuando se registró

No es Lazy<T>. Ese memoriza: ejecuta el thunk una vez y se queda con la respuesta. Este lo ejecuta otra vez en cada lectura, que es lo que necesita la resolución de macros — una macro cuyo valor sigue algo que se mueve.

Ni es el Expression<TDelegate> de .NET. Allí una expresión es un árbol inspeccionable; un TDeferred<T> es un closure y nadie puede mirar dentro. El árbol inspeccionable es IExpr y TSpecification<T>, en Daf.Specification.

TMacroExpression es TDeferred<string> con el nombre que usa la lista de macros.

TMacrosList

Un conjunto de TMacroExpression con nombre, y la sustitución de ${nombre} dentro de una cadena.

var Macros := TMacrosList.Create;
try
  Macros['product'] := 'DAF';
  Macros['edition'] := TFunc<string>(
    function: string begin Result := EdicionActual; end);

  Macros.ResolveMacros('${product} ${edition}');   // 'DAF Community'
finally
  Macros.Free;
end;
Miembro Responde
Value[Name] (la propiedad por defecto) Lee y escribe una macro. Leerla resuelve el thunk
ResolveMacros(Source, MaxRecursionLevel) Sustituye cada ${nombre} de Source. Una macro cuyo valor nombra otra macro se resuelve también, hasta MaxRecursionLevel — 100 por defecto, que es lo que detiene a un par que se nombra mutuamente
Contains(Key) Count Qué hay registrado
Keys[I] ValueFromIndex[I] El orden de registro se conserva: la lista es un diccionario ordenado

Una macro que no está registrada

De fábrica, una referencia a una macro que no está registrada se resuelve como cadena vacía. MissingMacroPolicy cambia eso, y a OnMissingMacro se le pregunta antes:

Macros.MissingMacroPolicy := mmpError;
Macros.ResolveMacros('${root}/charts');
// EMacroNotFound: No macro is registered with the name "root"
Miembro Responde
MissingMacroPolicy mmpReturnDefault, la de por defecto, sustituye MissingMacroValue, que está vacío salvo que se asigne · mmpKeepToken deja ${nombre} en el resultado · mmpError lanza EMacroNotFound
OnMissingMacro Se llama por cada referencia a una macro que no está registrada, antes que la política. Asigna Value y Handled para resolverla, o deja Handled en False para pasársela a la política
MissingMacroError(Name) La excepción que lanza mmpError. Un gancho que tenga que fallar lanza esta: raise TMacrosList(Sender).MissingMacroError(Name)

La política solo se aplica en ResolveMacros: leer Macros['nombre'] de una macro que no está registrada devuelve vacío sea cual sea la política. Contains pregunta si está.

TEnvVars

Las variables de entorno del proceso leídas a un diccionario, y escritas de vuelta como el bloque de entorno que recibe un proceso hijo.

var Env := TEnvVars.Create;
try
  for var Pair in Env.Vars do
    Writeln(Pair.Key, '=', Pair.Value);

  Env.Refresh;             // volver a leer el sistema, por si cambió
finally
  Env.Free;
end;
Miembro Responde
Vars El diccionario recogido al construirse. Refresh vuelve a leer el sistema
Buffer El mismo conjunto como el bloque separado por nulos que espera CreateProcess
WriteTo(Buffer) Escribe ese bloque en un puntero de quien llama — así se le da a un proceso hijo un entorno con entradas añadidas al del padre
ClearBuffer Libera el bloque

Daf.Configuration lee Vars para convertir el entorno en una fuente de configuración, y Daf.SystemProcess usa WriteTo para entregarle a un hijo su propio entorno.

Coalesce

La primera de varias referencias a interfaz que no es nil.

var Elegido := Coalesce.From<ILogger>(Explicito, DelContenedor);
var Primero := Coalesce.From<ILogger>([Explicito, DelContenedor, PorDefecto]);

Los dos argumentos se evalúan antes de la llamada, como en cualquier función: esto elige entre referencias que ya se tienen, y no es un cortocircuito.

Debugger

Debugger.Write('caché cargada con %d entradas', [Count]);
if Debugger.Enabled then
  Debugger.Write(Informe);
Miembro Responde
Write(Text) / Write(FmtStr, Args) Escribe en la salida de depuración del sistema. En Windows es OutputDebugString; fuera de Windows no escribe nada — para un destino que sí funcione allí, ver TPlatformLogBootstrapSink en Daf.FMX
Enabled Si hay un depurador enganchado (System.DebugHook). No si el build es Debug
CurrentProcessId CurrentThreadId Los ids, para un informe que tenga que decir qué proceso o qué hilo lo escribió