I personally have used TOndrej's approach extensively in several large mission critical data processing platforms. The benefit of enumerations is that they can be easily passed around within an application, are very compact (an ordinal type), work perfectly in case statements and are completely type safe. The later point is important for maintenance since deleting or changing enumeration values will cause a compile error (a good think IMHO).
Some gotcha's to look out for with this approach:
Changing the declared order of enum values will foo bar the enum->string lookup array.
If you use the enum in case statements (a nice feature) be sure to take into account new values being added. I usually add an else to the case and throw an exception on unknown values. Much better than falling through the case.
If you are very concerned about the first gotcha, you can use a record in the lookup array and include the enum value in each record and validate the ordering from the unit initialization. This has saved my bacon many times in mission critical systems where maintenance is frequent. This approach can also be used to add additional meta data to each value (a very handy feature).
Best of luck!
unit Unit1;
interface
type
TItemStatusEnum = (isOpen, isActive, isClosed);
TItemStatusConst = class
class function EnumToString(EnumValue : TItemStatusEnum): string;
property OPEN: string index isOpen read EnumToString;
property CLOSED: string index isClosed read EnumToString;
property ACTIVE: string index isActive read EnumToString;
end;
var
ItemStatusConst : TItemStatusConst;
implementation
uses
SysUtils;
type
TItemStatusRec = record
Enum : TItemStatusEnum;
Value : string;
end;
const
ITEM_STATUS_LOOKUP : array[TItemStatusEnum] of TItemStatusRec =
((Enum: isOpen; Value: 'OPEN'),
(Enum: isActive; Value: 'ACTIVE'),
(Enum: isClosed; Value: 'CLOSED'));
procedure ValidateStatusLookupOrder;
var
Status : TItemStatusEnum;
begin
for Status := low(Status) to high(Status) do
if (ITEM_STATUS_LOOKUP[Status].Enum <> Status) then
raise Exception.Create('ITEM_STATUS_LOOKUP values out of order!');
end;
class function TItemStatusConst.EnumToString(EnumValue: TItemStatusEnum): string;
begin
Result := ITEM_STATUS_LOOKUP[EnumValue].Value;
end;
initialization
ValidateStatusLookupOrder;
end.
Update:
Thanks for the critique - you are absolutely correct in that I was solving what I perceived as the problem underlying the question. Below is a much simpler approach if you can live with the constraint that this must ONLY work in Delphi 2007 or later versions (might work in D2006?). Hard to shed those nagging thoughts of backward compatibility ;)
type
ItemStatusConst = record
const OPEN = 'OPEN';
const ACTIVE = 'ACTIVE';
const CLOSED = 'CLOSED';
end;
This approach is simple and has a similar feel to what one might do in a Java or .Net application. Usage semantics are as expected and will work with code completion. A big plus is that the constants are scoped at a record level so there is no risk of clashes with other definitions as happens with unit scoped types.
Sample usage:
ShowMessage(ItemStatusConst.ACTIVE);
ShowMessage(ItemStatusConst.CLOSED);
Just for fun, I have also revised my earlier approach to directly address the original question with a similar outcome. This time I made use of "simulated class properties" (see here) with a property indexer. This is clearly more complex than the record approach, but retains the ability to work with enum values, sets, strings AND can also be extended to implement additional meta data features where required. I believe this works for Delphi versions going as far back as Delphi 5 IIRC.
An example of where I used this technique to great effect was a parsing framework I created in Delphi to handle the full IBM AFPDS print data stream grammar. This flexibility is why I love working in Delphi - it is like a Swiss army knife ;)