Aber um mal eine kurze Erklärung für Generics zu geben, Generic erlauben es Typen und Funktionen zu erstellen die im nachhinein angepasst werden können. So als würde man den Code schreiben aber geziehlt lücken lassen die dann zu einem späteren Zeitpunkt ausgefüllt werden können.
Man erstellt also so zu sagen eine Blaupause von einem Typen oder einer Funktion die dann auf verschiedene Fälle angewendet werden kann. Im Grunde ist also die Idee, wenn man sehr oft code doppelt schreibt, indem sich nur ein paar sachen ändern schreibt man lieber ein Generic und lässt die verschiedenen sachen dann als Parameter übergeben.
Zum beispiel die Unit Math hat die Folgenden funktionen:
Code: Alles auswählen
function Max(a, b: Integer): Integer;inline; overload;
function Max(a, b: Cardinal): Cardinal; overload;
function Max(a, b: Int64): Int64;inline; overload;
function Max(a, b: QWord): QWord;inline; overload;
function Max(a, b: Single): Single;inline; overload;
function Max(a, b: Double): Double;inline; overload;
function Max(a, b: Extended): Extended;inline; overload;
7 Funktionen die alle exakt das gleiche machen, und sich nur im typen unterscheiden. Mit generics kann man das als eine funktion machen
Code: Alles auswählen
generic function max<T>(const A, B: T): T;
begin
if A > B then Result := A
else Result := B;
end;
Das deckt alle 7 funktionen von oben ab, und noch dazu erlaubt die erweiterung auf alle weiteren Typen die den > operator unterstützen. Z.B:
Code: Alles auswählen
var
a, b, c: MPInteger; // Multi Precision Integer aus der GMP unit;
begin
...
c := max(a, b);
end;
Das heist das Generics sich vor allem eignen in Bibliotheken die viel wiederverwendeten code haben.
Generische Parameter können aber nicht nur für Typen von Parametern verwendet werden, z.B. können diese auch direkt referenziert werden:
Code: Alles auswählen
type
generic TLess<T> = class
class function Compare(const A, B: T): Boolean; inline; static; // Gibt A < B zurück
end;
generic TGreater<T> = class
class function Compare(const A, B: T): Boolean; inline; static; // Gibt A < B zurück
end;
generic procedure Swap<T>(var A, B: T); inline
var
tmp: T;
begin
tmp := A;
A := B;
B := tmp;
end;
generic function Sort<T, TCompare>(Data: specialize TArray<T>): specialize TArray<T>;
var i, j: Integer;
begin
Result := Data;
SetLength(Result, Length(Data)); // in Result kopieren
// Bubblesort weil faul:
for i:=0 to Length(Result) -1 do
for j:=0 to Length(Result) - 2 - i do
if TCompare.Compare(Result[j+1], Result[j]) then
Swap(Result[j], Result[j+1]);
end;
// benutzung:
Sort<Integer, specialize TLess<Integer>(data); // Aufsteigend sortieren
Sort<Integer, specialize TGreater<Integer>(data); // Absteigend sortieren
Als kurze erklärung, TCompare wird hier nicht als Typ für irgendeine Variable verwendet, sondern lediglich als funktion. Soweit ich weis unterstützt der FPC keine direkten Funktionen als Generische Parameter, deshalb muss der Umweg über den typen mit der class function gemacht werden. Mit diesen funktionen kann man jeden Typen der < und > implementiert sortieren.
In bestehendem code wie z.B. TStringLists sortierung, wird das über Events also Funktionspointer geregelt. Allerdings bedeutet das das das zur Laufzeit geregelt werden muss. Das heist zum einen das das in dem Funktionspointer Falsche daten Drin stehen können (z.B. Nil) dann krachts, was bei Generics der Compiler nicht erlaubt, zum anderen kann der Compiler hier optimieren, sodass am ende da wirklich nix anderes als < oder > steht.
Da allerdings alles zur Compiletime passiert, sind keinerlei Runtime informationen vorhanden. D.h. auch das die Typen nicht direkt verwand sind. So ist z.B. TList<Integer> und TList<Float> 2 verschiedene typen, und man kann nicht z.b. das eine in das andere Casten oder ähnliches.
Ein paar Beispiele, wie z.b. ich in der letzten Zeit Generics verwendet habe:
Dynamische Typen, z.B. ein Union typ:
https://github.com/Warfley/ObjPasUtils/ ... namictypes
Code: Alles auswählen
var
union: TUnion<String, Integer>;
begin
union := EmptyUnion; // Empty
union := 'Hello World'; // Set the value to being a string
union := 42; // Set the value to being an integer
...
if union.isFirst then
WriteLn('Union is String: ', Union.First);
if union.isSecond then
WriteLn('Union is Integer: ', Union.Second);
Oder woran ich aktuell arbeite, Iteratoren Bibliothek:
https://github.com/Warfley/ObjPasUtils/ ... /iterators
Z.B. um einen Byte Array als Hex String darzustellen
Code: Alles auswählen
HexStr := Reduce<String>(Map<Byte, String>(Iterate<Byte>(arr), ByteToHex), ConcatStr);
Was ich hoffe was wenn die bugs mit der Implicit specialization gefixed sind, am ende so aussieht:
Code: Alles auswählen
HexStr := Reduce(Map(Iterate(arr), ByteToHex), ConcatStr);
Oder bei meiner STAX bibliothek:
https://github.com/Warfley/STAX
Gibt es Tasks die rückgabewerte haben können, das ist in dieser form eigentlich nur wirklich mit Generics möglich, und somit z.B. ermöglicht einen Funktionsaufruf im hintergrund zu starten und wenn die Funktion fertig ist aufgeweckt zu werden und das ergebnis zurückzubekommen:
Code: Alles auswählen
function AsyncConcat(AExecutor: TExecutor; AName: String; ANumber: Integer): String;
...
Line := Await<String>(AsyncFunction<String, String, Integer>(AsyncConcat, AName, i));
Was ich hoffe was mit implicit specialization irgendwann nur noch so aussieht:
Code: Alles auswählen
function AsyncConcat(AExecutor: TExecutor; AName: String; ANumber: Integer): String;
...
Line := Await(AsyncFunction(AsyncConcat, AName, i));
Aber auch als parameter sind Generics sehr nützlich, z.B. habe ich eine Pfadbibliothek gebaut:
https://github.com/Warfley/ObjPasUtils/ ... rc/pathlib
Und da Pfade ja auf verschiedenen betriebsystemen unterschiedlich sind, werden die über Generics Parametrisiert:
Code: Alles auswählen
TWindowsPathParams = record
const PathDelim = '\';
const CaseSensitive: Boolean = False;
class function isAbsolute(const Path: String): Boolean; static; inline;
class function isRoot(const Path: String): Boolean; static; inline;
end;
TUnixPathParams = record
const PathDelim = '/';
const CaseSensitive: Boolean = True;
class function isAbsolute(const Path: String): Boolean; static; inline;
class function isRoot(const Path: String): Boolean; static; inline;
end;
TWindowsPath = specialize TCustomPath<TWindowsPathParams>;
TUnixPath = specialize TCustomPath<TUnixPathParams>;
{$IfDef WINDOWS}
TPath = TWindowsPath;
{$Else}
TPath = TUnixPath;
{$EndIf}