В Delphi перечисления (enums) являются мощным инструментом для работы с константами, которые могут быть легко именованы. Однако, иногда возникает необходимость ассоциировать с каждым элементом перечисления дополнительные данные, такие как атрибуты. В этой статье мы рассмотрим, как это можно сделать и какие методы доступа к этим атрибутам существуют.
Введение в атрибуты перечислений
Атрибуты перечислений позволяют связывать дополнительную информацию с каждым элементом перечисления. Это может быть полезно, например, для хранения метаданных, таких как имена строковых значений, которые могут быть использованы для отображения или для других целей. В Delphi атрибуты можно использовать для аннотирования элементов перечисления, но из-за ограничений в RTTI (Run-Time Type Information) доступ к атрибутам конкретных элементов может быть ограничен.
Пример использования атрибутов перечислений
Рассмотрим пример перечисления с атрибутами:
type
TOAuthSubjectTypes = (
[MappingAttr('public')]
ostPublic,
[MappingAttr('pairwise')]
ostPairwise
);
Здесь атрибут MappingAttr ассоциируется с каждым элементом перечисления. Мы хотим получить доступ к этому атрибуту для конкретного элемента, например, для ostPairwise.
Ограничения доступа к атрибутам
В Delphi RTTI не хранит информацию об атрибутах конкретных элементов перечисления. RTTI предоставляет информацию о типах и их полях, но не о конкретных элементах перечислений. Это означает, что стандартный способ доступа к атрибутам через RTTI не работает для отдельных элементов перечисления.
Решение через атрибуты типа перечисления
Одним из возможных решений является использование атрибутов, связанных с типом перечисления, а не с отдельными элементами. Например, можно определить атрибут для всего перечисления и использовать его для доступа к атрибутам элементов через индексацию.
Затем можно использовать RTTI для доступа к атрибутам по индексу элемента перечисления.
Реализация доступа к атрибутам
Для реализации доступа к атрибутам можно использовать следующие функции:
EnumToString: Преобразует значение перечисления в строку, связанную с атрибутом.
StringToEnum: Преобразует строку в значение перечисления на основе связанного атрибута.
Пример кода
Вот пример реализации этих функций:
type
TEnumMapping = class
public
class function EnumToString<T>(const Value: T): string; static;
class function StringToEnum<T>(const Value: string): T; static;
end;
class function TEnumMapping.EnumToString<T>(const Value: T): string;
var
RTTICtx: TRttiContext;
RTTIType: TRttiType;
RTTIEnum: TRTTIEnumerationType;
GenericValue: TValue;
OrdValue: Int64;
begin
RTTICtx := TRTTIContext.Create;
try
RTTIType := RTTICtx.GetType(TypeInfo(T));
if (RTTIType = nil) or (RTTIType.TypeKind <> tkEnumeration) then
raise Exception.CreateFmt('Type %s is not an enumeration', [RTTIType.Name]);
GenericValue := TValue.From<T>(Value);
if not GenericValue.TryAsOrdinal(OrdValue) then
raise EInvalidCast.Create('Could not cast generic value to ordinal');
RTTIEnum := TRTTIEnumerationType(RTTIType);
if (OrdValue > RTTIEnum.MaxValue) or (OrdValue < RTTIEnum.MinValue) then
raise EEnumNameNotFound.CreateFmt('%d has no valid enum name for %s', [OrdValue, RTTIType.Name]);
exit(MappingAttr(RTTIEnum.GetAttributes()[OrdValue]).Value);
finally
RTTICtx.Free;
end;
end;
class function TEnumMapping.StringToEnum<T>(const Value: string): T;
var
RTTICtx: TRttiContext;
RTTIType: TRttiType;
Index: Integer;
Found: Boolean;
begin
RTTICtx := TRTTIContext.Create;
try
RTTIType := RTTICtx.GetType(TypeInfo(T));
if (RTTIType = nil) or (RTTIType.TypeKind <> tkEnumeration) then
raise Exception.CreateFmt('Type %s is not an enumeration', [RTTIType.Name]);
FillChar(Result, SizeOf(Result), 0);
var ItemCount, AttrCount: Integer;
ItemCount := Length(TRTTIEnumerationType(RTTIType).GetNames());
AttrCount := Length(RTTIType.GetAttributes());
if (AttrCount = 0) or (ItemCount <> AttrCount) then
raise ERTTIMappingBadFormat.CreateFmt('RTTI Mapping in %s is badly formated.', [RTTIType.Name]);
Index := 0;
Found := False;
for var Attr: TCustomAttribute in RTTIType.GetAttributes do
begin
if Attr is MappingAttr then
begin
Inc(Index);
Found := MappingAttr(Attr).Value = Value;
if Found then
break;
end;
end;
if not Found then
raise ERTTIMappingNotFound.CreateFmt('No match for %s on %s', [Value, RTTIType.Name]);
PInteger(@Result)^ := Index;
Exit(Result);
finally
RTTICtx.Free;
end;
end;
Альтернативные решения
Если использование RTTI кажется слишком сложным, можно рассмотреть альтернативные подходы:
Хранение атрибутов в отдельных структурах данных: Вместо использования атрибутов можно создать отдельную структуру данных, которая будет хранить атрибуты для каждого элемента перечисления. Это может быть массив или список, где каждый элемент содержит значение перечисления и его атрибут.
Использование словарей: Можно использовать словарь (Dictionary) для хранения атрибутов, где ключом будет значение перечисления, а значением — атрибут.
Заключение
Доступ к атрибутам перечислений в Delphi имеет свои ограничения из-за отсутствия RTTI для отдельных элементов. Однако, используя атрибуты, связанные с типом перечисления, и соответствующие функции, можно эффективно работать с этими атрибутами. Альтернативные подходы, такие как использование структур данных или словарей, могут быть полезны в ситуациях, где RTTI не является оптимальным решением.
Контекст описывает методы ассоциации и доступа к атрибутам в перечислениях Delphi, обсуждая ограничения и предлагая различные подходы для решения этой задачи.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.