Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Delphi merkt nicht wenn ein case-Statement vollständig ist, oder? (https://www.delphipraxis.net/209408-delphi-merkt-nicht-wenn-ein-case-statement-vollstaendig-ist-oder.html)

Der schöne Günther 2. Dez 2021 20:05

Delphi-Version: 10 Seattle

Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Ich bekomme bei folgendem
Delphi-Quellcode:
case
-Statement eine Warnung, dass eine Variable "möglicherweise" nicht initialisiert sein könnte:
Delphi-Quellcode:
type
   TEnum = (a, b, c);
var
   myEnum: TEnum;
   value: Byte;
begin
   myEnum := TEnum.a;
   case myEnum of
      TEnum.a:   value := 10;
      TEnum.b:   value := 20;
      TEnum.c:   value := 30;
   end;
   WriteLn(value); // W1036"might not have been initialized"
   // altough the case statement is exhaustive
end.
Bei C++ ist der Compiler schlau genug:
Code:
enum Enum {a, b, c};

int main(int argc, char **argv) {
   Enum e = Enum::b;
   char c;
   switch (e)
   {
      case Enum::a:
         c = 1;
         break;
      case Enum::b:
         c = 2;
         break;
      case Enum::c:
         c = 3;
         break;
   }   

   std::cout << c;
   return NO_ERROR;
}
Bei Sprachen wie Swift oder Rust bricht der Compiler mit einem Fehler ab, wenn ein match-Statement nicht vollständig ist.

Delphi bekommt von all dem aber nichts mit, und redet sich damit raus, dass es "möglicherweise" undefiniert sein könnte.
Zumindest in 10.0 Seattle.

Ist das in einer aktuellen Delphi-Version immer noch so?

himitsu 2. Dez 2021 20:09

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Jupp, denn Delphi weiß, dass in deinem Enum noch 253 weitere Werte sein können.

z.B.
Delphi-Quellcode:
myEnum := TEnum(123);



Sowas kannst du nur ausschließen, wenn bei Zuweisung an diese Variable der Wertebereich geprüft wird,
und wenn man sämtliche Fehler ausschließen kann, z.B. böse Casts, fehlgeleitete Pointer, Buffer-Overflow, Häcker/Würmer/Viren, usw.

Der schöne Günther 2. Dez 2021 20:14

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Oder es heißt dass der Teil der die Fehlermeldungen generiert gar nichts mehr von Enums weiß, da er nur noch mit blanken Zahlentypen arbeitet.

Oh mann... 😕

BerndS 2. Dez 2021 20:32

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Ich füge dem case oft noch ein else hinzu, wobei dann im Debug eine Exception ausgelöst wird und im Release passiert nichts.
Kommt später was zur Aufzählung hinzu, so erinnert mich die Exception dann, das noch was fehlt.

bernau 2. Dez 2021 23:32

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Zitat:

Zitat von BerndS (Beitrag 1498595)
Ich füge dem case oft noch ein else hinzu, wobei dann im Debug eine Exception ausgelöst wird und im Release passiert nichts.
Kommt später was zur Aufzählung hinzu, so erinnert mich die Exception dann, das noch was fehlt.

Bei mir auch. Genau so.

himitsu 2. Dez 2021 23:47

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Ich werfe die Exceptions immer, egal ob Debug oder Release.
Oder setze im Else die Variable auf NIL, was eh sein muß, weil ja sonst der Compiler von nicht-initialisierten Variablen schwafelt.

Abgesehn da, wo eh immer nur ein Teil behandelt wird.

Rollo62 3. Dez 2021 06:41

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Ich setze IMMER ein "else", weil man ja schnell mal einen Typen ergänzt und dabei ein case statement vergisst.
Das macht es dann "sichtbarer", fehlertolerant und die Fehlersuche ist dann viel leichter als wenn man sich auf Compiler-Automatismen verlassen würde.
Deswegen finde ich das Warning auch gut und richtig.

Mavarik 19. Nov 2024 19:59

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1498589)
Bei Sprachen wie Swift oder Rust bricht der Compiler mit einem Fehler ab, wenn ein match-Statement nicht vollständig ist.

Oh wie gruselig ist das den?

Wieso soll ich den immer alles definieren? Vielleicht will ich mit der Case ja nur Teile prüfen...

Oder übersehen ich da etwas?

Mavarik

jaenicke 19. Nov 2024 20:17

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Zitat:

Zitat von Mavarik (Beitrag 1543267)
Wieso soll ich den immer alles definieren? Vielleicht will ich mit der Case ja nur Teile prüfen...

Oder übersehen ich da etwas?

Nein, da übersiehst du nichts. Aber das Problem ist dabei, dass solcher Code auch fehleranfällig ist. Denn du merkst ja gar nicht, wenn ein Enum erweitert wird und der Fall nicht behandelt wird. Wenn mehrere Entwickler an einem Projekt arbeiten, passiert so leicht ein Fehler.

Genau aus diesen Gründen wird das in Rust und anderen Sprachen geprüft und genau deshalb werden diese an sicherheitsrelevanten Stellen gerne eingesetzt.

Für den von dir beschriebenen Fall kann man z.B. mit if-Statements arbeiten.

TomyN 20. Nov 2024 06:17

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Ich denke, ein 'Defaultwert' ist immer wichtig. Ob man den jetzt im else setzt oder (was ich inzwischen, auch weil ich beginne die Exit Anweisung einzusetzen, gerne mache) vor der Unterscheidung erstmal einen Wert zuzuweisen, ist in meinen Augen (fast) egal. Beim Else offenbart sich ein Fehler (d.h. eine 'unerwartete' Bedingung) eher, was in der Entwicklung wünschenswert, beim Kunden aber ggf. kontraproduktiv ist.

Uwe Raabe 20. Nov 2024 10:26

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Ich persönlich hätte auch etwas gegen einen Compilerfehler bei unvollständigem
Delphi-Quellcode:
case x of
. Es kommt schon häufiger vor, dass ich vorab per
Delphi-Quellcode:
if x in [..] then
eine Bereichsprüfung mache und dann mit einem
Delphi-Quellcode:
case x of
die verschiedenen (in diesem Bereich möglichen) Fälle auseinandernehme.

Gäbe es generell einen Fehler bei einem unvollständigen case-Statement wäre die else-Syntax ja auch überflüssig.

Nichtsdestotrotz gibt es bei mir auch reichlich case-Konstrukte, bei denen im else-Zweig eine EProgrammerNotFound Exception geworfen wird. Die bereits anderswo erwähnten Unit-Tests sind dabei schon sehr hilfreich.

jaenicke 20. Nov 2024 10:48

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1543309)
Gäbe es generell einen Fehler bei einem unvollständigen case-Statement wäre die else-Syntax ja auch überflüssig.

Mit else sollte kein Fehler kommen, das sehe ich auch so. Ich setze das else aber meistens nur, um einen Fehler durch fehlende Behandlung neuer Elemente abzufangen. Das wäre dann oft unnötig.

QuickAndDirty 20. Nov 2024 12:02

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1498592)
Oder es heißt dass der Teil der die Fehlermeldungen generiert gar nichts mehr von Enums weiß, da er nur noch mit blanken Zahlentypen arbeitet.

Oh mann... 😕

Ich wähle Dich! Der schöne Günther for Bundeskanzler

Rollo62 20. Nov 2024 12:03

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1543309)
...
Delphi-Quellcode:
if x in [..] then
eine Bereichsprüfung mache ... und dann
Delphi-Quellcode:
case x of

Gibt es denn einen Vorteil, außer der vielleicht besseren Lesbarkeit oder Zusammenfassung von Gruppen, das vorab mit
Delphi-Quellcode:
if x in [..]
zu machen?
Z.B. Performance, oder so?

Ich meine ein "case x of" sollte immer am performantesten sein und das zusätzliche "if x in" überflüssig machen.
Ok, bei großen Strukturen wird es unlesbarer ...

Uwe Raabe 20. Nov 2024 14:29

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Das hat eigentlich ganz andere Gründe. Nehmen wir z.B. einen Enum-Typ, der positive und negative Elemente enthält. Bei positiven bzw. negativen Werten sind spezifische Code-Teile auszuführen, die teilweise vom genauen Wert unabhängig sind, während andere Teile vom exakten Wert abhängen:
Delphi-Quellcode:
type
{$SCOPEDENUMS ON}
  TRating = (undefined, Perfect, OK, Acceptable, NotGoodEnough, Bad, TotalShit);

  TRatingHelper = record helper for TRating
    procedure HandleIncompleteCase(Rating: TRating);
  end;

procedure TRatingHelper.HandleIncompleteCase(Rating: TRating);
begin
  raise EProgrammerNotFound.Create('unhandled case for TRating: ' + TRttiEnumerationType.GetName(Rating));
end;

const
  cRatingPositive = [TRating.Perfect, TRating.OK, TRating.Acceptable];
  cRatingNegative = [TRating.NotGoodEnough, TRating.Bad, TRating.TotalShit];

procedure HandleRating(Rating: TRating);
begin
  if Rating in cRatingPositive then begin
    // Handle positive rating part 1
    ...
    // Handle specific rating
    case Rating of
      TRating.Perfect: ...;
      TRating.OK: ...;
      TRating.Acceptable: ...;
    else
      HandleIncompleteCase(Rating);
    end;
    // Handle positive rating part 2
    ...
  end
  else if Rating in cRatingNegative begin
    // Handle negative rating part 1
    ...
    // Handle specific rating
    case Rating of
      TRating.NotGoodEnough: ...;
      TRating.Bad: ...;
      TRating.TotalShit: ...;
    else
      HandleIncompleteCase(Rating);
    end;
    // Handle negative rating part 2
    ...
  end
  else begin
    // Handle undefined rating
    ...
  end;
end;

Der schöne Günther 20. Nov 2024 16:05

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Zitat:

Zitat von QuickAndDirty (Beitrag 1543315)
Ich wähle Dich! Der schöne Günther for Bundeskanzler

Lieber gar keine case-Fallunterscheidungen, statt schlechte case-Fallunterscheidungen.

Rollo62 20. Nov 2024 17:27

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1543323)
Das hat eigentlich ganz andere Gründe.

Ah ja, das hatte ich mir schon gedacht.
Danke, das ist ein sehr schönes Beispiel :thumb:

Aber trotzdem, ist es am Ende nicht fast das gleiche Ergebnis wie:

Delphi-Quellcode:

// Handle specific rating
    case Rating of
      //Positive rating
      TRating.Perfect: ...;
      TRating.OK: ...;
      TRating.Acceptable: ...;

      //Negative rating
      TRating.NotGoodEnough: ...;
      TRating.Bad: ...;
      TRating.TotalShit: ...;
    else
      HandleIncompleteCase(Rating); //<== OK, hier kann man nicht genau in Positive/Negative unterscheiden
                                     //    Aber Du kommst ja trotzdem auf den richtigen, "falschen" case.
                                     //    Wenn das "if x in" nicht passt, dann muss es auch nicht für "case x of" passen.
     
    end;

type
    cRatingPositive = [TRating.Perfect..TRating.Acceptable]; // Wenn man es so definieren könnte, dann vielleicht.
So richtig sehe ich den Mehrwert noch nicht.
Ist das dann nicht eher ein Fall für ein Enum Set, mit allen seinen mathematischen Möglichkeiten?

Uwe Raabe 20. Nov 2024 17:57

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Zitat:

Zitat von Rollo62 (Beitrag 1543327)
So richtig sehe ich den Mehrwert noch nicht.

Einen Mehrwert wollte ich auch gar nicht demonstrieren. Es geht mir eher darum Anwendungsfälle aufzuzeigen, bei denen ein unvollständiges case im Kontext durchaus vollständig sein kann. Dann würde ein Error-Mechanismus wie oben beschrieben zu einem unnötigen Workaround-Aufwand führen um die Fehlermeldung (vermutlich sollte es vielleicht doch eher nur eine Warnung oder ein Hinweis sein) zu beseitigen. Immerhin kann der Fehler im obigen Code gar nicht auftreten - zumindest nicht in den case-Anweisungen.

Diese ganze Diskussion ist eh nur theoretisch, da sie allenfalls case-Anweisungen ohne else-Zweig beträfe. Nur so könnte man das durchziehen ohne bestehenden Code plötzlich als fehlerhaft zu diffamieren. (Ich gehe dabei mal davon aus, dass der Entwickler weiß was er tut.)

Insofern sind die im Code vorhandenen else-Zweige eigentlich auch obsolet und decken lediglich den Fall ab, dass die vorhergehende if-Anweisung klammheimlich erweitert und dabei die case-Anweisung nicht mitgezogen wird.

Im Übrigen ist der Code auch nicht ganz korrekt in Bezug auf den record helper (und ein fehlendes then). Kommt davon wenn man Code im Web-Editor eingibt anstatt in der IDE.

Rollo62 21. Nov 2024 07:18

AW: Delphi merkt nicht wenn ein case-Statement vollständig ist, oder?
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1543329)
Zitat:

Zitat von Rollo62 (Beitrag 1543327)
So richtig sehe ich den Mehrwert noch nicht.

Einen Mehrwert wollte ich auch gar nicht demonstrieren. Es geht mir eher darum Anwendungsfälle aufzuzeigen...

Sorry, auch Anwendungsfälle sehe ich für einen Guard vorab nicht unbedingt, statt diesen zentral am Ende im else-Zweig durchzuführen.
Der Vorab-Guard nimmt nur Performance und hilft meiner Meinung nach noch nicht mal bessere Lesbarkeit durch Gruppierung zu erzeugen.
Ich bleibe bei Version HandleRating2.
Trotzdem interessiert mich ein echtes Beispiel, wo das "if x in" gegenüber das "case x of" klar gewinnt, denn Ersteres nutze ich eigentlich so gut wie nie.
Das liegt vielleicht daran, dass ich seit C sehr in das switch bzw. case of verliebt bin :oops:

Das Beispiel habe ich mal entsprechend erweitert, ist ausführbar, um alle Fälle auszutesten:

Delphi-Quellcode:

type
{$SCOPEDENUMS ON}
  //(1) Wegfall eines Enums ==> Compilierfehler
//  TRating = (undefined, Perfect, { OK, } Acceptable, NotGoodEnough, Bad, TotalShit);

  //(2) Zusätzliches, positives Enum  NochWasGutes
  TRating = (undefined, Perfect, OK, NochWasGutes, Acceptable, NotGoodEnough, Bad, TotalShit);

  //(3) Zusätzliches, negatives Enum                                              SehrSchlecht
//  TRating = (undefined, Perfect, OK, NochWasGutes, Acceptable, NotGoodEnough, Bad, SehrSchlecht, TotalShit );

  //(4) Zusätzliches, negatives Enum am Ende                                      SehrSchlecht            OutOfRange
//  TRating = (undefined, Perfect, OK, NochWasGutes, Acceptable, NotGoodEnough, Bad, SehrSchlecht, TotalShit, OutOfRange );

  //(5) Behandelt Undefined
//  TRating = (Undefined, Perfect, OK, NochWasGutes, Acceptable, NotGoodEnough, Bad, SehrSchlecht, TotalShit, OutOfRange );


  TRatingHelper = record helper for TRating
    procedure HandleIncompleteCase;
  end;

procedure TRatingHelper.HandleIncompleteCase;
begin
  var LName := TRttiEnumerationType.GetName( Self );
  raise Exception.Create('unhandled case for TRating: ' + LName );
end;

const
  //cRatingPositive = [TRating.Perfect, TRating.OK, TRating.Acceptable];

  //(A) Funktioniert nur, wenn die Ratings als SubRange definiert werden können
  cRatingPositive = [TRating.Perfect      .. TRating.Acceptable ];
  cRatingNegative = [TRating.NotGoodEnough .. TRating.TotalShit  ];

  //(B) Funktioniert noch besser, wenn die äußeren Grenzen mit High/Low definiert werden können
  //                  Starte NICHT ab Undefined bis zum letzten
//  cRatingPositive = [ Succ(Low(TRating))    .. TRating.Acceptable ];
//  cRatingNegative = [ TRating.NotGoodEnough .. High(TRating),     ];

  //(C) Funktioniert noch besser, wenn die äußeren Grenzen mit High/Low definiert werden können
  //                  Starte NICHT ab Undefined bis zum letzten gültigen Enum
//  cRatingPositive = [ Succ(Low(TRating))    .. TRating.Acceptable ];
//  cRatingNegative = [ TRating.NotGoodEnough .. TRating.TotalShit  ];



procedure HandleRating1(Rating: TRating);
begin

  if Rating in cRatingPositive then begin
    // Handle positive rating part 1       //<== (1) Kommt hier rein, wenn es einen neuen Positiven gibt

    // Handle specific rating
    case Rating of
      TRating.Perfect: ;
      TRating.OK: ;             //<== (1) Compile-Error bei Wegfall eines Enums
      TRating.Acceptable: ;
    else
       Rating.HandleIncompleteCase;       //(2)(3) Runtime-Error: "NochWasGutes", Wenn es einen neuen Positiven gibt
    end;
    // Handle positive rating part 2

  end
  else if Rating in cRatingNegative then begin
    // Handle negative rating part 1

    // Handle specific rating
    case Rating of
      TRating.NotGoodEnough: ;
      TRating.Bad: ;
      TRating.TotalShit: ;
    else
      Rating.HandleIncompleteCase;        //(3)(4) Runtime-Error: "SehrSchlecht", Wenn es einen neuen Negativen gibt
    end;
    // Handle negative rating part 2

  end
  else begin
    // (4)(5) Handle Undefined or OutOfRange rating
    Rating.HandleIncompleteCase;
  end;

end;


procedure HandleRating2(Rating: TRating);
begin

   // Handle specific rating
   case Rating of
     TRating.Perfect: ;
     TRating.OK: ;                  //<== (1) Compile-Error bei Wegfall eines Enums
     TRating.Acceptable: ;

   // Handle specific rating
     TRating.NotGoodEnough: ;
     TRating.Bad: ;
     TRating.TotalShit: ;
   else
     Rating.HandleIncompleteCase;  //(2)(3)(4)(5) Runtime-Error: "NochWasGutes", "SehrSchlecht", Zentral, für alle
                                    //             Könnte notfalls hier in Gruppen zerlegt werden, was nur im Fehlerfall Performance braucht
   end;

end;



procedure Test;
begin
    var LRating := TRating.NochWasGutes;   // (2)
//    var LRating := TRating.SehrSchlecht;   // (3)
//    var LRating := TRating.OutOfRange;       // (4)
//    var LRating := TRating.Undefined;       // (5)

    HandleRating1( LRating ); //RunTime-Fehler, wenn es was neues, Positives/Negatives gibt

//    HandleRating2( LRating ); //RunTime-Fehler, wenn es was neues, Positives/Negatives gibt

end;


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:47 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-2025 by Thomas Breitkreuz