如何检查一个Delphi类是否声明为抽象类?

6

在Delphi中,是否可以使用RTTI(或其他方法)来检查一个类是否被声明为抽象类? 类似于:

TMyAbstractClass = class abstract(TObject)
  // ...
end;

...

if IsAbstract(TMyAbstractClass.ClassInfo) then
  ShowMessage('Yeah')
else
  ShowMessage('Computer says no...');

这个问题的答案http://stackoverflow.com/questions/791004/how-can-i-detect-if-a-delphi-class-has-a-virtual-constructor可能会有所帮助。 - RobS
2个回答

6
我没有最新的版本可以直接回答你的问题,但请记住,class是抽象的并不重要。它所做的只是让编译器阻止您直接在类上调用构造函数。如果将类引用放入类引用变量中,则编译器将允许您在变量上调用构造函数,在运行时,您将拥有一个据称无法实例化的类的实例。
var
  c: TClass;
  o: TObject;
begin
  c := TMyAbstractClass;
  o := c.Create;
  Assert(o is TMyAbstractClass);
end;

真正重要的是类是否有任何抽象方法。您可以相对容易地检查这一点。查看类的VMT。包含指向System._AbstractError的指针的任何虚拟方法插槽都是抽象方法。棘手的部分是知道要检查多少个虚拟方法插槽,因为没有记录。Allen Bauer演示了如何做到这一点另一个问题的答案中,但在评论中,Mason Wheeler指出它可能会返回比应该更大的值。他提到{{link3:GetVirtualMethodCount}}函数来自{{link4:JCL}},应该给出用户定义虚拟方法的更准确计数。使用该函数和{{link5:GetVirtualMethod}},也来自JCL,我们得到以下函数:

function HasAbstractMethods(c: TClass): Boolean;
var
  i: Integer;
begin
  Result := True;
  for i := 0 to Pred(GetVirtualMethodCount(c)) do
    if GetVirtualMethod(c, i) = @_AbstractError then
      exit;
  Result := False;
end;

如果一个抽象类没有抽象方法,那么它到底有多抽象呢?它必须被标记为抽象以防止开发人员创建其实例,但是如果您真的想要,您仍然可以创建其实例,因此将抽象类标记为抽象实际上更像是一个警告而不是任何实际的使用限制。

不幸的是,当我运行了一些测试时,发现艾伦的算法并不适用于所有情况。我会仔细研究一下,如果我能找出解决方法,我会在那个问题中发布更正内容。 - Mason Wheeler
显然在名称前面可能会放置其他一些东西。不要使用Allen的算法,而是尝试JclSysUtils.GetVirtualMethodCount,它方便地位于与GetVirtualMethod相同的JCL单元中。 - Mason Wheeler
确实。JCL版本检查VMT中的所有其他指针,如果它们中的任何一个出现在第一个虚拟方法和类名之间,它会“缩短”其认为是虚拟方法范围的窗口。 - Rob Kennedy

0
快速浏览TypInfo单元并没有找到任何有用的东西。我认为“抽象类”的概念纯粹是为了编译器的好处。它给了编译器一个规则来执行——不能实例化这个类,只能实例化它的子类——但在运行时并没有真正做任何事情,因此没有必要为其记录任何RTTI。
你为什么要找出这个问题呢,只是出于好奇吗?

我有一个TCollection派生类,可以包含具有共同祖先的不同类类型(TCollectionItem)。具体来说,它是一个网格列集合,包含不同类型的列。现在我想创建一个设计时集合编辑器,允许用户添加已注册的列类型(使用TClassFinder)...但我不希望在此编辑器中出现抽象类。我的抽象类名为TCustom*,因此我正在使用类名进行过滤。 - mrMoo
因此,你的问题不在于如何检测抽象类,而在于如何获取有效的“列类型”列表。简单的解决方案是确保只注册有效的列类型类。首先不要注册抽象类。注册类的唯一原因是可以通过名称搜索并实例化它。不要注册永远不会被实例化的类。你应该发现VCL也没有注册自己的“自定义”基类。 - Rob Kennedy
非常正确,问题在于我只注册“有效”的类,就像你建议的那样,但是Delphi还会自动注册父类,例如如果您使用RegisterClass(TButton)注册TButton,则Delphi也会注册TCustomButton,TButtonControl等等...无论如何,感谢您关于抽象方法的帖子,非常有趣,但我想我的问题的正确答案是“不”。 - mrMoo
“抽象类”的概念为“编译器”提供了一条规则——不能实例化该类,只能实例化其子类。但在Delphi中不存在这样的规则。编译器会愉快地实例化一个抽象类,并且直到运行时调用其中一个抽象方法时才会出现错误。我曾经看到很多人意外地直接实例化TStringsTStream,然后想知道为什么他们的代码会因此崩溃。 - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接