Hallo!
In neueren Delphi-Versionen sind Records mehr als nur
strukturierte Typen. Man kann sie zur
Unterstützung einfacher Typen einsetzen oder ihnen mittels
überladener Operatoren neue Fähigkeiten beibringen, die man z.B. bei Vergleichen einsetzen kann.
Ich habe eine kleine Demo-Anwendung erstellt, welche den einfachen Vergleich von Versionsnummern demonstriert. Oftmals liegen diese nicht in verwertbarer numerischer Form vor sondern als Strings wie
1.0 oder
25.0.29899.2631.
Will man zwei Versionsnummern vergleichen, muss man den String in vier numerische Werte zerlegen und diese einzeln vergleichen. Dass dies nicht ganz so trivial ist wie es auf den ersten Blick scheint, zeigt so manche
Diskussion hier in der
DP.
Wie schön wäre es, wenn man Versionsnummern direkt mit einander vergleichen könnte. Das klappt zwar auf den ersten Blick erstaunlich gut:
Delphi-Quellcode:
begin
if '1.0' > '0.1' then ShowMessage('1.0 ist größer als 0.1');
end;
Aber wie so oft steckt der Teufel hier im Detail. Denn schon eine geringfügig andere Schreibweise der an sich identischen Information lässt diesen Vergleich fehlschlagen:
Delphi-Quellcode:
begin
if '1.0.0' > '1.0' then ShowMessage('1.0.0 ist größer als 1.0');
end;
Denn "1.0.0" ist nun mal
gleich "1.0", aber nicht
größer. Dennoch erscheint hier die Message. Der Vergleich ergibt ein False-Positive.
Also müsste man Delphi beibringen, die beiden Strings anders zu vergleichen. Genau das erreiche ich im beigefügten Beispiel durch
überladene Operatoren eines Records. Durch einen simplen Typecast lassen sich die Versions-Strings dann korrekt vergleichen:
Delphi-Quellcode:
begin
if TVersion('1.0.0') > TVersion('1.0') then ShowMessage('1.0.0 ist größer als 1.0');
end;
Folgende Operatoren habe ich in dem vorliegenden Beispiel implementiert:
Code:
= (Equal)
> (Greater)
>= (GreaterOrEqual)
< (Less)
<= (LessOrEqual)
<> (NotEqual)
:= (Implicit)
Während die ersten sechs allesamt Vergleichsoperatoren sind, ist
Implicit ein Zuweisungsoperator. Dadurch wird es möglich, einer TVersion-Variablen direkt einen String zuzuweisen:
Delphi-Quellcode:
var
LString: string;
LVersion: TVersion;
begin
LString := '1.0';
LVersion := LString;
end;
Ohne einen
Implicit-Operator würde dies mit einem Compilerfehler "Inkompatible Typen" fehlschlagen. Der Einfachheit halber erledigt
Implicit auch gleich das Zerlegen in die vier numerischen Werte, welche über die Record-Properties MajorMS, MajorLS, MinorMS und MinorLS zugreifbar sind.
Ein überladener Vergleichsoperator hat immer zwei Parameter: Die linke und die rechte Seite, meist als "A" und "B" benannt:
class operator Equal(A: TVersion; B: TVersion): Boolean;
Weil wir im vorliegenden Fall aber auch direkt mit Strings vergleichen wollen, muss jeder überladene Vergleichsoperator
drei Mal implementiert werden:
Delphi-Quellcode:
class operator Equal(A: TVersion; B: TVersion): Boolean;
class operator Equal(A: TVersion; B: string): Boolean;
class operator Equal(A: string; B: TVersion): Boolean;
Zu guter Letzt gibt es noch einen weiteren Zuweisungsoperator namens
Explicit, welcher immer dann greift, wenn man wie oben gezeigt mit Typecasts arbeitet:
Delphi-Quellcode:
begin
if TVersion('1.0.0') > TVersion('1.0') then ShowMessage('1.0.0 ist größer als 1.0');
end;
Im Beispiel ist jeder überladene Zuweisungsoperator zweimal implementiert:
Delphi-Quellcode:
class operator Implicit(AString: string): TVersion;
class operator Implicit(AVersion: TVersion): string;
Das hat den einfachen Grund, dass die Zuweisung auch in beiden Richtungen funktioniert:
Delphi-Quellcode:
var
LString: string;
LVersion: TVersion;
begin
LString := '1.0';
LVersion := LString;
LString := LVersion;
end;
oder man (vereinfacht ausgedrückt) Strings und TVersions synonym verwenden kann:
Delphi-Quellcode:
var
LVersion: TVersion;
begin
LVersion := '1.0';
if LVersion = '1.0.0' then ShowMessage('LVersion ist gleich 1.0.0');
end;
Für den Fall dass man auf der einen Seite numerische Werte vorliegen hat und auf der anderen Seite einen String, lassen sich die Properties MajorMS, MajorLS, MinorMS und MinorLS auch schreibend verwenden:
Delphi-Quellcode:
var
LVersion: TVersion;
begin
LVersion.MajorMS := 4;
LVersion.MajorLS := 60;
LVersion.MinorMS := 128;
LVersion.MinorLS := 5;
if LVersion > '4.21.9.2' then begin
ShowMessage(Format('%s ist größer als 4.21.9.2', [LVersion.ToString]));
end;
end;
Doch Moment! Warum verwende ich für Format() hier die Methode LVersion.
ToString, wenn TVersion und String zuweisungskompatibel sind? Das liegt an der Implementierung von Format(): Der zweite Parameter ist ein
array of const
, also eine Liste untypisierter Variablen. Darum weiß der Compiler hier schlicht nicht, dass wir TVersion als String haben möchten. Deshalb greift der überladene Operator
Implicit nicht und der Compiler würde sich über inkompatible Typen beschweren.
Die Methode ToString hat einen optionalen Parameter Elements, welcher angibt bis zu welcher Stelle wir die Versionsnummer zum String umwandeln möchten:
Delphi-Quellcode:
var
LVersion: TVersion;
begin
LVersion.MajorMS := 4;
LVersion.MajorLS := 60;
LVersion.MinorMS := 128;
LVersion.MinorLS := 5;
ShowMessage(LVersion.ToString(2));
end;
ergibt die Anzeige "4.60". Die Zuweisung der numerischen Elemente verhält sich dabei "intelligent":
Delphi-Quellcode:
var
LVersion: TVersion;
begin
LVersion.MajorMS := 4;
LVersion.MinorMS := 128;
ShowMessage(LVersion.ToString);
end;
ergibt die Anzeige "4.0.128". TVersion erkennt also, dass wir hier nur das erste und dritte Element zugewiesen haben und ergänzt selbstständig die "0" für das zweite Element.
Was soll eigentlich passieren, wenn man einen String zuweist, welcher keine gültige Versionsnummer enthält? Nun, das hängt von der Implementierung des Zuweisungsoperators
Implicit ab. Im Beispiel löse ich der Einfachheit halber einfach eine
EConvertError-
Exception aus:
Delphi-Quellcode:
begin
try
if TVersion('1.0') > '1.0 RC1' then begin
end;
except
on EConvertError do begin
ShowMessage('Da ging etwas schief');
end;
end;
end;
Abschließend stellt sich noch die Frage, warum ich das Ganze so kompliziert löse. Es gibt den relativ einfachen Ansatz, die
Versionsnummer in einen Int64 zu konvertieren und dann rein numerisch zu vergleichen. Das funktioniert allerdings nur so lange, als dass keiner der Einzelwerte in den Elementen größer als 65535 ist. Das entspricht High(Word) oder 16 Bit. Vier Words mit je 16 Bit ergäben einen Int64 mit 64 Bit. Kommen allerdings größere Elementwerte vor, funktioniert diese Lösung nicht mehr.
That's it! Zu Risiken und Nebenwirkungen fragen Sie Ihren Delphi-Compiler oder stecken den Kopf in den Sand