Special

Special Print Closeout!

We're clearing out the remainder of our print issues at fire sale prices -- as much as 75% off! Quantities are extremely limited and only available while supplies last. Hurry to take advantage of this one-time offer.

RBD Magazines

Once these printed back issues are gone, they are gone!

Article Preview


Buy Now

Print:
PDF:

Object-Oriented Thinking

The Finalizer

When Finally Is Not Enough

Issue: 6.4 (May/June 2008)
Author: Charles Yeomans
Article Description: No description available.
Article Length (in bytes): 5,274
Starting Page Number: 40
RBD Number: 6415
Resource File(s): None
Related Link(s): None
Known Limitations: None

Excerpt of article text...

It is sometimes the case that a method needs to perform some task on exit, even if an exception is raised. For this, REALbasic has the Finally block. It suffices for most situations, but not all. Deletion of temporary files is a standard example of the use of a Finally block. Temporary files created in standard locations should be deleted by the operating system from time to time - but suppose your code runs amok and creates too many files, or sucks up all available disk space. For reasons of security, an application might not want to leave temporary files containing sensitive information laying around. And it may be necessary to create temporary files in a location entirely under the control of the application to prevent substitution attacks. So it is a good practice to delete temporary files when they are no longer needed. Let's begin with a naive example. Sub Example() dim tmp as new FolderItem = GetTemporaryFolderItem if tmp <> nil then //more code here tmp.Delete else //handle error end if End Sub There is a problem lurking, of course: if an exception is raised, the temporary file will not be deleted. This is the sort of situation the for which the Finally block was added to the REALbasic language. Sub Example2() dim tmp as new FolderItem = GetTemporaryFolderItem if tmp <> nil then //more code here else //handle error end if Finally if tmp <> nil then tmp.Delete end if End Sub But I do not find this entirely satisfactory. The deletion of the tmp file will occur long after the code that uses it is finished if there is more to the method. And there is the duplicated check tmp <> nil. A Try block allows us to move the deletion closer to its ideal location. Sub Example3() dim tmp as new FolderItem = GetTemporaryFolderItem if tmp <> nil then try //more code here finally tmp.Delete end try else //handle error end if End Sub So a Finally block is just the thing to ensure that a temporary file will be cleaned up. But for other situations, the use of a Finally block has disadvantages. When you call Return in a method, the method returns from that point in the execution of the code, without executing any following Finally block; this came as a surprise to quite a few people when Finally was added to the language. This forces a single-exit structure on code that I often prefer not to use. And here is a situation taken from some actual code. In a console application, I need to reboot the machine on exit, but only if certain conditions are met. To accomplish this with a Finally block, I need to maintain state flags in the main body of code, and check them in the Finally block. Experience has shown that such an approach is error-prone. Here is an alternative that is not. The Finalizer Finalizer is a class that allows me to perform cleanup actions on method or block exit. The idea is simple: put the cleanup code in the destructor of an object. Delegates allow me to provide the cleanup code to another object without breaking information hiding or performing other contortions. The implementation of such a class is quite simple. First, define a delegate type. The delegate declaration must live in a module. So, to a module, add a declaration Delegate Sub FinalizerDelegate() Next, define a class Finalizer. We would like to be able to add and remove FinalizerDelegates, and when a Finalizer object is destroyed, its FinalizerDelegates should be called. So the interface is the following. Sub Constructor(paramArray d() as FinalizerDelegate) Sub Add(d as FinalizerDelegate) Sub Remove(d as FinalizerDelegate) Sub Destructor() We need some internal storage, for which I use Private DelegateMap as Dictionary The implementation of the methods is straightforward. Sub Constructor(paramArray theList() as FinalizerDelegate) me.DelegateMap = new Dictionary for each d as FinalizerDelegate in theList me.Add d next End Sub Sub Add(d as FinalizerDelegate) if d = nil then return end if me.DelegateMap.Value(d) = nil End Sub Sub Remove(d as FinalizerDelegate) if me.DelegateMap.HasKey(d) then me.DelegateMap.Remove d end if End Sub Sub Destructor() for each d as FinalizerDelegate in me.DelegateMap.Keys d.Invoke next End Sub Now let us replace the Try block in our temporary file example with a Finalizer object. Sub Example4() dim tmp as new FolderItem = GetTemporaryFolderItem if tmp <> nil then dim onExit as new Finalizer(AddressOf tmp.Delete) //more code here end try else //handle error end if End Sub And here is how I might be using a Finalizer object in other code. Function Run(args() as String) as Integer if AnotherCopyOfThisAppIsRunning then return ExitCode.ProcessAlreadyRunning end if dim onExit as new Finalizer(AddressOf RemoveProcessOKFile) if SomeOtherConditionIsNotSatisfied then return ExitCode.SomethingElseIsWrong end if //from now on, I can be reasonably certain of a reboot. onExit.Add AddressOf App.Reboot //main body of code return ExitCode.OK End Function I find this approach much cleaner than keeping track of status flags possibly scattered throughout the method.

...End of Excerpt. Please purchase the magazine to read the full article.

Article copyrighted by REALbasic Developer magazine. All rights reserved.


 


|

 


Weblog Commenting and Trackback by HaloScan.com