Duck typing in Delphi 2007?

前端 未结 3 1829
你的背包
你的背包 2020-12-05 16:02

Question:

Is there a way to do duck typing with Delphi 2007 (i.e. without generics and advanced Rtti features)?


Duck typing Re

相关标签:
3条回答
  • 2020-12-05 16:42

    Quick answer:

    Not in a meaningful way

    Longer answer: According to the wiki page "Duck Typing" is identified by:

    In duck typing, one is concerned with just those aspects of an object that are used, rather than with the type of the object itself. For example, in a non-duck-typed language, one can create a function that takes an object of type Duck and calls that object's walk and quack methods. In a duck-typed language, the equivalent function would take an object of any type and call that object's walk and quack methods. If the object does not have the methods that are called then the function signals a run-time error.

    The equivalent Delphi non-compilable code would look like this:

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

    I intentionally did not specify a type for D because that would defeat the purpose. Delphi is statically typed so this would never work. If you need this for some small functionality you can use Interfaces or RTTI and get something like this:

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

    If you can get RTTI:

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

    I have personally used the RTTI method to identify (and manipulate) list objects in a way that makes the code work with both TList descendants and generic TList<T> variants.

    The take-away from this should be: Even with advanced functionality in the newest versions of Delphi (generics and comprehensive RTTI) you'll only get close to Duck typing for limited functionality and with significant effort. This is simply not in the DNA of Delphi (because Delphi's DNA says "static typing"), but you might be able to get something close enough, and with a lot of effort, and only for specific functionality. Maybe if you give us an idea of what specific functionality you'd like, we'd be able to figure something out.

    0 讨论(0)
  • 2020-12-05 16:44

    Here's an idea that requires you create a type library.

    Use OLE Automation types, and implement Dispatch Interfaces (dual COM objects).

    Now you Now you can write whatever you want after that type, and we'll be finding out at runtime whether or not it works, or blows up. Welcome to dynamic typing.

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

    I consider it ugly, but others might not.

    0 讨论(0)
  • 2020-12-05 16:46

    With the help of the ObjAuto.pas and invokable variant types it should be possible (written in XE but should also run in Delphi 7 or lower):

    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.
    

    You can simply use it like this:

    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;
    
    0 讨论(0)
提交回复
热议问题