You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi RTTI问题:GetClassAttribute返回空属性值的原因及解决方法

问题原因分析

你的代码出现这个问题的核心原因是TRttiContext的生命周期与返回的TCustomAttribute实例绑定,具体来说:

在GetClassAttribute函数中,你使用了临时创建的TRttiContext.Create来获取属性列表,这个上下文对象在枚举完属性后就会被自动销毁。而在Delphi老版本(比如XE之前)中,通过TRttiType.GetAttributes返回的TCustomAttribute实例是由RTTI上下文管理的——当上下文被销毁后,这些实例的内部数据会失效,导致你访问Description属性时得到空值。

反观GetAttributePropertyValue函数,它在上下文还存活的情况下(循环枚举过程中)就通过RTTI获取了属性值并保存到TValue中,TValue会复制字符串的内容,所以即使上下文销毁,值仍然有效。

另外,你的GetClassAttribute函数直接把返回值Result作为for-in循环变量的写法也有隐患,容易导致对实例生命周期的误判。

正确的解决方法

要解决这个问题,我们需要返回一个独立于RTTI上下文的属性实例副本,而不是直接返回由上下文管理的临时实例。以下是两种可行的方案:

方案1:针对特定属性类创建副本(简单直接)

如果你只需要处理DescriptionAttribute这类特定的属性,可以直接在函数中创建新的实例并复制属性值:

function GetClassAttribute(const AClass: TClass; AAttribute: TClass): TCustomAttribute;
var
  Context: TRttiContext;
  Attr: TCustomAttribute;
begin
  Result := nil;
  Context := TRttiContext.Create;
  try
    for Attr in Context.GetType(AClass).GetAttributes do
    begin
      if Attr.InheritsFrom(AAttribute) then
      begin
        // 针对DescriptionAttribute创建新实例并复制值
        if AAttribute = DescriptionAttribute then
          Result := DescriptionAttribute.Create(GetPropertyValue(Attr, 'Description').AsString)
        else
          // 可扩展处理其他属性类
          raise Exception.CreateFmt('Unsupported attribute type: %s', [AAttribute.ClassName]);
        Break;
      end;
    end;
  finally
    Context.Free;
  end;
end;

方案2:通用的属性实例复制方法(适用于所有属性类)

如果你需要支持任意自定义属性类,可以通过RTTI动态创建实例并复制所有属性值:

function GetClassAttribute(const AClass: TClass; AAttribute: TClass): TCustomAttribute;
var
  Context: TRttiContext;
  Attr: TCustomAttribute;
  AttrType: TRttiType;
  Ctor: TRttiMethod;
  Prop: TRttiProperty;
  ParamValues: TArray<TValue>;
begin
  Result := nil;
  Context := TRttiContext.Create;
  try
    for Attr in Context.GetType(AClass).GetAttributes do
    begin
      if Attr.InheritsFrom(AAttribute) then
      begin
        AttrType := Context.GetType(AAttribute);
        // 获取属性类的构造函数(假设构造函数参数与属性一一对应,或使用无参构造)
        Ctor := AttrType.GetMethod('Create');
        if not Assigned(Ctor) then
          raise Exception.CreateFmt('No constructor found for %s', [AAttribute.ClassName]);

        // 收集构造函数所需的参数值(这里以单参数构造为例,可根据实际扩展)
        SetLength(ParamValues, Ctor.GetParameters.Count);
        for var I := 0 to Ctor.GetParameters.Count - 1 do
        begin
          Prop := AttrType.GetProperty(Ctor.GetParameters[I].Name);
          if Assigned(Prop) then
            ParamValues[I] := Prop.GetValue(Attr);
        end;

        // 创建新实例并返回
        Result := Ctor.Invoke(AAttribute, ParamValues).AsObject as TCustomAttribute;
        Break;
      end;
    end;
  finally
    Context.Free;
  end;
end;

测试验证

修改后,你的测试代码就能正确输出MyClass description了:

var
  oAttr: TCustomAttribute;
begin
  { 正常输出 'MyClass description' }
  WriteLn(GetAttributePropertyValue(TMyClass, DescriptionAttribute, 'Description').AsString);
  { 现在也能正常输出 'MyClass description' }
  oAttr := GetClassAttribute(TMyClass, DescriptionAttribute);
  WriteLn(DescriptionAttribute(oAttr).Description);
  ReadLn;
end.

内容的提问来源于stack exchange,提问作者Branko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:25:29