AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Tutorials Arbeiten mit überladenen Operatoren

Arbeiten mit überladenen Operatoren

Ein Tutorial von Codehunter · begonnen am 7. Nov 2019 · letzter Beitrag vom 8. Nov 2019
Antwort Antwort
Benutzerbild von Codehunter
Codehunter
Registriert seit: 3. Jun 2003
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.1then 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.0then 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.0then 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.2then 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 RC1then 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
Miniaturansicht angehängter Grafiken
easy-version-compare.png  
Angehängte Dateien
Dateityp: zip VersionComparer.zip (3,8 KB, 13x aufgerufen)
Ich mache grundsätzlich keine Screenshots. Schießen auf Bildschirme gibt nämlich hässliche Pixelfehler und schadet der Gesundheit vom Kollegen gegenüber. I und E zu vertauschen hätte den selben negativen Effekt, würde aber eher dem Betriebsklima schaden

Geändert von Codehunter ( 7. Nov 2019 um 11:52 Uhr)
 
Der schöne Günther

 
Delphi 10 Seattle Enterprise
 
#2
  Alt 7. Nov 2019, 12:03
Schönes Beispiel mit Versionsnummern. Vektorrechnung wäre auch ein Kandidat gewesen.

Ein Link auf die Doku würde ich vielleicht noch einmal etwas prominenter am Schluss zum Nachschlagen:
http://docwiki.embarcadero.com/RADSt...toren_(Delphi)

(Ja, ich weiß dass der auch oben im Fließtext vorkommt)
  Mit Zitat antworten Zitat
Benutzerbild von Codehunter
Codehunter

 
Delphi 10.2 Tokyo Professional
 
#3
  Alt 7. Nov 2019, 12:38
Ich war Anfangs noch auf dem Dreh dass Int64 für Versionsnummern ausreichen müsste und habe mit vier Words und einem varianten Record experimentiert. Aber da mochte der Compiler nicht so recht mitspielen was überladene Operatoren im selben Record betraf. Außerdem hatte ich schon nach nur einem Tag einen Fall, wo das dritte Element deutlich größer als 65535 war. Weil ich nun nicht unbedingt darauf warten wollte dass AMD oder Intel eine 128-Bit-CPU bringt und, noch viel länger, dass von Emba ein entsprechender Compiler kommt, habe ich das hier geschrieben. Und dann dachte ich, wäre eigentlich ein schönes Tuto bzgl. überladener Operatoren.

Nun müsste mir nur noch einer der Delphi-Auguren erklären, weshalb überladene Operatoren ohne das Schlüsselwort overload auskommen

EDIT:
Schönes Beispiel mit Versionsnummern. Vektorrechnung wäre auch ein Kandidat gewesen.
Nur zu! Vielleicht kann ich da ja noch was lernen

Geändert von Codehunter ( 7. Nov 2019 um 12:40 Uhr)
  Mit Zitat antworten Zitat
Der schöne Günther

 
Delphi 10 Seattle Enterprise
 
#4
  Alt 7. Nov 2019, 12:40
Oder auch weshalb die Argumente const sein können, oder auch nicht. Und es trotzdem passt.
  Mit Zitat antworten Zitat
Benutzerbild von Codehunter
Codehunter

 
Delphi 10.2 Tokyo Professional
 
#5
  Alt 7. Nov 2019, 12:42
Oder auch weshalb die Argumente const sein können, oder auch nicht. Und es trotzdem passt.
IMHO war es Marco Cantu, der solche wundersamen Dinge als Compiler Magic definierte
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe
Online

 
Delphi 10.3 Rio
 
#6
  Alt 7. Nov 2019, 13:08
Nun müsste mir nur noch einer der Delphi-Auguren erklären, weshalb überladene Operatoren ohne das Schlüsselwort overload auskommen
Operatoren sind von Natur aus overload. Sie sind ja nichts anderes als Implementierungen für Konstrukte wie A + B , bei denen entweder A oder B vom Record-Typ und der jeweils andere ein beliebiger Typ sein kann. Dann wird vom Compiler nach einer ADD-Operator-Implementierung für eine solche Konstellation gesucht.

So gesehen enthält das Schlüsselwort operator implizit schon das overload.
Uwe Raabe
  Mit Zitat antworten Zitat
Benutzerbild von Codehunter
Codehunter

 
Delphi 10.2 Tokyo Professional
 
#7
  Alt 7. Nov 2019, 13:41
Sagen wir mal, es wirkt einfach in sich nicht schlüssig. Denn bei normalen Methoden gleichen Namens pocht der Compiler auf das Schlüsselwort overload. Allerdings scheint es ihn auch nicht sonderlich zu stören, wenn man es bei den class operators mit angibt.

Was mich gerade viel mehr stört: In einem meiner Projekte eingebunden bekomme ich die Compilerwarnung "Sprach-Feature nicht unterstützt: Explicit". Aber auch nur bei diesem Projekt. Ist dieses Feature von irgendwelchen Projekteinstellungen oder Compilerschaltern abhängig?
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe
Online

 
Delphi 10.3 Rio
 
#8
  Alt 7. Nov 2019, 14:18
Deaktiviere mal in den Projekt-Optionen die Erzeugung der C++ Header-Dateien.
Uwe Raabe
  Mit Zitat antworten Zitat
Benutzerbild von Codehunter
Codehunter

 
Delphi 10.2 Tokyo Professional
 
#9
  Alt 8. Nov 2019, 07:14
EDIT: Danke, hat sich erledigt. Zwar war das Projekt auf "Nur DCUs erzeugen" eingestellt, aber es gab noch ein Package das auf alles erzeugen eingestellt war.

Geändert von Codehunter ( 8. Nov 2019 um 08:37 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von Stevie
Stevie

 
Delphi 10.1 Berlin Enterprise
 
#10
  Alt 8. Nov 2019, 10:43
Evtl auch interessant: https://github.com/VSoftTechnologies...emanticVersion
Stefan
  Mit Zitat antworten Zitat
Themen-Optionen Tutorial durchsuchen
Tutorial durchsuchen:

Erweiterte Suche
Ansicht

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 23:54 Uhr.
Powered by vBulletin® Copyright ©2000 - 2019, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2019 by Daniel R. Wolf