web-dev-qa-db-fra.com

Comment écrire correctement les instructions Try..Finally..Except?

Prenez le code suivant comme exemple:

procedure TForm1.Button1Click(Sender: TObject);
var
  Obj: TSomeObject;
begin
  Screen.Cursor:= crHourGlass;

  Obj:= TSomeObject.Create;
  try
    // do something
  finally
    Obj.Free;
  end;

  Screen.Cursor:= crDefault;
end;

si une erreur s'est produite dans le // do something section, le TSomeObject qui a été créé je suppose ne sera pas libéré et le Screen.Cursor sera toujours bloqué comme un sablier, parce que le code a été cassé avant d'arriver à ces lignes?

Maintenant, à moins que je ne me trompe, une déclaration d'exception devrait être en place pour faire face à une telle occurrence d'une erreur, quelque chose comme:

procedure TForm1.Button1Click(Sender: TObject);
var
  Obj: TSomeObject;
begin
  try
    Screen.Cursor:= crHourGlass;

    Obj:= TSomeObject.Create;
    try
      // do something
    finally
      Obj.Free;
    end;

    Screen.Cursor:= crDefault;
  except on E: Exception do
  begin
    Obj.Free;
    Screen.Cursor:= crDefault;
    ShowMessage('There was an error: ' + E.Message);
  end;
end;

À moins que je ne fasse quelque chose de vraiment stupide, il ne devrait y avoir aucune raison d'avoir le même code deux fois dans le bloc Final et après, et dans le bloc Exception.

Fondamentalement, j'ai parfois des procédures qui peuvent être similaires au premier échantillon que j'ai publié, et si j'obtiens une erreur, le curseur est bloqué comme un sablier. L'ajout des gestionnaires d'exception aide, mais cela semble une façon sale de le faire - il ignore fondamentalement le bloc Final, sans parler du code laid avec copier-coller des parties Enfin à Exception.

J'apprends encore beaucoup Delphi, donc veuillez m'excuser si cela semble être une question/réponse simple.

Comment le code doit-il être correctement écrit pour gérer les instructions et libérer correctement les objets et capturer les erreurs, etc.?

24
user741875

Vous avez juste besoin de deux blocs try/finally:

Screen.Cursor:= crHourGlass;
try
  Obj:= TSomeObject.Create;
  try
    // do something
  finally
    Obj.Free;
  end;
finally
  Screen.Cursor:= crDefault;
end;

La règle à suivre est que vous devez utiliser finally plutôt que except pour protéger les ressources. Comme vous l'avez observé, si vous essayez de le faire avec except, vous êtes alors obligé d'écrire le code de finalisation deux fois.

Une fois que vous entrez dans le bloc try/finally, Le code de la section finally est garanti pour fonctionner, peu importe ce qui se passe entre try et finally.

Ainsi, dans le code ci-dessus, le try/finally Externe garantit que Screen.Cursor Est restauré face à toutes les exceptions. De même, le try/finally Interne garantit que Obj est détruit en cas de levée d'exceptions au cours de sa durée de vie.


Si vous souhaitez gérer une exception, vous avez besoin d'un bloc try/except Distinct. Cependant, dans la plupart des cas, vous devez pas tenter de gérer les exceptions. Laissez-le simplement se propager jusqu'au gestionnaire d'exceptions de l'application principale qui affichera un message à l'utilisateur.

Si vous gérez l'exception pour descendre la chaîne d'appels, le code appelant ne saura pas que le code qu'il a appelé a échoué.

33
David Heffernan

Votre code d'origine n'est pas aussi mauvais que vous le pensez:

procedure TForm1.Button1Click(Sender: TObject);
var
  Obj: TSomeObject;
begin
  Screen.Cursor := crHourGlass;

  Obj := TSomeObject.Create;
  try
    // do something
  finally
    Obj.Free;
  end;

  Screen.Cursor := crDefault;
end;

Obj.Freesera exécuté, peu importe ce qui se passe lorsque vous // do something. Même si une exception se produit (après try), le bloc finallywill sera exécuté! C'est tout l'intérêt du try..finally construire!

Mais vous souhaitez également restaurer le curseur. La façon la plus pédante est d'utiliser deux try..finally construit:

procedure TForm1.Button1Click(Sender: TObject);
var
  Obj: TSomeObject;
begin

  Screen.Cursor := crHourGlass;
  try
    Obj := TSomeObject.Create;
    try
      // do something
    finally
      Obj.Free;
    end;
  finally
    Screen.Cursor := crDefault;
  end;

end;

[Cependant, cela ne me dérangerait pas non plus

procedure TForm1.Button1Click(Sender: TObject);
var
  Obj: TSomeObject;
begin

  Obj := TSomeObject.Create;
  Screen.Cursor := crHourGlass;      
  try
    // do something
  finally
    Screen.Cursor := crDefault;      
    Obj.Free;
  end;

end;

trop. Le risque de Screen.Cursor := crHourGlass l'échec est assez faible, mais dans un tel cas, l'objet ne sera pas libéré (le finally ne fonctionnera pas parce que vous n'êtes pas à l'intérieur du try), donc le double try..finally est plus sûr.]

17
Andreas Rejbrand

Comme d'autres l'ont expliqué, vous devez protéger le changement de curseur avec try finally bloquer. Pour éviter d'écrire ceux que j'utilise du code comme celui-ci:

unit autoCursor;

interface

uses Controls;

type
  ICursor = interface(IInterface)
  ['{F5B4EB9C-6B74-42A3-B3DC-5068CCCBDA7A}']
  end;

function __SetCursor(const aCursor: TCursor): ICursor;

implementation

uses Forms;

type
  TAutoCursor = class(TInterfacedObject, ICursor)
  private
    FCursor: TCursor;
  public
    constructor Create(const aCursor: TCursor);
    destructor Destroy; override;
  end;

{ TAutoCursor }
constructor TAutoCursor.Create(const aCursor: TCursor);
begin
  inherited Create;
  FCursor := Screen.Cursor;
  Screen.Cursor := aCursor;
end;

destructor TAutoCursor.Destroy;
begin
  Screen.Cursor := FCursor;
  inherited;
end;

function __SetCursor(const aCursor: TCursor): ICursor;
begin
  Result := TAutoCursor.Create(aCursor);
end;

end.

Maintenant vous l'utilisez comme

uses
   autoCursor;

procedure TForm1.Button1Click(Sender: TObject);
var
  Obj: TSomeObject;
begin
  __SetCursor(crHourGlass);

  Obj:= TSomeObject.Create;
  try
    // do something
  finally
    Obj.Free;
  end;
end;

et le mécanisme d'interface compté par référence de Delphi s'occupe de restaurer le curseur.

14
ain

Je le ferais comme ça:

var
  savedCursor: TCursor;
  Obj: TSomeObject;
begin
  savedCursor := Screen.Cursor;
  Screen.Cursor := crHourGlass;
  Obj:= TSomeObject.Create;
  try
    try
      // do something
    except
      // record the exception
    end;
  finally
    if Assigned(Obj) then
      Obj.Free;
    Screen.Cursor := savedCursor;
  end;
end;
3
sacconago

Après avoir fait beaucoup de code dans les services/serveurs qui doit gérer les exceptions et ne pas tuer l'application, je choisis généralement quelque chose comme ceci:

procedure TForm1.Button1Click(Sender: TObject);
var
   Obj: TSomeObject;
begin  
     try
        Obj := NIL;
        try
          Screen.Cursor := crHourGlass;
          Obj := TSomeObject.Create;
          // do something
        finally
          Screen.Cursor := crDefault;
          if assigned(Obj) then FreeAndNil(Obj);
        end;
     except
        On E: Exception do ; // Log the exception
     end;
end;

Notez enfin l'essai; à l'intérieur de l'essai sauf; et le placement de la création Obj.

si l'Obj crée d'autres choses à l'intérieur de son constructeur, il peut fonctionner à mi-chemin et échouer avec une exception à l'intérieur du .create (); mais toujours être un Obj créé. Je m'assure donc que l'Obj est toujours détruit s'il est attribué ...

1
K.Sandell

Je pense que la version la plus "correcte" serait la suivante:

procedure TForm1.Button1Click(Sender: TObject);
var
  Obj: TSomeObject;
begin
  Obj := NIL;
  Screen.Cursor := crHourGlass;
  try
    Obj := TSomeObject.Create;
    // do something
  finally
    Screen.Cursor := crDefault;
    Obj.Free;
  end;
end;
0
dummzeuch