Is it safe to remove and reassign events this way? If not, why? - delphi

A.Event1 := nil;
A.Event2 := nil;
try
...
finally
A.Event1 := MyEvent1;
A.Event2 := MyEvent2;
end;
Can something go wrong with it?
EDIT:
I've accepted Barry's answer because it answered exactly what I asked, but Vegar's answer is also correct depending on the scenario, sadly I can't accept both.

This sounds like an event-nightmare I have seen before :-)
Instead of removing the events, I usually set a flag that I check in the event. I often use a integer rather than boolean so that the same flag can be set multiple times in one processing.
Something like this:
procedure TMyObject.Traverse;
begin
inc(FTraverseFlag);
try
...
finally
dec(FTracerseFlag);
end;
end;
procedure TMyObject.OnBefore( ... );
begin
if FTraverseFlag > 0 then
exit;
...
end;
I guess this easily could be made thread-safe to solve Barrys concerns.

It entirely depends on what happens in the bit of code marked '...'. If it e.g. starts up a background thread and tries to invoke Event1 or Event2 after execution has continued into the finally block, you may get unexpected results.
If the code is entirely single-threaded, then yes, neither Event1 nor Event2 should be triggered while the code is between the try and finally.
However, that does assume that Event1 and Event2 properties (all Delphi events are properties of a method pointer type) do not do unusual things in their setters. A pathologically malicious event setter could squirrel away a method pointer, and still be able to invoke it, even after you later call the setter with 'nil' as the value.
But that would be highly unusual behaviour.

As Barry said, the only real concern is with multithreaded concerns - other than that is perfectly normal. As VCL events setters just assign the event, nothing need to be worried.

If it is multithreaded you need to make operation atomic. Disabling several event handlers opens possibility that they are running at the moment then they are being disabled. Or Event2 starts when Event1 is being set to nil. All kinds of malfunctions may happen if these events called often. Possible solution to use semaphores instead. But then you may need to add extra code to event handlers as well.
Any way setting event handler temporarily to nil looks like a bad design. It will be incomprehensible very soon and hard to make do what it have to do any way. So you’d better spend some time and develop something more sophisticated.

Related

Empty Functions - Placeholder?

so I was just looking through the Code of our inventory management system
and I saw some Snippet's of one of my Coworkers, all he does in some Functions is
simply open them and insert a command in there for example
procedure TWerkStF.TBBtnStatiClick(Sender: TObject);
begin
inherited;
//
end;
so i am wondering when you should do something like that and when is it usefull, are there some benefits?
This is not useful, and has no benefits. Such a function can, and should, be removed.
The function in the question, by using inherited, simply searches in the super classes for a function of the same name, and if one is found calls it. If one is not found then no action is performed. As a rule, such a function, one that only calls inherited, does not modify the behaviour of the program. You can remove it without changing behaviour.
We use such functions/procedures most times to tell other Co-Workers where they should not change anything, the reason for that is because some events are just not fitting the needs of some Modules, therefore we "warn" each other with empty Functions.
Sounds wiered, i know.

What is the correct way to dynamically create/release runtime forms?

I always try to create my Applications with memory usage in mind, if you dont need it then don't create it is the way I look at it.
Anyway, take the following as an example:
Form2:= TForm2.Create(nil);
try
Form2.ShowModal;
finally
Form2.FreeOnRelease;
end;
I actually think Form2.Destroy is probably the better option, which brings me to my question..
What is the difference between calling:
Form2.Destroy;
Form2.Free;
Form2.FreeOnRelease;
They all do the same or similar job, unless I am missing something.
And also when should any of the above be used? Obviously when freeing an Object I understand that, but in some situations is Destroy better suited than Free for example?
Form2:= TForm2.Create(nil);
This is a code-smell, because Form2 is probably the global, IDE-generated variable that would normally hold an IDE-created TForm2. You most likely want to use a local variable, and one with a better name. This is not necessary an error, just a code-smell.
Form2.Destroy vs Form2.Free
Use Form2.Free, because it calls Destroy anyway. You can CTRL+Click on the name (Free) to see it's implementation. Essentially Free calls Destroy if Self is not nil.
Form2.FreeOnRelease
As the documentation says, "It should not be necessary to call FreeOnRelease directly."
I've never actually heard of FreeOnRelease before. A quick Google search turned up the reason why. From the official documentation:
FreeOnRelease is called when an
interface implemented by the component
is released. FreeOnRelease is used
internally and calls the corresponding
interface method. It should not be
necessary to call FreeOnRelease
directly.
As for Free vs. Destroy, Free is a safety feature. It's basically implemented as if self <> nil then self.Destroy;, and it was created to make constructors and destructors safe to use. Here's the basic idea:
If you're constructing an object and an unhandled exception is raised, the destructor gets called. If your object contains other objects, they may or may not have been created yet by the time the error occurred, so you can't just try to call Destroy on all of them. But you need a way to make sure that the ones that have been created do get destroyed.
Since Delphi zeros out the address space of an object before calling the constructor, anything that hasn't been created yet is guaranteed to be nil at this point. So you could say if FSubObject <> nil then FSubObject.Destroy again and again for all the sub-objects, (and if you forget that you're going to get access violations,) or you can use the Free method, which does it for you. (This is a huge improvement over C++, where the memory space is not zeroed before the constructor is called, which requires you to wrap all your sub-objects in smart pointers and use RAII to maintain exception safety!)
It's useful in other places as well, and there's really no reason not to use it. I've never noticed that Free imposes any measurable performance penalty, and it improves the safety of your code, so it's a good idea to use it in all cases.
Having said that, when dealing with forms specifically, there's an additional variable to factor into the equation: the Windows message queue. You don't know if there are still pending messages for the form you're about to free, so it's not always safe to call Free on a form. For that, there's the Release method. It posts a message to the queue that causes the form to free itself once it's got no more messages to handle, so it's generally the best way to free a form you no longer need.
The canonical form is:
Form := TMyForm.Create(nil);
try
Form.ShowModal;
finally
Form.Free;
end;
Never call Destroy, always call Free instead.
FreeOnRelease is a total red herring. Sometimes, if there are queued messages destined for your form or its children, then you might elect to call Release although often that's indicative of design problems.
The idiomatic usage is
procedure SomeProc;
var
frm: TForm2;
begin
frm := TForm2.Create(nil);
try
frm.ShowModal;
finally
frm.Free;
end;
end;
or, unless you hate the with construct,
with TForm2.Create(nil) do
try
ShowModal;
finally
Free;
end;
You should never call Destroy, according to the documentation. In fact, Free is exactly equivalent to if Self <> nil then Destroy;. That is, it is a 'safe' version of Destroy. It doesn't crash totally if the pointer happens to be nil. [To test this, add a private field FBitmap: TBitmap to your form class, and then OnCreate (for instance), try FBitmap.Free vs. FBitmap.Destroy.]
If you create the form using the approach above, Free is perfectly safe, unless you do some strange things in the form class.
However, if you use CreateForm(TForm2, Form2) to create the form and store the form object in the global instance variable Form2 and you don't free it immediately [for instance, if you want the window to stick around next to the main form in a non-modal way for a few minutes], you should probably use Release instead of Free. From the documentation,
Release does not destroy the form
until all event handlers of the form
and event handlers of components on
the form have finished executing.
Release also guarantees that all
messages in the form's event queue are
processed before the form is released.
Any event handlers for the form or its
children should use Release instead of
Free (Delphi) or delete (C++). Failing
to do so can cause a memory access
error.
FreeOnRelease has nothing in particular do to with forms. From the docs:
It should not be necessary to call
FreeOnRelease directly.
the other way is passing caFree to Action of formonclose
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end

How do I check if form is Closed?

The only way I see is to add flag for this, but is this the best way?
When the form is destroyed and I check if(Assigned(form2)) the result is true? Why?
What is the way to do this?
You can use Form1.Showing to see if a form is closed or not.
Just closing a form does not free it unless you set Action := caFree in OnClose event. Default is caHide.
Wow, a blast from the past :)
The way that Assigned() works, is that it basically does nil check on the pointer. If you destroy form2, there will still be a memory address that form2 points to.
I has been a very long time since I've done any Delphi, but from memory, you need to manually set the form2 var to nil when it is destroyed. If you have a central place (eg. a form broker?) where you create & destroy forms, this should be quite easy.
If you use Form1.Free or Form1.Destroy, Delphi will destroy the object but wont set the object reference to nil. So instead use FreeAndNil.
For more information, check Andreas Rejbrand answer in this link
Faced with the same issue when doing some routine on closing application. In this case all forms are destroyed behind the stage but pointers are not set to nil. This code helphs me:
procedure TMyForm.FormDestroy(Sender: TObject);
begin
MyForm:=nil;
end;
So pointer becomes nil and I can check it with Assigned or compare to nil.
Just as a tip, the correct way to do it in some special cases is to create a timer that do the nil assign to the variable.
I will explain it (somehow it is complex), if you create your form within your own code MyForm:=TMyForm.Create and you have a MyFrom.Close it is very easy, just add a MyForm:=nil or also better MyForm.FreeAndNil... but sometimes the reference is not anyware.
Sample: You create inside a procedure, on a loop a lot of copies of the same form (or just one), let the form open and end that procedure, now the reference to the opened form is nowhere, so you can not assign nil or do a freeandnil, etc., in a normal way.
For that cases, the trick is to use a timer (of just one milisecond) that do it, that timer needs the reference, so you must store on a global like the reference to Self, all that can be done on the on close event.
The easiest way to do the free (when no reference anywhere) is to create a TObjectList on the main form, so it will hold all form references that needs to be free, and define a timer (one milisecond) that will go through that list doing the freeandnil; then on the onlcose you add Self to that list and enable that timer.
Now the other part, you have a normal form that is auto created on start, but you need to set it to nil and re-create it on your own code.
That case has a global that point to that form, so you only need to free and nil it, but NOT (i say it loud) on any part inside the own form code, you must do it OUT (i say if loud) of the form code.
Some times you will need to free the form, when user close it, and it is not shown in modal, this case is complex, but again the same trick is valid, on the onclose event you enable a timer (that is out of that form, normally on main form) adn that timer will free and nil. That timer interval can be set as just one milisecond, it will not be run until form has totally closed (please have in mind not using Application.ProcessMessages, that is normally a really bad idea).
If you set Self to nil, free or whatever inside the own form, you can corrupt your application memory (doing that is totally unsafe, not to mention it can eat ram).
The only way to free a form (and nil its reference), a form that is not shown as modal and is the user who close it, is to program a trigger that do that after the form is totally closed.
I know about setting action to do the free, but to set it to nil there is no other safe way.
Must say: If you use timers on your main form, run a Enabled:=False on all of them on the Onclose event... otherwise weird things may occur (not allways, but sometimes... race conditions about destroying application and running code on that timers), and of couse if some one was enabled act correctly to terminate it correctly or abort it, etc.
Your question is one of the complex things to do... free and nil a form that is closed not by code, but by user action.
For all the rest: Think like if the application has at the same time a lot of forms opened and all can interact with the user at the same time (anyone is modal), and you have code that references some of them from the others... you need to know f user has closed any form to avoid accesing that form from code. This is not trivial to be done unless you use timers.
If you have a 'central' form (like an MDI application) you can put that timer on the main MDI form, so any child form that is closed can be freed and nil, the trick is again a timer on that main form.
Only and only if you are sure you can free and nil all non visible forms, you can have a timer on the main form that goes through all forms and if Visible is false, then call FreeAndNil, i consider this way prone to errors, since if you add on a future a form that must not be freed but can stay hidden... this code will not be valid.
Allways remember that if is the user the onw who closes the form that must be freed and nil, there is no way on code to detect and act, no event is launched (after the form is totally closed) and before the form is totally closed you must not even try to free it or nil its reference, weird things can occur (more prone to that if motherboard has more than one socket, also more if your app uses threads, etc).
So, for threaded apps (and also not threaded) i use another method that works great and do not need timers, but need double checking prior to each ThatForm.*, the trick is to define a Form bolean public variable like as PleaseFreeAndNilMe on the public section on the form, then on the onclose (as last line) set it to True and on the OnCreate set it as False.
That way you will know if that form had been closed or only hidden (to hide a form never call close, just call hide).
So coded will look like (you can use this as a warper, instead of defining forms as TForm define them as TMyform, or also better, use a hack like type TForm=class(Forms.TForm) instead of TMyForm=class(TForm) just to have that variable added to all forms):
TMyForm=class(TForm)
...
public
PleaseFreeAndNilMe:=Boolean;
...
procedure TMyForm.FormCreate(Sender: TObject);
begin
PleaseFreeAndNilMe:=False;
...
end;
procedure TMyForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
...
PleaseFreeAndNilMe:=True;
end;
If you preffer hacked version:
TForm=class(Froms.TForm)
public
PleaseFreeAndNilMe:=Boolean;
end;
procedure TForm.FormCreate(Sender:TObject);
begin
inherited Create(Sender);
PleaseFreeAndNilMe:=False;
end;
procedure TForm.FormClose(Sender:TObject;var Action:TCloseAction);
begin
PleaseFreeAndNilMe:=True;
inherited FormClose(Sender,Action);
end;
But as i said, prior to access any member (or just where you do the nil compare) just call a 'global' function passign the reference (no matter if it was nil or not), coded as:
function IsNilTheForm(var TheForm: TMyForm);
begin
if nil=TheForm
then begin // The form was freed and nil
IsNilTheForm:=True; // Return value
end
else begin // The form refence is not nil, but we do not know is it has been freed or not
try
if TheForm.PleaseFreeAndNilMe
then begin // The form is not freed but wants to
try
TheForm.Free;
except
end;
try
TheForm:=Nil;
except
end;
IsNilTheForm:=True; // Return value
end
else begin // The form is not nil, not freed and do not want to be freed
IsNilTheForm:=False; // Return value
end;
except // The form was freed but not set to nil
TheForm:=Nil; // Set it to nil since it had beed freed
IsNilTheForm:=True; // Return value
end;
end;
end;
So where you do if nil=MyForm then ... you can now do if (IsNilTheForm(MyForm)) then ....
That is it.
It is better the timer solution, since form is freed as soon as possible (less ram used), with the PleaseFreeAndNilMe trick the form is not freed until IsNilTheForm is called (if you do not free it any where else).
That IsNilTheForm is so complex because it is considering all states (for a multi socket motherboard and threaded apps) and letting the code free / nil it anywhere else.
Of course, that function must be called on main thread and in atomic exclusion.
Free a Form and Nil its pointer is not a trivial thing, most when user can close it at any time (since no code out of the form is fired).
The big problem is: When a user closes a form there is no way to have a event handler that is triggered out of that form and after the form ends all things it is doing.
Imaigne now that the coder has put a lot of Application.ProcessMessages; every where on the app, also on that form, etc... and has not taken the care for race conditions... try to free and nil such a form after the user asks it to be closed... this is a nightmare, but can be solved with the hacked version of TForm that has a variable that tells that the form has not been freed but wants it.
Now imagine you use hacked TForm and want a normal TForm, just define it as ...= class(Forms.TForm), that way it will now have that extra variable., so calling IsNilTheForm will act as comparing to nil.
Hope this helps VCL coders to FIX such things, like raising an event when an object is destroyed, freed, niled, hide, etc... out of the code of that object, like on main form, etc. That would make live easier... or just fix it... Close and Free implies set to Nil all refences that point to it.
There is another thing that can be done (but i try to allways avoid it): Have multiple variables that point to the exact same form (not to copies ot it), that is prone to a lot of errors, you free one and need to nil all of them, etc... The code i show is also compatible with that.
I know the code is comples... but Free and Nil a form is more complex than my code.

Does "Long running method is done" have a design pattern?

I have got a method that takes a long time to complete and want to check regularly whether it is done. This is what I have come up with (simplified code, Delphi 2007):
type
IWaitForDone = interface
function IsDone: boolean;
end;
function TSomeClass.doSomethingThatTakesLong: IWaitForDone;
begin
Result := TClassThatDoesIt.Create;
end;
var
Waiter: IWaitForDone;
begin
Waiter := SomeClass.doSomethingThatTakesLong;
while not Waiter.isDone do
doSomethingElse;
Waiter := nil;
end;
In the context it is possible that calling isDone actually does a part of what is to be done and returns true, when finished and false while there are still parts to be done. Alternatively it could just check whether another thread is done with its work. I don't want this to be visible to the caller.
I guess that I am not the first one to come across this type of problem and this solution probably already has got a name (a design pattern?), but I could not find it.
So what is it called?
Microsoft suggests two patterns for long running methods (invoke asynchronously and rendezvous according to pattern)
Event-based Asynchronous Pattern Overview
Calling Asynchronous Methods Using IAsyncResult
Not sure if that helps/suits you, but take a look at AsyncCalls unit.
I've seen this referred to as a 'Future' before.
Here are a couple of links:
This one is by Oren Eini who is a prolific .Net developer. As an aside - it is well worth reading his blog (where this link is from) if you are interested in coding pattterns, best practices etc...
Futures post from Oren Eini (Ayende#Rahien)
And this link is yours but I thought I'd just update the answer with it to be complete.
Futures link from uni-sb.de
I'm not sure if I understand your problem correctly, but I don't like the idea that part of the actual work is done in the IsDone function (funny sentence). I would expect a method like that to just check some flag and return quickly. What if I decide not to continously call IsDone but to perform some other work and check IsDone after that? Then, no work has been done until the first call - not really what I would expect from an asynchronous call...
I didn't really answer your question, but I doubt that this is a widely used design pattern.

Is Delphi "with" keyword a bad practice?

I been reading bad things about the with keyword in delphi but, in my opinion, if you don't over use it. It can make your code look simple.
I often put all my TClientDataSets and TFields in TDataModules. So in my forms I had code like this
procedure TMyForm.AddButtonClick(Sender: TObject);
begin
with LongNameDataModule do
begin
LongNameTable1.Insert;
LongNameTable1_Field1.Value := "some value";
LongNameTable1_Field2.Value := LongNameTable2_LongNameField1.Value;
LongNameTable1_Field3.Value := LongNameTable3_LongNameField1.Value;
LongNameTable1_Field4.Value := LongNameTable4_LongNameField1.Value;
LongNameTable1.Post;
end
end;
without the with keyword I have to write the code like this
procedure TMyForm.AddButtonClick(Sender: TObject);
begin
LongNameDataModule.LongNameTable1.Insert;
LongNameDataModule.LongNameTable1_LongNameField1.Value := "some value";
LongNameDataModule.LongNameTable1_LongNameField2.Value :=
LongNameDataModule.LongNameTable2_LongNameField1.Value;
LongNameDataModule.LongNameTable1_LongNameField3.Value :=
LongNameDataModule.LongNameTable3_LongNameField1.Value;
LongNameDataModule.LongNameTable1_LongNameField4.Value :=
LongNameDataModule.LongNameTable4_LongNameField1.Value;
LongNameDataModule.LongNameTable1.Post;
end;
I think is easier to read using the with keyword.
Should I avoid using the with keyword?
The biggest danger of with, outside of pathological conditions like "with A, B, C, D" is that your code can silently change meaning with no notice to you. Consider this example:
with TFoo.Create
try
Bar := Baz;
DoSomething();
finally
Free;
end;
You write this code knowing that Bar is a property of TFoo, and Baz is a property of the type containing the method which has this code.
Now, two years later, some well-meaning developer comes in adds a Baz property to TFoo. Your code has silently changed meaning. The compiler won't complain, but the code is now broken.
The with keyword is a nice feature for making your code more readable but there are some pitfalls.
Debugging:
When using code like this:
with TMyClass.Create do
try
Add('foo');
finally
Free;
end;
There is no way to inspect the properties of this class, so always declare a variable and use the with keyword on that.
Interfaces:
When creating an interface in the with clause it lives till the end of your method:
procedure MemoryHog;
begin
with GetInterfaceThatTakes50MBOfMemory do
Whatever;
ShowMessage('I''m still using 50MB of memory!');
end;
Clarity
When using a class in a with clause that has properties or method names that already exists within the scope, it can fool you easily.
with TMyForm.Create do
Width := Width + 2; //which width in this with is width?
Of course when having duplicate names, you're using the properties and methods of the class declared in your with statement (TMyForm).
The with statement has its place but I have to agree that overuse can lead to ambiguous code. A good rule of thumb is to make sure the code is "more" readable and maintainable after adding the with statement. If you feel you need to add comments to explain the code after adding the statement then it is probably a bad idea. If the code is more readable as in your example then use it.
btw: this was always one of my favorite patterns in Delphi for showing a modal window
with TForm.Create(nil) do
try
ShowModal;
finally
Free;
end
I tend towards baning the with-statement altogether. As previously stated, it can make things complicated, and my experience is that it will. Many times the debugger want evaluate values because of withs, and all to often I find nested withs that lead to code that hard to read.
Brian's code seems readable and nice, but the code would be shorter if you just typecast the sender directly, and you remove all doubt about that component you enable:
TAction(Sender).Enabled := Something;
If you are concerned about typing to much, I prefare to make a temporary referance to the long-named object:
var
t: TTable;
begin
t := theLongNamedDataModule.WithItsLongNamedTable;
t.FieldByName(' ');
end;
I can't se why typing should bother you, though. We Are Typists First, Programmers Second, and code-completion, copy-paste and key-recording can help you be a more effective typist.
update:
Just stumbled over an long article with a little section on with-statements: he with keyword. The most hideous, dangerous, blow-your-own-feet-off feature in the language. :-)
When I first began pascal programming (with TurboPascal!) and learnt as I went, WITH seemed wonderful. As you say, the answer to tedious typing and ideal for those long records. Since Delphi arrived, I've been removing it and encouraging other to drop it - neatly summed-up by Verity at the register
Apart from a reduction in readability there are two main reasons why I'd avoid it:
If you use a class then you dont need it anyway - only records 'seem' to benefit from it.
Using the debugger to follow the code to the declaration with Ctrl-Enter doesnt work.
That said, for readability I still use the syntax:
procedure ActionOnUpdate( Sender : TObject )
begin
With Sender as TAction do
Enabled := Something
end;
I've not seen a better construct.
Your example, of a datamodule access within a button click, is a poorly contrived example in my opinion. The whole need for WITH goes away if you move this code into the data module where it should be. The OnClick then just calls LongNameDataModule.InsertStuff and there is no with needed.
With is a poor device, and you should look at your code to see why you are needing it. You probably did something wrong, or could do it a better way.
As Vegar mentioned, it's just as neat and much more readable, easier to debug, and less prone to stealth issues to use a temporary reference.
So far, I have never found a need to use with. I used to be ambivalent over it, until I took over a project that used the mind bending double with frequently. Questioning whether the original developer intended to reference items in the first with or second, if that ambiguous reference was a with-slip or clumsy code, the torment of trying to debug it, and the knock on effects of extending or modifying classes that use these abominations is just not worth anyone's time.
Explicit code is simply more readable. This way you can have your cake and enjoy eating it.
procedure TMyForm.AddButtonClick(Sender: TObject);
var
dm: TLongNameDataModuleType
begin
dm:=LongNameDataModule;
dm.LongNameTable1.Insert;
dm.LongNameTable1_Field1.Value := "some value";
dm.LongNameTable1_Field2.Value := LongNameTable2_LongNameField1.Value;
dm.LongNameTable1_Field3.Value := LongNameTable3_LongNameField1.Value;
dm.LongNameTable1_Field4.Value := LongNameTable4_LongNameField1.Value;
dm.LongNameTable1.Post;
end;
I'm a firm believer of removing WITH support in Delphi. Your example usage of using a datamodule with named fields is about the only instance I could see it working out. Otherwise the best argument against it was given by Craig Stuntz - which I voted up.
I just like to point out that over time you may eventually (should) rmeove all coding in OnClick events and your code will also eventually migrate away from named fields on datamodules into using classes that wrap this data and the reason to use WITH will go away.
Your question is an excellent example of 'a hammer is not always the solution'.
In this case, 'with' is not your solution: You should move this business logic out of your form into your datamodule.
Not doing so violates Law of Demeter like mghie (Michael Hieke) already commented.
Maybe your example was just illustrative, but if you are actually using code like that in your projects, this is what you should do in stead:
procedure TLongNameDataModule.AddToLongNameTable1(const NewField1Value: string);
begin
LongNameTable1.Insert;
LongNameTable1_Field1.Value := NewField1Value;
LongNameTable1_Field2.Value := LongNameTable2_LongNameField1.Value;
LongNameTable1_Field3.Value := LongNameTable3_LongNameField1.Value;
LongNameTable1_Field4.Value := LongNameTable4_LongNameField1.Value;
LongNameTable1.Post;
end;
And then call it from your form like this:
procedure TMyForm.AddButtonClick(Sender: TObject);
begin
LongNameDataModule.AddToLongNameTable1('some value');
end;
This effectively gets rid of your with statement, and makes your code more maintainable at the same time.
Of course surrounding Delphi strings with single quotes will help making it compile as well ;-)
The main problem with "with" is that you don't know where its scope ends, and you could have multiple overlapping with statements.
I don't think you should avoid using it, as long as your code is readable.
One of the proposals to make it more readable (and less confusing in longer code) was if codegear added the option to allow for aliases in with, and probably allowing multiple withs in one:
procedure TMyForm.AddButtonClick(Sender: TObject);
begin
with LongNameDataModule as dm, dm.LongNameTable1 as t1, dm.LongNameTable2 as t2 do
begin
t1.Insert;
t1.FieldByName('Field1').AsString := 'some value';
t1.FieldByName('Field2').AsString := t2.FieldByName('Field2').AsString;
t1.Post;
dm.Connection.Commit;
end
end;
As far as I'm concerned, With is quite acceptable in the case you give. It certainly improves the code clarity.
The real evil is when you have multiple with's open at once.
Also, my opinion is that what you are using the with on makes a big difference. If it's a truly different object then the with is probably a bad idea. However, I dislike having a lot of variables at one level even when this makes sense--generally data objects that hold an entire very complex data item--generally the entire piece of work the program is designed to work with. (I do not think this case would occur in an app that didn't have such an item.) To make the world clearer I often use records to group related items. I find that almost all withs I use are for accessing such subgroups.
There are many excelent answers here as to why the with statement is bad, so I'll try not to repeat them. I've been using the with statement for years and I'm very much starting to shy away from it. This is partially beause it can be difficult to work out scope, but I've been starting to get into refactoring lately, and none of the automated refactorings work withing a with statement - and automated refactoring is awesome.
Also some time ago I made a video on why the with statement is bad, it's not one of my best works but Here it is
Use with only temporarily (just as you comment-out temporarily).
It helps you write code sketches to get something compiled and running fast. If you consolidate the solution, clean it up! Remove with as you move code to the right place.
The current With statement is "dangerous", but it can be substantially improved:
With TForm1.Create (Nil) Do // New TForm1 instance
Try
LogForm ("); // That same instance as parameter to an outer method
"ShowModal; // Instance.ShowModal
Finally
"Free; // Instance.Free
End;
My proposal is:
No more than one object/record per With header.
Nested Withs not allowed.
Usage of " to indicate the object/record (double quotes are similar
to the ditto mark: http://en.wikipedia.org/wiki/Ditto_mark).

Resources