Delphi-PRAXiS
Seite 1 von 3  1 23      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Prüfen ob Integer im Enumeration-Type enthalten ist (https://www.delphipraxis.net/183642-pruefen-ob-integer-im-enumeration-type-enthalten-ist.html)

Keks 25. Jan 2015 14:41

Delphi-Version: XE

Prüfen ob Integer im Enumeration-Type enthalten ist
 
Angenommen ich habe folgendes:
Delphi-Quellcode:
type TMeinBeispiel = (mbEins=0, mbZwei=1, mbDrei=5);


Jetzt erhalte ich einen Integer i aus einer externen Quelle (Ini, DB, etc.) und möchte diesen zu TMeinBeispiel casten.
Per
Delphi-Quellcode:
MeinBeispiel := TMeinBeispiel(i);
funktioniert dies immer. Auch dann, wenn i gar nicht in TMeinBeispiel vorhanden ist. Dann hat MeinBeispiel schlicht den Wert i angenommen (der Debugger sagt "out of bound (i)").

Nun würde ich gerne bei Werten, die nicht in der Aufzählung vorhanden sind, einen Standardwert vorgeben.
Wie kann ich nun aber prüfen, ob ein Wert enthalten ist (0, 1, 5: True; 3, 6: False)? Sowas wie
Delphi-Quellcode:
if i in TMeinBeispiel then
funktioniert ja leider nicht.

Muss ich den unschönen Umweg über GetEnumName/GetEnumValue gehen oder gibt es eine elegantere Lösung?

himitsu 25. Jan 2015 15:24

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Für Emums man man maximal in der RTTI abfragen was der größte Wert ist, aber nur, wenn das ein Enum ohne Wertdefinitionen ist, denn dann gibt es in der RTTI keine Namensliste.

Also gerade dein Beispiel ist so nicht und niemals lösbar.
Zitat:

Delphi-Quellcode:
type TMeinBeispiel = (mbEins=0, mbZwei=1, mbDrei=5);

Nur ohne "=", also bei
Delphi-Quellcode:
type TMeinBeispiel = (mbEins, mbZwei, mbDrei);
existiere vollständige RTTI-Infos.

Ansonsten kann man nur den maximalen Wertebereich prüfen und nichts die einzelnen "Werte".
für Enums: 0..255, 0..65535 oder 0..4294967295
für Sets: 0..7, 0..15, 0..31, 0..63, ... bis maximal 0..255



Der Compiler rundet alles auf den nächst größeren kleinstmöglichen Speichertypen, also bei deinem TMeinBeispiel (als Enum) ist das genau ein Byte und somit passt in den Typen grundsätzlich erstmal alles rein, von 0 bis 255 und als Set 0 bis 7.

Zitat:

Muss ich den unschönen Umweg über GetEnumName/GetEnumValue gehen oder gibt es eine elegantere Lösung?
Das hast du vermutlich noch nicht probiert, denn bei deinem TMeinBeispiel ergibt das eine geile Exception. (und mein Lösungsvorschlag, für das Problem, wurde vor vielen Jahren schon ignoriert)

Sir Rufo 25. Jan 2015 15:56

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Gerade wenn die Bedeutung zwischen internem und externem System ausgetauscht werden muss, empfiehlt sich ein eigener DatenTyp um so auch typischer im Kontext zu bleiben.
Delphi-Quellcode:
unit Unit2;

interface

type
  TMeinBeispiel = record
  private const
    VALID_VALUES: array [0 .. 2] of Integer = ( 0, 1, 5 );
    class function GetValue( const Index: Integer ): TMeinBeispiel; static;
  public
    class function Values: TArray<TMeinBeispiel>; static;
    class property Eins: TMeinBeispiel index 0 read GetValue;
    class property Zwei: TMeinBeispiel index 1 read GetValue;
    class property Drei: TMeinBeispiel index 2 read GetValue;
  public
    class operator implicit( const a: Integer ): TMeinBeispiel;
    class operator implicit( const a: TMeinBeispiel ): Integer;

    class operator Equal( const a, b: TMeinBeispiel ): Boolean;
    class operator NotEqual( const a, b: TMeinBeispiel ): Boolean;
    // hier können noch weitere Operatoren definiert werden, je nach Belieben
  private
    FValue: Integer;
  public
    constructor Create( const Value: Integer );
    property Value: Integer read FValue;
  end;

implementation

uses
  System.SysUtils;

{ TMeinBeispiel }

constructor TMeinBeispiel.Create( const Value: Integer );
var
  LIdx: Integer;
begin
  for LIdx := Low( VALID_VALUES ) to High( VALID_VALUES ) do
    if Value = VALID_VALUES[LIdx]
    then
      begin
        FValue := Value;
        Exit;
      end;
  raise EConvertError.CreateFmt( '%d kein gültiger Wert für TMeinBeispiel', [Value] );
end;

class operator TMeinBeispiel.Equal( const a, b: TMeinBeispiel ): Boolean;
begin
  Result := a.FValue = b.FValue;
end;

class function TMeinBeispiel.GetValue( const Index: Integer ): TMeinBeispiel;
begin
  Result := TMeinBeispiel.VALID_VALUES[Index];
end;

class operator TMeinBeispiel.implicit( const a: Integer ): TMeinBeispiel;
begin
  Result := TMeinBeispiel.Create( a );
end;

class operator TMeinBeispiel.implicit( const a: TMeinBeispiel ): Integer;
begin
  Result := a.FValue;
end;

class operator TMeinBeispiel.NotEqual( const a, b: TMeinBeispiel ): Boolean;
begin
  Result := not( a = b );
end;

class function TMeinBeispiel.Values: TArray<TMeinBeispiel>;
var
  LIdx: Integer;
begin
  SetLength( Result, Length( TMeinBeispiel.VALID_VALUES ) );
  for LIdx := Low( TMeinBeispiel.VALID_VALUES ) to High( TMeinBeispiel.VALID_VALUES ) do
    begin
      Result[LIdx] := TMeinBeispiel.VALID_VALUES[LIdx];
    end;
end;

end.
Und man benutzt das dann einfach
Delphi-Quellcode:
procedure DoWithValue( AValue : TMeinBeispiel );
begin
  // irgendwas damit machen
end;

begin
  DoWithValue( 5 ); // <- Wert aus der Datenbank ist ein einfacher Integer
end.
Unzulässige Typen werden mit einer Exception direkt beim Umwandeln quittiert.

Keks 25. Jan 2015 16:28

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Huiuiui, da werden aber große Geschütze aufgefahren! :shock:

Ich dachte, mein Lösungsansatz (GetEnumName/GetEnumValue) sei zu umständlich und es müsse doch irgendwie einfacher gehen.
Aber bei Sir Rufos Lösung (Vielen Dank für die Mühe!) bin ich doch etwas baff.

Ich habe jetzt mal meinen Ansatz ausprobiert:
Delphi-Quellcode:
var
  MeinBeispiel: TMeinBeispiel;
begin
  MeinBeispiel := GetEnumMeinBeispielDefault(i);

...

function GetEnumMeinBeispielDefault(const value: Integer): TMeinBeispiel;
var
  s: String;
  i: Integer;
begin
  s := GetEnumName(TypeInfo(TMeinBeispiel), value);
  i := GetEnumValue(TypeInfo(TMeinBeispiel), s); //s enthält "Speichermüll", wenn value nicht in TMeinBeispiel, statt leer zu sein
  if i>0 then
    Result := TTrayAction(value)
  else
    Result := mbEins; //Default-Wert
end;
Aber das geht wohl nur, wenn TMeinBeispiel keine manuelle Indizes-Anpassung erhält, sonst "Type 'TMeinBeispiel' has no type info". Das ist wohl das, was himitsu meinte.

Wie gesagt war meine ursprüngliche Hoffnung, dass es sowas wie
Delphi-Quellcode:
if i in TMeinBeispiel then
geben müsste, deren Syntax mir nicht bekannt ist. Letztlich sind in der Aufwählung einige Werte manuell festgelegt (0,1,5) und ich möchte gegen diese einen anderen Wert vergleichen.

Sir Rufo 25. Jan 2015 16:53

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Ich stelle mir immer die Frage nach Typsicherheit und der Bequemlichkeit nach der "Anstrengung". Du kannst dir auch einen Record erstellen der zwischen dem ENUM und dem korrespondierendem Integer-Wert vermittelt. Alle Schnittstellen benutzen den Record, die Anwendung den ENUM und die Datenbank den Integer. Das geht auch.

himitsu 25. Jan 2015 16:58

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Wie gesagt, ohne Typ-Info bleibt nur noch die Speichergröße.
Und die Typinfo fehlt, weil man zu blöd ist und es nicht schafft "fehlende" Werte in die Namensliste aufzunehmen. :roll:

Zitat:

Zitat von Sir Rufo (Beitrag 1287701)
Du kannst dir auch einen Record erstellen der zwischen dem ENUM und dem korrespondierendem Integer-Wert vermittelt.

Zitat:

Delphi-Quellcode:
class operator implicit( const a: Integer ): TMeinBeispiel;
class operator implicit( const a: TMeinBeispiel ): Integer;

Jupp, statt nur zwischem dem Record und Integer zu casten, kann man auch noch den Enum mit in die Casts aufnemen und zusätzlich vielleicht noch Getter-, Setter- und Übersetzngsmethoden implementieren.

Keks 25. Jan 2015 17:17

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Beim Debuggen sehe ich:
Delphi-Quellcode:
mb := TMeinBeispiel(1); //mb = mbZwei
mb := TMeinBeispiel(4); //mb = (out of bound) 4
Der Debugger stellt hierbei doch auch irgendwie fest, ob der Wert unter den vorgegebenen Werten ist oder nicht. Man selbst kann das aber nicht tun? :(

himitsu 25. Jan 2015 17:26

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
k.A. wo der Debugger die Werte her holt, aber in der einkompilierten RTTI fehlt ganz einfach die Liste der Namen, sobald man selber die Werte zuweist.
Und die neue erweiterte RTTI geht bei Enums IMHO auch nur auf die alte RTTI.

Es wird dafür ein Array verwendt, wie man es z.B. von der Registry und anderen WinAPIs kennt.
Delphi-Quellcode:
'NameFürWert0'#0'NameFürWert1'#0'NameFürWert2'#0'NameFürWert3'#0#0

Und wenn man jetzt Werte weg lässt, dann entstünde #0#0, was ja dem Listenende entspricht, und die Liste wäre unvollständig/kaputt. Darum lässt der doofe Compiler/Linker diese Liste einfach ganz weg, anstatt z.B. einen "Dummy"-Wert einzufügen oder eine andere Speicherstruktur zu benutzen. :stupid:

Keks 25. Jan 2015 17:46

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Zitat:

Zitat von himitsu (Beitrag 1287704)
k.A. wo der Debugger die Werte her holt, aber in der einkompilierten RTTI fehlt ganz einfach die Liste der Namen, sobald man selber die Werte zuweist.

Moment, aber ich brauche doch gar nicht die Namen, sondern nur die Zahlen-Werte!?

himitsu 25. Jan 2015 17:58

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Es werden aber nicht die Zahlen gespeichert, sondern die Namen, in einem indizierten Array. (Index = Zahl :stupid:)

Als Zahl ist alles im Wertebereich des Speichertyps gültig.


Alle Zeitangaben in WEZ +1. Es ist jetzt 13:19 Uhr.
Seite 1 von 3  1 23      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz