Delphi-PRAXiS

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 15: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 16: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 16: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 17: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 17: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 17: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 18: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 18: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 18: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 18: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.

Sir Rufo 25. Jan 2015 19:06

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Die einfachste Art ist doch hier
Delphi-Quellcode:
type
  TMeinBeispiel = ( mbEins, mbZwei, mbDrei );
const
  MeinBeispielValues : array[TMeinBeispiel] of integer = ( 0, 1, 5 );
Wenn man das jetzt noch in einen Record packt, der die Umwandlung vornimmt, dann hat man doch alles zusammen und kann nach Belieben hin- und her konvertieren und das auch noch voll automatisch.

Keks 25. Jan 2015 21:04

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Sorry, aber irgendwie verstehe ich Dein Beispiel nicht ... oder ich stehe auf dem Schlauch.
Delphi-Quellcode:
MeinBeispielValues[mbDrei] = 5
aber was mache ich damit? Die Zuordnung
Delphi-Quellcode:
Ord(mbDrei) = 5
hatte ich doch vorher schon?

himitsu 25. Jan 2015 21:10

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

Delphi-Quellcode:
Ord(mbDrei) = 2
:zwinker:

Der Enum ist "einfach", besitzt eine vollständige RTTI, aber diehn nur der als Namensliste.
Die eigentlichen Werte sind aber extern abgelegt und werden nur für's Casten benutzt.

Keks 25. Jan 2015 21:12

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Ähm ...
Delphi-Quellcode:
type TMeinBeispiel = (mbEins=0, mbZwei=1, mbDrei=5);
begin
  ShowMessage(IntToStr(Ord(mbDrei))); // -> "5"

himitsu 25. Jan 2015 21:29

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

Delphi-Quellcode:
type
  TMeinBeispiel = ( mbEins, mbZwei, mbDrei );
const
  MeinBeispielValues : array[TMeinBeispiel] of integer = ( 0, 1, 5 );

:roll:

[edit]
"hatte" ... hatte "hab" gelesen. :oops:

Aber zusätzlich hast du so eben einen Code, wo man alle gültigen "Namen" auslesen kann und auch eine Liste (Array) mit allen gültigen Werten hat.

Dejan Vu 26. Jan 2015 08:28

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Da sieht man mal wieder, weshalb Enums 'böse' sind.

Ich würde eine Funktion schreiben, die über ein 'CASE' die Validität des Integers prüft und mich dann um die wichtigen Dinge kümmern.
Delphi-Quellcode:
Function IsValidEnum (aValue : Integer) : Boolean;
Begin
  case TMyEnum(aValue) Of
     myFirstEnum,
     mySecondEnum,
     ...
     myLastEnum : result := True;
     else result := False;
  end
end;
Das ist häßlich, aber leicht verständlich, und schnell umgesetzt. Klar, kommt ein Enum dazu, muss ich das die case-label anpassen erweitern.

Vereinfachen (z.B. über RTTI) würde ich das nie, denn das verleitet dann zum Verwenden von Enums auch bei neuen Projekten.

Keks 27. Jan 2015 12:15

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Die Lösung von Dejan Vu ist für meinen Spezialfall vermutlich die passendste. Hatte da seltsamerweise nicht selbst dran gedacht, aber ich hatte ja ohnehin wohl fälschlicherweise angenommen, dass Delphi da eine eigene Abfragemöglichkeit mitbringt.
Die Möglichkeit per Array-Konstante von Sir Rufo würde natürlich auch gehen (mit dem Vorteil die Werte iterieren zu können), aber letzlich müssen die Werte dann getrennt vom Enum gepflegt werden (bei längeren Aufzählungen blos nicht die Werte vertauschen ;)) und zum Gültigkeits-Abfrage brauche ich ja doch wieder eine zusätzliche Funktion.

Aber ich habe wieder was gelernt und habe eine Lösung. Vielen Dank Euch Dreien!!

himitsu 27. Jan 2015 14:26

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Statt der Funktion hatte ich mir, die ein/zwei Mal, wo ich sowas brauchte, eine Konstante direkt bei dem Typen deklariert.
Ist bei Änderungen auffälliger, als die Funktion außerhalb des Blickfeldes.

Delphi-Quellcode:
type
  TMyEnum = (a, b, c, z=25);

const
  cMyEnumRange = [a, b, c, z]; //cMyEnumRange: set of TMyEnum = [a, b, c, z];



var
  e: TMyEnum;
  s: set of TMyEnum;

if not (e in cMyEnumRange) then
  raise Exception.Create('nö');

if s - cMyEnumRange <> [] then
  raise Exception.Create('nö');

Dejan Vu 27. Jan 2015 14:42

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Können Enums nur Werte <=255 annehmen? Ansonsten eine sehr schöne Idee, zumal die Konstante direkt beim Enum deklariert werden kann: Wenn schon Schrott, dann wenigstens an einer Stelle. :thumb:

NicoDE 27. Jan 2015 14:55

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

Zitat von Dejan Vu (Beitrag 1287951)
Können Enums nur Werte <=255 annehmen?

Ein Delphi-Set is intern ein Bitfeld (wobei die Werte den Index angeben), welches auf 256 Bits limitiert ist (wahrscheinlich um "set of AnsiChar" zu unterstützen).
http://docwiki.embarcadero.com/RADSt...s_%28Delphi%29

himitsu 27. Jan 2015 15:31

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Das SET nutzt Befehle der CPU und dort gibt es nur ein Byte als Index ... darum auch nur 0 bis 255.
Darum funktioniert das SET für WideChar nicht mehr. :cry:

Der ENUM kann mindestens einen Integer als Speicher nutzen (also bis 32 Bit ... ob 64 Bit auch geht, weiß ich grade nicht auswendig)


SET: 0 <= x < 2^8 (maximal 32 Byte adressierbar)
ENUM: 0 <= x < 2^32 (vielleicht auch 64, aber ich glaub eher nicht)

Dejan Vu 27. Jan 2015 19:35

AW: Prüfen ob Integer im Enumeration-Type enthalten ist
 
Also ist das keine allgemeingültige Lösung.


Alle Zeitangaben in WEZ +1. Es ist jetzt 00:18 Uhr.

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