11

问题:

有没有办法用 Delphi 2007 做鸭子打字(即没有泛型和高级 Rtti 功能)?


Delphi 2010 以后的鸭子打字资源:

最后编辑:

我已经深入研究了上面列出的资源,并在这里研究了每个发布的答案。

我最终完善了我的要求,并对这个问题进行了跟进。

4

3 回答 3

10

在 ObjAuto.pas 和可调用变体类型的帮助下,它应该是可能的(用 XE 编写,但也应该在 Delphi 7 或更低版本中运行):

unit DuckTyping;

interface

function Duck(Instance: TObject): Variant;

implementation

uses
  ObjAuto,
  SysUtils,
  TypInfo,
  Variants;

type
  TDuckVarData = packed record
    VType: TVarType;
    Reserved1, Reserved2, Reserved3: Word;
    VDuck: TObject;
    Reserved4: LongWord;
  end;

  TDuckVariantType = class(TPublishableVariantType)
  protected
    function GetInstance(const V: TVarData): TObject; override;
  public
    procedure Clear(var V: TVarData); override;
    procedure Copy(var Dest: TVarData; const Source: TVarData;
      const Indirect: Boolean); override;
    function DoFunction(var Dest: TVarData; const V: TVarData;
      const Name: string; const Arguments: TVarDataArray): Boolean; override;
  end;

var
  DuckVariantType: TDuckVariantType;

{ TDuckVariantType }

procedure TDuckVariantType.Clear(var V: TVarData);
begin
  V.VType := varEmpty;
  TDuckVarData(V).VDuck := nil;
end;

procedure TDuckVariantType.Copy(var Dest: TVarData; const Source: TVarData;
  const Indirect: Boolean);
begin
  if Indirect and VarDataIsByRef(Source) then
    VarDataCopyNoInd(Dest, Source)
  else
  begin
    with TDuckVarData(Dest) do
    begin
      VType := VarType;
      VDuck := TDuckVarData(Source).VDuck;
    end;
  end;
end;

function TDuckVariantType.DoFunction(var Dest: TVarData; const V: TVarData;
  const Name: string; const Arguments: TVarDataArray): Boolean;
var
  instance: TObject;
  methodInfo: PMethodInfoHeader;
  paramIndexes: array of Integer;
  params: array of Variant;
  i: Integer;
  ReturnValue: Variant;
begin
  instance := GetInstance(V);
  methodInfo := GetMethodInfo(instance, ShortString(Name));
  Result := Assigned(methodInfo);
  if Result then
  begin
    SetLength(paramIndexes, Length(Arguments));
    SetLength(params, Length(Arguments));
    for i := Low(Arguments) to High(Arguments) do
    begin
      paramIndexes[i] := i + 1;
      params[i] := Variant(Arguments[i]);
    end;

    ReturnValue := ObjectInvoke(instance, methodInfo, paramIndexes, params);
    if not VarIsEmpty(ReturnValue) then
      VarCopy(Variant(Dest), ReturnValue);
  end
  else
  begin
    VarClear(Variant(Dest));
  end;
end;

function TDuckVariantType.GetInstance(const V: TVarData): TObject;
begin
  Result := TDuckVarData(V).VDuck;
end;

function Duck(Instance: TObject): Variant;
begin
  TDuckVarData(Result).VType := DuckVariantType.VarType;
  TDuckVarData(Result).VDuck := Instance;
end;

initialization
  DuckVariantType := TDuckVariantType.Create;

finalization
  FreeAndNil(DuckVariantType);

end.

您可以像这样简单地使用它:

type
  {$METHODINFO ON}
  TDuck = class
  public // works in XE, not sure if it needs to be published in older versions
    procedure Quack;
  end;

procedure TDuck.Quack;
begin
  ShowMessage('Quack');
end;

procedure DoSomething(D: Variant);
begin
  D.Quack;
end;

var
  d: TDuck;
begin
  d := TDuck.Create;
  try
    DoSomething(Duck(d));
  finally
    d.Free;
  end;
end;
于 2012-02-29T15:28:20.250 回答
6

快速回答:

不是以有意义的方式

更长的答案:根据wiki 页面, “Duck Typing”由以下内容标识:

在鸭子类型中,人们只关心对象的那些被使用的方面,而不是对象本身的类型。例如,在非 Duck 类型语言中,可以创建一个函数,该函数接受 Duck 类型的对象并调用该对象的 walk 和 quack 方法。在鸭子类型语言中,等效函数将获取任何类型的对象并调用该对象的 walk 和 quack 方法。如果对象没有被调用的方法,则该函数会发出运行时错误信号。

等效的 Delphi 不可编译代码如下所示:

procedure DoSomething(D);
begin
  D.Quack;
end;

我故意没有指定类型,D因为这会破坏目的。Delphi 是静态类型的,所以这永远不会工作。如果你需要一些小功能,你可以使用InterfacesRTTI得到这样的东西:

procedure DoSomething(D:TObject);
begin
  (D as ISomeIntf).Quack;
end;

如果您可以获得 RTTI:

procedure DoSomething(D:TObject);
begin
  CallQuackUsingRTTI(D);
end;

我个人使用该RTTI方法来识别(和操作)列表对象,使代码同时适用于TList后代和通用TList<T>变体。

从中得出的结论应该是:即使在最新版本的 Delphi 中具有高级功能(泛型和综合 RTTI),您也只能在有限的功能和大量努力下接近 Duck 类型。这根本不在 Delphi 的 DNA 中(因为 Delphi 的 DNA 说“静态类型”),但是您可能能够获得足够接近的东西,并且付出很多努力,并且仅用于特定功能。也许如果你告诉我们你想要什么特定的功能,我们就能想出办法。

于 2012-02-29T11:46:52.500 回答
4

这是一个需要您创建类型库的想法。

使用 OLE 自动化类型,并实现调度接口(双 COM 对象)。

现在你现在你可以在那个类型之后写任何你想要的东西,我们将在运行时找出它是否有效,或者是否崩溃。欢迎使用动态类型。

procedure DoSomething(D:OleVariant);
begin
  D.Quack; // Might work, might blow up.
end;

我认为它丑陋,但其他人可能不会。

于 2012-02-29T12:18:21.793 回答