Search code examples
delphidelphi-10.2-tokyo

Delphi how to avoid an uninitialized variable in chain methods


I'm trying to build an API, but I'm stopped at the following problem:

Here is all the code to simulate the problem

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Rtti;

type
  TForm1 = class(TForm)
    btnWorking: TButton;
    btnNotWorking: TButton;
    procedure btnWorkingClick(Sender: TObject);
    procedure btnNotWorkingClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  TEntityManagerWhereExpression = record
    FCondition: Boolean;
  end;

  TEntityManagerWhereExpressionFnc = reference to function(): TEntityManagerWhereExpression;

  TEntityExpressionOperation = record
  private
    FCondition: Boolean;
  public
    class operator Implicit(A: TEntityExpressionOperation): TEntityManagerWhereExpression;
    class operator Equal(A, B: TEntityExpressionOperation): TEntityExpressionOperation;
    class operator BitwiseAnd(A, B: TEntityExpressionOperation): TEntityExpressionOperation;
  end;

  TEntityExpression<T> = record
  private
    FValue: TValue;
  public
    class operator Implicit(A: string): TEntityExpression<T>;
    class operator Implicit(A: TEntityExpression<T>): string;

    class operator Equal(A, B: TEntityExpression<T>): TEntityExpressionOperation;
  end;

  IMyEntity = interface
    ['{BE3EE808-47BB-4E83-94BA-ABC9B3D861F2}']
    function GetNome: TEntityExpression<string>;
    property Nome: TEntityExpression<string> read GetNome;
  end;

  TMyEntity = class(TInterfacedObject, IMyEntity)
    function GetNome: TEntityExpression<string>;
  end;

  IManager = interface
    ['{DE38CC69-069F-48F9-A9A4-E93BA0A87E5F}']
    function produce(out myEntity: IMyEntity): IManager;
    procedure Show(const exp: TEntityManagerWhereExpression);
  end;

  TMyManager = class(TInterfacedObject, IManager)
  public
    function produce(out myEntity: IMyEntity): IManager;
    procedure Show(const exp: TEntityManagerWhereExpression);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.btnNotWorkingClick(Sender: TObject);
var
  my: IMyEntity;
begin
  TMyManager.Create().produce(my).Show(my.Nome = 'Paulo');
end;

procedure TForm1.btnWorkingClick(Sender: TObject);
var
  my: IMyEntity;
  MyManager: IManager;
begin
  MyManager := TMyManager.Create().produce(my) as IManager;
  MyManager.Show(my.Nome = 'Paulo 2');
end;

{ TMyManager }

function TMyManager.produce(out myEntity: IMyEntity): IManager;
begin
  Result := Self;
  myEntity := TMyEntity.Create;
end;

procedure TMyManager.Show(const exp: TEntityManagerWhereExpression);
begin
  if exp.FCondition then
    ShowMessage('true')
  else
    ShowMessage('false');
end;

{ TEntityExpressionOperation }

class operator TEntityExpressionOperation.BitwiseAnd(A, B: TEntityExpressionOperation): TEntityExpressionOperation;
begin
  Result.FCondition := A.FCondition and B.FCondition;
end;

class operator TEntityExpressionOperation.Equal(A, B: TEntityExpressionOperation): TEntityExpressionOperation;
begin
  Result.FCondition := False;
end;

class operator TEntityExpressionOperation.Implicit(A: TEntityExpressionOperation): TEntityManagerWhereExpression;
begin
  Result.FCondition := A.FCondition;
end;

{ TEntityExpression<T> }

class operator TEntityExpression<T>.Equal(A, B: TEntityExpression<T>): TEntityExpressionOperation;
begin
  Result.FCondition := A.FValue.AsString = B.FValue.AsString;
end;

class operator TEntityExpression<T>.Implicit(A: string): TEntityExpression<T>;
begin
  Result.FValue := A;
end;

class operator TEntityExpression<T>.Implicit(A: TEntityExpression<T>): string;
begin
  Result := A.FValue.AsString;
end;

{ TMyEntity }

function TMyEntity.GetNome: TEntityExpression<string>;
begin
  Result := 'Paulo';
end;

end.

The problem is in the code snippet

TMyManager.Create (). Produces (my) .Show (my.Name = 'Paul');

where the variable my was not initialized, since the code snippet

my.name = 'Paulo'

is checked before

produce

the problem does not occur when I call it this way:

var
   my: IMyEntity;
   MyManager: IManager;
begin
   MyManager: = TMyManager.Create (). Produces (my) as IManager;
   MyManager.Show (my.Name = 'Paul 2');

because the variable my is initialized before calling the Show method

how can I prevent the variable my from being used before it is initialized?

Can someone help me solve this problem? thank you so much


Solution

  • The code is still far too complex, but the main problem is that

    TMyManager.Create
    

    produces an object (not an interface reference!) with a reference count of 0. If you cast it to the proper interface type, it will get a reference count of 1:

    (TMyManager.Create as IManager)
    

    So now this works:

    procedure TForm1.btnNotWorkingClick(Sender: TObject);
    var
      my: IMyEntity;
    begin
      (TMyManager.Create as IManager).produce(my).Show(my.Nome = 'Paulo');
    end;
    

    and produces the result 'true'.

    That is why your modified code works. You assign the result of Create to an IManager before you call Show, and produce obviously doesn't change the reference count for the manager, while Show (indirectly) does. I didn't try to find out how -- your code is pretty convoluted.

    Note that Show is not called, nor its parameter evaluated, before produce gets the chance to initialize my. That is not the problem at all.