Jump to content
Lars Fosdal

Delphi pitfalls: Enumerated types and for loops

Recommended Posts

6 minutes ago, Lars Fosdal said:

Isn't there something weird about RTTI for enums that have manually set ordinal values as well?

Define "weird". Enumerations with defined ordinal values do not have RTTI.

 

See http://docwiki.embarcadero.com/RADStudio/en/Simple_Types_(Delphi)#Enumerated_Types_with_Explicitly_Assigned_Ordinality

Edited by Leif Uneus

Share this post


Link to post
Just now, Leif Uneus said:

Define "weird". Enumerations with defined ordinal values do not have RTTI.

That is the "weird" right there.  Definitively a good reason to avoid defined ordinal values in enumerations. 

  • Like 1

Share this post


Link to post
35 minutes ago, Lars Fosdal said:

Definitively a good reason to avoid defined ordinal values in enumerations.

Lately I often use helpers to map those enumerations to their ordinal values. 

type
  TEnum = (plough, foo, bar, wtf);

  TEnumHelper = record helper for TEnum
  private
  const
    FMap: array[TEnum] of Integer = (5, 9, 14, 1000);
    function GetAsInteger: Integer;
    procedure SetAsInteger(const Value: Integer);
  public
    property AsInteger: Integer read GetAsInteger write SetAsInteger;
  end;

function TEnumHelper.GetAsInteger: Integer;
begin
  Result := FMap[Self];
end;

procedure TEnumHelper.SetAsInteger(const Value: Integer);
var
  idx: TEnum;
begin
  for idx := Low(FMap) to High(FMap) do begin
    if FMap[idx] = Value then begin
      Self := idx;
      Exit;
    end;
  end;
  raise ERangeError.CreateFmt('invalid integer for TEnum: %d', [Value]);
end;

This is pretty simple to use:

var
  enum: TEnum;
begin
  enum.AsInteger := plough.AsInteger + foo.AsInteger;
  if enum = bar then begin
    ShowMessage('Hooray!');
  end
  else begin
    ShowMessage(wtf.AsInteger.ToString);
  end;
end;

 

  • Like 2

Share this post


Link to post

@Uwe Raabe Phuhhh, this is just mapping numbers into an enum.

 

Your helper actually is like the following code, with obfuscated names. I don't think I want to debug such code.

It even works on negative values in the list.

  TEnum = (five, nine, fourteen, thousand)

  enum.AsInteger := five.AsInteger + nine.AsInteger;
  if enum = fourteen then
  begin
    Writeln('Hooray!');
  end

 

There must be a special use-case to do this, I suppose.

 

 

Edited by Attila Kovacs

Share this post


Link to post
3 minutes ago, Attila Kovacs said:

this is just mapping numbers into an enum.

Well, that is what giving number in the enum declaration is, too,

 

The ADD example was just a little joke, because the numbers given nicely add up by coincidence.  

  • Like 1

Share this post


Link to post
13 minutes ago, Lars Fosdal said:

Just curious: Has anyone seen a valid use case for defined ordinal values in enumerations?

Not really when writing my own code, I try to avoid them if possible.

Sometimes for interop with an API, e.g. when translating C headers where enums are already declared that way.

Or when you need to support a binary format which uses some specific ordinal values, then it depends - you might still prefer to use an enum but avoid conversion.

Share this post


Link to post

I used it for grouping (in very rare cases:

type
  TMyEnum = ( 
               None,
               GroupA = 100,
                   A_1,
                   A_2,

               GroupB = 200,
                   B_1,
                   B_2,

               Group_Last
            );

  TMyEnum_Helper = record helper for TMyEnum
      function IsGroupA : Boolean;
  end;
  
function TMyEnum_Helper.IsGroupA : Boolean;
begin
    Result := (Value > TMyEnum.GroupA) and (Value < TMyEnum.GroupB);
end;

function TMyEnum_Helper.IsGroupB : Boolean;
begin
    Result := (Value > TMyEnum.GroupB) and (Value < TMyEnum.Group_Last);
end;
                                                        

I know thats a little smelly, so please don'T throw stones on my head.

But I still find it very useful and efficient in some special cases, where I want to avoid a second variable for the grouping.

 

 

 

Share this post


Link to post
5 hours ago, Ondrej Kelle said:

Not really when writing my own code, I try to avoid them if possible.

Sometimes for interop with an API, e.g. when translating C headers where enums are already declared that way.

Or when you need to support a binary format which uses some specific ordinal values, then it depends - you might still prefer to use an enum but avoid conversion.

I still prefer using regular constants for bit-fiddling.

  • Like 1

Share this post


Link to post
25 minutes ago, Rollo62 said:

so please don'T throw stones on my head.

Only virtually 🚯

Share this post


Link to post
On 2/25/2019 at 8:36 AM, Lars Fosdal said:

Just curious: Has anyone seen a valid use case for defined ordinal values in enumerations?

Yes, C or C++ header translations (altough I would prefer integers and masks for those).

 

For plain Delphi code they don't make a lot of sense.

Edited by Rudy Velthuis

Share this post


Link to post
On 2/25/2019 at 2:36 AM, Lars Fosdal said:

Just curious: Has anyone seen a valid use case for defined ordinal values in enumerations?

 

Mapping days of the weeks to numbers. For instance, in some systems Monday is zero, while with ISO it is 1-7.  And in the United States we like the week to start on Sunday instead of Monday.

 

 

Share this post


Link to post
On 3/1/2019 at 3:42 PM, Rudy Velthuis said:

Yes, C or C++ header translations (altough I would prefer integers and masks for those).

 

For plain Delphi code they don't make a lot of sense.

Why don't they make a lot of sense?

Share this post


Link to post
On 2/18/2019 at 12:05 PM, Lars Fosdal said:

Not all for loops are created equal.
Consider
for x in [value1, value2, value3]

You would expect to see x vary in the order of the values in the list.  However – if x and the values are of an enumerated type, looping the “list” does NOT loop in the apparent order of the constant, but in the order of the enumerated type declaration, such as it would for any set.

 

Example at: https://larsfosdal.blog/2019/02/18/delphi-pitfalls-enumerated-types-and-for-loops/

If you use in, i.e. enumerators, you should not rely on any specific order, unless the enumerator is documented to have one. The only guarantee is that every element is visited, if possible (i.e. no break, no exceptions, etc.).

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×