views:

610

answers:

6

Hi,

Considering such an enumeration :

type
  TTypeOfData = (
    [XmlName('ABC')] todABC,
    [XmlName('DEF')] todDEF,  
    [XmlName('GHI')] todGHI
  );

Where XmlName is a custom attribute used to define the serialization string for members of this enumeration.

How can I explore the attributes attached to each member of this enumeration ?

Regards,

-- Pierre

+11  A: 

Attributes associated with elements in enumerations are not currently stored in Win32 RTTI data in the executable. RTTI is already responsible for a fair increase in the size of executables, so some lines had to be drawn somewhere. Attributes in Delphi Win32 are supported on types, on fields of records, and fields, methods, their parameters, and properties of classes.

The attribute declarations don't cause errors because of backward compatibility with Delphi for .NET.

Barry Kelly
Good explanation. But IMO in that case, they ought to cause an "unsupported language feature" warning, like other invalid attribute use does.
Mason Wheeler
+2  A: 

These is a good overview of RTTI in Delphi 2010 on the web: http://robstechcorner.blogspot.com/2009/09/so-what-is-rtti-rtti-is-acronym-for-run.html

You can get the enumeration values and back the ordinals using the "OLD" RTTI functions in the unit TypInfo (GetEnumValue, GetEnumName). And clip off the lowercase letters you get the same result as above but it is not as flexible.

Ritsaert Hornstra
+10  A: 

While Barry clearly answered your question regarding the attributes on enum elements, I'll take a stab at another suggestion. From your example, you're prefixing each enum element with 'tod' as is traditional in Delphi because enum elements are global in scope (ie. if you had an identifier todABC in scope in addition to the todABC enum elements, you could get some odd behaviors).

Starting in D2007, we introduced the notion of "scoped enums" which, when enabled, require you to qualify the enum element with the identifier of the enum itself. For instance:

{$SCOPEDENUMS ON}
type
  TTypeOfData = (ABC,DEF,GHI);

Will require you to refer to the ABC element as TTypeOfData.ABC. This allows you to use non-prefixed enum element identifiers and not run the risk of having conflicts since the elements are "scoped" to the enumeration. Any enum declared while {$SCOPEDENUMS} is enabled will behave in this manner.

Given that, you can now safely use the RTTI to get the actual enum element names in the format you wish.

Allen Bauer
Thank you Allen,That was a bad example. My enumeration is a bit more complex and serialized strings are not the same as the enumeration members.
ZeDalaye
Phew, that is cool! Didn't know that, always disliked the global clutter vs. ugly prefixes compromise one had to make with enum...
Robert Giesecke
@ZeDalaye, I suspected that, however if there is some nugget of usefulness in my suggestion... If not, I'm sure someone may find it useful.
Allen Bauer
What a cool hack! Nice one Allen! I would have added "you can safely use classic RTTI TypInfo OR the new RTTI, to get the enum element names".
Warren P
+1  A: 

For those who are interrested in a practical solution to that problem, I solved it that way :

type
  TTypeOfData = (todABC, todDEF, todGHI);

  TMySerializableClass = class
  private
    FType: TTypeOfData;
  public
    property &Type: TTypeOfData read FType write FType;
    class function TypeOfDataAsString(&Type: TTypeOfData): String;
  end;

implementation

class function TMySerializableClass.TypeOfDataAsString(&Type: TTypeOfData): String;
const
  TYPE_STRING: array[TypeOfDataAsString] of String = ('ABC', 'DEF', 'GHI);
begin
  Result := TYPE_STRING[&Type];
end;

And later, in the serialization code, I use RTTI to look for a class function conventionnaly named AsString and call it with the property TValue :

procedure Serialize(const V: TValue);
var
  N: String;
  T: TRttiType;
  F: TRttiField;
  M: TRttiMethod;
  R: TValue;
 begin
   case V.TypeInfo^.Kind of
   tkEnumeration:
   begin
     T := Ctx.GetType(TypeInfo(TMySerializableClass));
     N := V.TypeInfo.Name + 'AsString';
     if N[1] = 'T' then
       Delete(N, 1, 1);
     M := T.GetMethod(N);
     if (M <> nil) and M.IsClassMethod and (M.MethodKind = mkClassFunction) and (M.ReturnType.TypeKind = tkUString) then
     begin
       R := M.Invoke(TTicket, [V]);
       // serialize R.AsString
     end;
   end;
   ...
 end;
ZeDalaye
This is a reasonable solution. I really like Allen Bauer's suggestion too.
Warren P
A: 

Did someone find a better solution for this problem? I think for an application with lots of enumerations this solution is hard to apply, as I have to write an "AsString" function for each enumeration type.

Furthermore, I would prefer to have the declaration of strings in the const section of the interface close to the declaration of the enumeration type, which has advantages when changing the enumeration type.

Another problem might be, that I want to translate the descriptions for localization.

Christian Metzler
A: 

Ok I think I have found a better solution. I declare a new attribute type, e.g.:

TEnumAttribute = class (TCustomAttribute)
  private 
    FCaption : string;
  public
    constructor Create (const Caption : string);
    property Caption : string read FCaption write FCaption;
end;

Now I add attributes to my enumeration:

[TEnumAttribute ('Normal')]
[TEnumAttribute ('High')]
TExampleEnum = (eeNormal,eeHigh);

Now it is easy to access the attributes by its ordinal:

RttiType := RttiContext.FindType ('ExampleUnit.TExampleEnum');
RttiAttributes := Rttitype.GetAttributes;
Test := TEnumAttributes(RttiAttributes[index]).Caption;
Christian Metzler