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.?
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é.
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.Free
sera exécuté, peu importe ce qui se passe lorsque vous // do something
. Même si une exception se produit (après try
), le bloc finally
will 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.]
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.
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;
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é ...
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;