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
相关产品推荐
相关产品推荐

