Kennisbank

Specific typing in Delphi

Primitieve types hebben standaard conversieregels; veel programmeertalen werken standaard zo. Dit zorgt ervoor dat je niet alles hoeft te casten en is puur voor het gemak. Je kunt bijvoorbeeld een Integer probleemloos aan een Extended toewijzen, zowel door een letterlijke 42 mee te geven als door een variabele van het type Integer toe te wijzen.

procedure Hello;
var
  SomeNumber: Extended;
  AnotherNumber: Integer;
begin
  AnotherNumber := 6;
  SomeNumber := 2; // works
  SomeNumber := AnotherNumber; // works
end;

Zelfs als je type-aliasing toepast, blijven de types dezelfde types als voorheen en gelden er automatische conversieregels:

type
  ANewType = Extended;
  AnotherNewType = Integer;

implementation

procedure Hello;
var
  SomeNumber: ANewType;
  AnotherNumber: AnotherNewType;
begin
  AnotherNumber := 6;
  SomeNumber := 2; // works
  SomeNumber := AnotherNumber; // works
end;

Wil je echt strikte conversieregels, bijvoorbeeld omdat je wilt voorkomen dat meters aan seconden worden toegekend of andersom, dan moet je nieuwe types aanmaken. En nieuwe types maak je met behulp van een record of een class.

Om bepaalde conversies toch toe te staan, kun je die regels aan de types toevoegen met operator overloading (zie Embarcadero Wiki).
In FreePascal is de syntaxis anders, maar je kunt hetzelfde bereiken (zie FreePascal Reference).

Je kunt vervolgens zelfs conversieregels maken voor meters * seconden, waarmee je een meter-per-seconde-type creëert.
Hier is een voorbeeld van hoe dat werkt in Delphi:

type
  TMetersPerSecond = record
  private
    m: Extended;
    s: Extended;
 
    value: extended;
 
    mset: Boolean;
    sset: Boolean;
 
    function Units: string;
  public
    function ToString: string;
  end;
 
  TMeters = record
  private
    value: Extended;
 
    function Units: string;
  public
    class operator Implicit(Value: Extended): TMeters ;
    class operator Explicit(Value: TMeters ): TMetersPerSecond ;
    class operator Multiply(ms: TMetersPerSecond; m: TMeters ): TMetersPerSecond;
 
    function ToString: string;
  end;
 
  TSeconds = record
  private
    value: Extended;
 
    function Units: string;
  public
    class operator Implicit(Value: Extended): TSeconds;
    class operator Explicit(Value: TSeconds): TMetersPerSecond;
    class operator Multiply(ms: TMetersPerSecond; s: TSeconds): TMetersPerSecond;
 
    function ToString: string;
  end;
 
implementation
 
uses
  SysUtils;
 
class operator TMeters.Implicit(Value: Extended): TMeters;
begin
  Result.value := Value;
end;
 
class operator TSeconds.Implicit(Value: Extended): TSeconds;
begin
  Result.value := Value;
end;
 
class operator TMeters.Explicit(Value: TMeters): TMetersPerSecond;
begin
  Result.m := Value.value;
  Result.mset := True;
  if Result.sset then
    Result.value := Result.s * Result.m; 
end;
 
class operator TSeconds.Explicit(Value: TSeconds): TMetersPerSecond;
begin
  Result.s := Value.value;
  Result.sset := True;
  if Result.mset then
    Result.value := Result.s * Result.m; 
end;
 
class operator TSeconds.Multiply(ms: TMetersPerSecond; s: TSeconds): TMetersPerSecond;
begin
  Result := ms;
  Result.s := s.value;
  Result.sset := True;
  if Result.mset then
    Result.value := Result.s * Result.m; 
end;
 
class operator TMeters.Multiply(ms: TMetersPerSecond; m: meters): TMetersPerSecond;
begin
  Result := ms;
  Result.m := m.value;
  Result.mset := True;
  if Result.sset then
    Result.value := Result.s * Result.m; 
end;
 
function TMetersPerSecond.Units: string;
begin
  Result := 'm/s';
end;
 
function TMetersPerSecond.ToString: string;
begin
  Assert(mset and sset);
 
  Result := Format('%f%s', [value, Units])
end;
 
function TMeters.Units: string;
begin
  Result := 'm';
end;
 
function TMeters.ToString: string;
begin
  Result := Format('%f%s', [value, Units]);
end;
 
function TSeconds.Units: string;
begin
  Result := 's';
end;
 
function TSeconds.ToString: string;
begin
  Result := Format('%f%s', [value, Units]);
end;
 
var
  time: TSeconds;
  distance: TMeters;
  speed: TMetersPerSecond;
  different_speed: TMetersPerSecond;
initialization
  time := 10.0;
  distance := 3;
 
  // distance := time; // does not work, because seconds are not meters!
 
  speed := TMetersPerSecond(time) * distance;
 
  // or the other way around
  speed := TMetersPerSecond(distance) * time;
 
  WriteLn('speed -> ' + time.ToString + ' * ' + distance.ToString + ' = ' + Speed.ToString);
 
  // different_speed := distance; // this doesn't work without explicit conversion
 
  WriteLn('different_speed -> ' + different_speed.ToString);
end.

Geschreven door Patrick Quist
Delphi ontwikkelaar

Contact

Laat ons helpen jouw ambities concreet te maken.