Search code examples
delphidictionarymemory-leakstdictionary

Delphi dictionary freeing


I implemented the following class:

type
  TUtilProcedure = procedure(var AJsonValue: TJSONObject);

  TCallback = class
  private
    FName: string;
    FProcedure: TUtilProcedure;
    FAnnotation: string;
  public
    constructor Create(AName: string; AProcedure: TUtilProcedure; AAnnotation: string); overload;
    constructor Create(ACallback: TCallback); overload;
    property Name: string read FName;
    property Proc: TUtilProcedure read FProcedure;
    property Annotation: string read FAnnotation;
 end;

Then I have a global variable:

procedures: TDictionary<string, TCallback>;

In OnFormActivate procedure I initalize the procedures variable:

procedures := TDictionary<string, TCallback>.Create();
procedures.Add('something', TCallback.Create('sth', @proc, 'annotation')); 
// ....

And then in OnFormClose I free it:

procedures.Clear;
procedures.Free;

Does my code leak memory? If so what is the correct way to free the dictionary? From what I know iteration is not good idea.


Solution

  • The code leaks memory because the objects contained in a TDictionary are not automatically freed.

    If you need to store objects in a dictionary, the adoption of a TObjectDictionary represents a better approach.

    If you want the objects contained in the dictionary to be automatically freed, use the doOwnsValues flag while creating the instance of the collection.

    • When the variable is really global (i.e. is declared under var in the interface section of the unit), it should be created and destroyed in the initialization and finalization section of the unit itself.

      . . .
      var
        procedures: TObjectDictionary<string, TCallback>;
      . . .
      initialization
        procedures:= TObjectDictionary<string, TCallback>.Create([doOwnsValues]);
      finalization
        procedures.Free;
      
    • When your variable belongs to the form class itself, you should create the dictionary in the form's OnCreate event.

      . . .
      public
        procedures: TObjectDictionary<string, TCallback>;
      . . .
      procedure TForm1.FormCreate(Sender: TObject);
      begin
        procedures:= TObjectDictionary<string, TCallback>.Create([doOwnsValues]);
      end;
      

      Free the dictionary in the form's OnDestroy event:

      procedure TForm1.FormDestroy(Sender: TObject);
      begin
        procedures.Free;
      end;
      
    • Furthermore, if you want the variable belonging to the class to be accessed without the need of an instance of the class itself (this is referred as a static variable in many programming languages), you can declare the dictionary as a class var and optionally access it through a class property; in such a case, it's better to create and destroy the collection in the class constructor and in the class destructor.

      . . .
      TMyClass = class
        private
          class constructor Create;
          class destructor Destoy;
        public
          class var procedures: TObjectDictionary<string, TCallback>;
      end;
      . . .
      class constructor TMyClass.Create;
      begin
        procedures := TObjectDictionary<string, TCallback>.Create([doOwnsValues]);
      end;
      
      class destructor TMyClass.Destoy;
      begin
        procedures.Free;
      end;
      

    TCallback = class
      private
        FName: string;
        FProcedure: TUtilProcedure;
        FAnnotation: string;
      public
        constructor Create(AName: string; AProcedure: TUtilProcedure; AAnnotation: string); overload;
        constructor Create(ACallback: TCallback); overload;
        property Name: string read FName;
        property Proc: TUtilProcedure read FProcedure;
        property Annotation: string read FAnnotation;
    end;
    

    As a side note, the TCallback class does not need to specify a destructor because only owns two strings and a pointer to a procedure. And so the default destructor inherited from TObject suffices.