Dispose and VALID-OBJECT

Posted by jquerijero on 06-Nov-2009 14:09

Is there a reason why VALID-OBJECT returns TRUE when checking a frm that is already been disposed?

Ex.

DEF VAR frm AS so.MyWindow NO-UNDO.

frm = NEW so.MyWindow().
frm:Dispose().


MESSAGE VALID-OBJECT(frm) VIEW-AS ALERT-BOX. -- I believe this should return FALSE instead of TRUE.

All Replies

Posted by jmls on 06-Nov-2009 14:34

I think it is still valid because garbage collection has not yet taken place. I use the IsDisposed attribute to check

MESSAGE VALID-OBJECT(frm) AND NOT frm:IsDisposed VIEW-AS ALERT-BOX

Posted by jquerijero on 06-Nov-2009 14:41

NOTE: IsDisposed is not a requirement for classes that implement IDisposable. You can not always rely that IsDisposed is there.

However, this is actually a lead in question to my other thread about the session form listing. Which is more about consistency that anything else because in this case form will always have to be "DELETE OBJECT".

Since the GUI for .NET relies on garbage collection, Dispose and DELETE OBJECT should be conceptually similar.

Posted by Matt Baker on 07-Nov-2009 09:09

Dispose is there to free the internal resources of the object.  valid-object() only test if the object handle itself is valid.  As far as the ABL is concerned there is no difference between calling Dispose() method or something called Initialize().  To the ABL they are just methods, what they do internally is up to the implementation of that individual object.

The result of IsDisposed() means different things to different objects.  If you can call a method, then you have access to an instance of an object which means the object is still valid.  Until you actually delete the reference to the object by calling delete object on the variable or set all the variables that point to it to something else , the object is still available.  If the variable points to a .net object, the object is still completely valid.  It will only get garbage collected when all the ABL references are removed and all the .net references are removed.  Calling delete object will invalidate all the ABL references, but not necessarily all the .net references.

Posted by jquerijero on 09-Nov-2009 08:53

Fair enough...

One more question, why is the destructor not calling THIS-OBJECT:Dispose()? Does the DELETE OBJECT calls it for you in the background?

Posted by Matt Baker on 09-Nov-2009 09:16

This is up to the implementation of the particular object.  In general, it is never a good idea to rely on a destructor to clean up resources.  There is never any guarantee that it will actually run.  And if it does run, there is no guarantee of when it will run.

For ABL objects calling delete object is sufficient to destroy the object.  In .net there is no such functionality.  So calling delete object from the ABL on a .net object does not destroy the object only release the pointer the AVM has to it.

For ABL if you don't call delete object, then the garbage collector will call it (if it has one) when it gets around to it.  The same holds true for .NET objects.  It gets called whenever the garbage collector decides to call it. You have no control over it.

UI components on nearly every platform hold onto system level resources.  These resources are finite.  So because the garabage collector is not guaranteed to call your destructor, you need a way to free those resources.  A dispose() method is the standard name for this sort of thing.   A call to dispose on one object will normally cascade that call down to its children.  It works this way in SWT (eclipse) as well.  The objects themselves are still valid, but the resources they point to in the OS (usually written in C++ code) have been released back to the OS.

To throw another wrench into your works, you need to understand that a lot of the .NET WinForm objects call "SuppressFinalize()" when Dispose() is executed.  This tells the garbage collector to NOT call the destructor when the object is cleaned up.   So you can't rely on the destructor either in .NET.

To finally answer your question, the destructor does not call dispose because that is not its responsibility.  Dispose() should be called by the application when the UI resources are done with at the application level.  Calling it as part of the destructor would generally be too late and could lead to running out of OS resources if the garbage collector never gets around to it.

So 3 take aways:

Never use destructors in your UI objects because they are unpredictable.

If there is a Dispose() method present on the object you are expected to ensure it is called when you are done with it.  You can and are expected to override dispose if you need to; making sure to call dispose in the base object.

Leave the garabage collecting up to the garbage collector.

Posted by jquerijero on 09-Nov-2009 09:20

So what is the default destructor is for, and what is calling it (it sounds like in your response DELETE OBJECT is not calling it)?

Posted by Matt Baker on 09-Nov-2009 09:25

I'm not sure I understand your question.  Can you explain what you mean by "default destructor"?

Posted by Matt Baker on 09-Nov-2009 09:34

When delete object is run then:

For ABL objects, the destructor (if it exists) gets called immediately.

For .NET objects, the destuctor (if it exists) it gets called by the .NET garbage collector when the garbage collecotr feels like it (its very emotional you know .  Unless SupressFinalize() is called on the object, in which case it never gets called.

For hybrids (ABL objects that inherit from a .NET object) the rule is the same as for .NET objects.

If delete object is not called and the last reference to the object is removed

For for .NET and ABL objects, the destructor ( if it exists) might get called at some point in the future.  The word we normally use is "non-deterministic".

Posted by rbf on 09-Nov-2009 10:05

mbaker wrote:

So 3 take aways:

Never use destructors in your UI objects because they are unpredictable.

If there is a Dispose() method present on the object you are expected to ensure it is called when you are done with it.  You can and are expected to override dispose if you need to; making sure to call dispose in the base object.

Leave the garabage collecting up to the garbage collector.


That does not make me very happy.

We are upgrading our customer from 10.2A GUI to 10.2A GUI for .NET. When the new application is started, it right away takes 70 MB of memory. This used to be 20 MB for the old application. When the user does nothing, memory usage stays at 70 MB. If the user clicks in the application after a while, memory is released immediately until only 2 MB (!) remains in use. So garbage collection works, but we would like to be able to trigger it.

That does not sound like a big deal, *except* that this customer is running 800 concurrent users on a Citrix farm. 50 MB extra times 20 users is 1GB extra memory per server and that kind of memory is not available. It also cannot be extended since all servers already have the maximum of 4GB of memory installed.

Upgrading the server farm to 64 bit is on the road map but in the meantime this means we cannot deploy.

Unless we can find a way to trigger garbage collection....

Posted by jquerijero on 09-Nov-2009 10:19

The default destructor contains this;

IF VALID-OBJECT(components) THEN
DO:      
     CAST(components, System.IDisposable):Dispose().    
END.

I expected this to be

THIS-OBJECT:Dispose(False).

Posted by Matt Baker on 09-Nov-2009 10:37

.NET wants its system resources disposed of, but there are some types of resources that are not components. For example, a button is a component, it belongs to the form.  When the form is closed/disposed it has a reference to the button in the Controls list and it can dispose of the button.

The button may have an image on it.  That image is normally created as part of an ImageList object.

In the code generated by the Visual Designer you'll always see this destructor generated for UI components (forms/dialogs/user controls...).  This destructor is there to handle the case of system level resources that are not actual components.  The most common case is the Image.  Images objects (bitmaps/jpegs...whatever) require system resources in order to paint to the UI and they have a Dispose(), but they are not "owned" by any individual object or by the form itself.  Instead the designer adds them to a list maintained by the object referenced by the "components" variable.   The components object is then responsible for for the cleanup.  Internally the components object just calls Dispose() on the objects it references.

You'll notice that sometimes the components object is not valid in the destructor.  This is because the form is not using any such resources that are required to be cleaned up (i.e. no images).

Posted by jquerijero on 09-Nov-2009 10:58

My question is that why aren't you calling the object own Dispose (considering it's a blackbox) ? I'm not saying that the implementation is wrong as I can adapt to it, I'm trying to guage the implication of why only the dispose of the components is considered as the most minimum requirement and not the object level disposal.

Posted by Matt Baker on 09-Nov-2009 10:59

I expected this to be

THIS-OBJECT:Dispose(False).

Your application is responsible for calling Dispose().  It is not the garbage collector's responsibility.

Doing such would be an indication that you have no idea of the state of your application.  I don't recommend trying to do this either.  Some of the .NET Winforms components will throw an exception if you try to call Dispose() twice.

The destructor might not run so having this code present is no guarantee of anything.  And since the parameter "false" indicates that it is not disposing of system level resources, then you still are not freeing the required resources and you still have a memory leak if you don't call Dispose().  And you still need to call Dispose() on the components object so using this-object:Dispose(false) doesn't satisfy that.

Posted by jquerijero on 09-Nov-2009 11:15

This is the most common .NET pattern when dealing with Dispose and Finalizer. I'm trying to understand if I have to be aware of something else because the default implementation of the destructor is odd. When I saw your default implementation of a "Finalizer," it threw me off as disposal of manage code should happen inside the Dispose methods chain not in the Finalizer.

   //Implement IDisposable.
   public void Dispose()
   {
     Dispose(true);
      GC.SuppressFinalize(this);
   }

   protected virtual void Dispose(bool disposing)
   {
      if (disposing)
      {
         // Free other state (managed objects).
      }
      // Free your own state (unmanaged objects).
      // Set large fields to null.
   }

   // Use C# destructor syntax for finalization code.
   ~Base()
   {
      // Simply call Dispose(false).
      Dispose (false);
   }

Posted by Matt Baker on 09-Nov-2009 11:58

I think you mean "disposal of UNMANAGED code should happen in the dispose method.  The code in the destructor isn't "our" code.  The VD in OEA generates this code because visual studio generates it in the same manner and we have to follow suit.

We don't generate a Dispose() method because the base class' will take care of anything that needs to happen normally.  I don't really know why MS chose to design it this way.

Posted by Laura Stern on 09-Nov-2009 13:37

Matt has this mostly correct, but he is missing a couple of things based on changes that were made in 10.2A01 (and therefore 10.2B).  Just to be clear - this whole discussion is about ABL extended .NET objects, which for the sake of brevity in this discussion I will refer to as hybrid objects.  It is a pretty murky subject, to be sure!

1. It is true that in native .NET, calling Dispose() has nothing to do with garbage collection.

2. Garbage collection on the AVM side is separate (though related to) garbage collection on the .NET side.  When we delete an object, whether via AVM garbage collection or via DELETE OBJECT, we can only get rid of the memory/resources used in the non-managed (i.e., non .NET) environment.  Our delete will remove a reference from the object.  But even if there are now no more references to this object anywhere, .NET may not get around to its side of garbage collection for a while - as has already been stated.

3. As Matt correctly states, finalizers are not run for objects that implement IDisposable (which includes Forms and Controls, since they inherit from Component) because the Dispose() method calls SuppressFinalize().  We previously relied on the Finalize running in .NET to know when to garbage collect an object on our end.  So because of SuppressFinalize, we needed a different mechanism for doing garbage collection for hybrids that inherit from these UI components.  This new behavior was introduced in 10.2A01.  If Dispose has been called on a hybrid object that implements IDisposable, AND there are no more references to that object in the ABL, the AVM will delete that object.  When a non-modal form is closed, Dispose is called automatically on the form and also on the components that are on the form.  Therefore, once a non-modal form is closed and any ABL references are gone, those objects will be garbage collected.  So, as stated in #2, this will free up AVM memory immediately.  In .NET, resources may take longer to go away.  See #4 for more on modal forms.

4. .NET has a strange (IMHO) model for modal forms - ones on which you called ShowDialog().  It does NOT call Dispose() on a modal form when it is closed.  In fact according to the doc, the Close() method is not called (even though FormClosed fires).  Instead .NET hides the form and you can then call ShowDialog again on the same form.  Therefore, a modal form will NOT get garbage collected unless you call Dispose on it yourself.  In this case it is actually better to call Dispose than to do DELETE OBJECT on the form.  If you call Dispose on the form, then the form will call Dispose on all of its components as well.  If any of these components are also hybrids (such as an inherited UserControl), this makes them available for garbage collection (again - assuming any ABL references to those objects are gone).  Whereas, if you just call DELETE, this will delete the AVM side of the hybrid form.  But the .NET side of the form will not go away, the form still holds references to the controls on the form, noone has called Disposed on these components, and so they still won't get garbage collected - on either side.

5. You can rely on your hybrid destructors running.  But there are two cases.  One is when the hybrid inherits from an object that implements IDisposable.  This will be the most common case - forms and controls.  As stated in #3, the destructors for those objects will be run as soon as the form is closed and there are no more ABL references.  If you happen to inherit an ABL class from some other .NET object, it will not get garbage collected by the AVM until it is garbage collected by .NET.  So in this case, the .NET memory will actually go away first, and then the AVM will clean up its side.  So indeed, the running of the destructor will happen, but it will probably be delayed for some time.  If you care about the destructor running sooner, then you would have to call DELETE OBJECT on it.  See # 6.

6. There is nothing wrong with calling DELETE OBJECT on a hybrid, but you just have to know what you are doing.  If .NET still has a reference to this object, it won't actually get deleted.  A bizarre case would be deleting a button that is on a form that is still on the screen!  Obviously not the right thing to do.  The button will remain there.  Plus you will get errors if you overrode any .NET methods or implemented any .NET interfaces that .NET might still call.  But if you are sure that no one is using this object anymore, you can certainly DELETE it.

7. You cannot override Dispose().  You can in .NET, but the AVM does not allow you to do it.  This was true in 10.2A and is still true.

8. There is a way to force .NET to do its garbage collection.  It is:

  System.GC:Collect().
  System.GC:WaitForPendingFinalizers().
  System.GC:Collect().

But I agree with Matt.  .NET has a sophisticated algorithm for determining when to do garbage collection in an efficient manner at an efficient time and by using other threads.  So from a performance perspective, it is best to let .NET be .NET and take care of its garbage collection in its own way.   There may be cases when you want to force it, but I would certainly use it sparingly, if at all, in a deployed application.  Certainly you could use it during development as a way to better understand what resources are really being used.

Despite this being rather complicated, I hope I have clarified this.  It is unfortunately, not as simple as we would hope.  So please feel free to post more questions.

Posted by Matt Baker on 09-Nov-2009 14:40

These pages has some notes on finalizers which you might find interersting, one of which talks about the standard Dispose/finalize pattern in .NET

http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx

http://blogs.microsoft.co.il/blogs/sasha/archive/2008/04/26/don-t-blindly-count-on-a-finalizer.aspx

http://msdn.microsoft.com/en-us/library/ms973837.aspx#dotnetgcbasics_topic5

http://blogs.msdn.com/tom/archive/2008/04/25/understanding-when-to-use-a-finalizer-in-your-net-class.aspx

Posted by jquerijero on 09-Nov-2009 17:01

Disposal of unmanage code should be done in Dispose method chains; however, if Dispose method is not called on the object the finalizer/destructor should kick in during collection thus the last time the object can free up un-manage code. Therefore, finalizer main task is to make sure unmanaged code is freed up.

Finalizer is suppressed when the Dispose method is called directly, it is not suppressed when the object just goes out of scope.

Posted by jquerijero on 09-Nov-2009 18:00

Thanks Laura,

I'm not particularly concern about the garbage collection. I understand that process very well. It's just that there are few ABL keywords that have no counterpart in .NET programming, ex. DELETE OBJECT . . ., and mixing the expectation in .NET programming that Disposed object is as good as gone with the keyword VALID-OBJECT (which I would expect will do the IsDisposed checking for you).

Plus the destructor/finalizer contains something that should be part of the Dispose method chains, in fact, it does contain the piece of code that the Dispose(bool disposing) override should have in it. This got me thinking, if the Progress destructor is really part of .NET or AVM specific.

To wrap things up, are these statements equivalent;

DELETE OBJECT myObject   myObject = ?

IF VALID-OBJECT(myObject) THEN ... IF myObject = ? THEN

Posted by Laura Stern on 10-Nov-2009 09:13

The VALID-HANDLE function has been around in the ABL for a long time.  With the advent of OO, we introduced the VALID-OBJECT function.  In both cases, when these return true, it means (and has always meant), simply, that the object exists and is accessible through the specified handle/object reference and so methods and attributes/properties can be called via that handle or object reference.  We did not change this meaning for .NET objects or ABL extended .NET objects.  So we do not take into account whether Dispose has been called on the object. In fact, even if Dispose has been called, you can still access the object, inquiring about its properties, etc.  Take this code for example:

DEFINE VAR frm AS System.Windows.Forms.Form.

frm = NEW System.Windows.Forms.Form().
frm:Dispose().
MESSAGE frm:ClientSize VIEW-AS ALERT-BOX.

This runs just fine and displays the form's default Size.  So it is pretty clear that the object still exists and is "VALID" as we've defined it.

Sorry - I don't really understand what you're saying in the 2nd paragraph about destructors/finalizers.

As to the these:

No - DELETE OBJECT myObject  is not equivalent to myObject = ?.  As I explained previously, DELETE OBJECT will delete the object immediately (at least the AVM side of an object, if it's a hybrid) and will do so whether or not there are any other references to it - whether these references are in the ABL or in .NET.  Setting the object reference to Unknown simply removes a reference.  If there are no other references then the object will be available for garbage collection.  If the object is an ABL extended .NET object, we use the rules that I previously explained to determine when to garbage collect it.  (i.e., if the object implements IDisposable it will be garbage collected once it has been Disposed, otherwise, it will be garbage collected once .NET has garbage collected it on the managed side.)

Yes - I believe these actually are equivalent:  IF VALID-OBJECT(myObject) and  IF myObject = ?.  It is not equivalent for handle-based object, but it is for OO objects.  Unlike for Handles, if you DELETE an object, its object reference is automatically set to Unknown.  Or if you explicitly set an object reference to Unknown, then VALID-OBJECT must return no, since by definition Unknown is not a valid object.  But just keep in mind that the purpose of this statement is to know whether you can use that object reference or not.  It doesn't tell you whether or not the object that this object reference once referred to is still alive.  For example:

DEFINE VAR btn AS System.Windows.Forms.Button.
DEFINE VAR btn2 AS System.Windows.Forms.Button.

btn = NEW System.Windows.Forms.Button().
btn2 = btn.
btn = ?.
MESSAGE VALID-OBJECT(btn) SKIP
        VALID-OBJECT(btn2)
    VIEW-AS ALERT-BOX.

This will display no and yes.

Posted by jquerijero on 10-Nov-2009 10:08

Luara thanks for being helpful.

As for the second paragraph . . . .

"Plus the destructor/finalizer contains something that should be part of the Dispose method chains, in fact, it does contain the piece of code that the Dispose(bool disposing) override should have in it. This got me thinking, if the Progress destructor is really part of .NET or AVM specific."

I'm trying to figure the default destructor that is provided by the Architect. In Visual Studio the one that is provided is an override of the Dispose method which is what Dispose and Finalizer(Destructor) eventually calls.

      

///
/// Clean up any resources being used.
///
/// true if managed resources should be disposed; otherwise, false.
protected override void Dispose(bool disposing)
{

            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
}

The default Finalizer that Architect provides looks a lot like what written above.

DESTRUCTOR PUBLIC HomeWindow ( ):

  IF VALID-OBJECT(components) THEN DO:
   CAST(components, System.IDisposable):Dispose().
  END.

END DESTRUCTOR.

I believe GC only calls the Finalizer/Destructor, since at that time it is already trying to collect manage code, so disposal is not needed but de-allocation of un-managed code still has to be done thus most finalizer/destructor implementation only calls Dispose(False), and Dispose (on-demand call) suppresses the Finalizer since it is promising that it will handle the freeing of un-managed code. Sure I can add the closing of file handles and such inside the destructor to make sure it runs during the garbage collection; however, I can no longer use Dispose pattern for freeing resources on demand for CUSTOM objects. I think the Dispose override was actually provided to have a single location for handling "on-demand" and "garbage collection time" freeing of resources.

Posted by egarcia on 10-Nov-2009 11:18

Hello Peter,

If the user clicks in the application after a while, memory is released immediately until only 2 MB (!) remains in use.

The 2 MB that you mentioned, seems to be the memory usage and not the VM size for the process. Windows may shrink the memory usage but the memory allocated by the process would remain the same.

I generally display the Mem Usage and the VM Size columns in the Windows Task Manager.

You can also use Process Explorer to see the memory usage of a process.

Process Explorer is also useful when using GUI for .NET because it can show you the assemblies that are loaded.

You may check the assemblies.xml file to make sure that only the assemblies required by the application are listed.

I hope this helps.

Posted by Laura Stern on 10-Nov-2009 13:29

There are really two issue here.

First - I think you have pointed out a valid issue with the code that is generated.  We are supposedly mimicing the Dispose pattern used by .NET, but it is not quite the same.  We have taken the code that .NET usually puts in its override Dispose(bool) method and moved it to our destructor.  As you point out, from the destructor, we should only be trying to free unmanaged resources.  Therefore, we shouldn't be calling components:Dispose() (with no parameters - which cleans up both managed and unmanaged resources.  So we will discuss this here and determine the correct thing to do.

The reason we cannot allow an ABL class to override Dispose(bool) is that it is usually (or always?) called on another thread.  This is because it is called during garbage collection.  Since we are single threaded, this does not work very well!

The 2nd issue is where do you put your cleanup code - for resources used by your ABL code?  If we were following the Dispose pattern exactly, you could put it in the Dispose(bool) method and be guaranteed that it is called either automatically - and more timely, when Dispose() is called (e.g., when a Form is closed) or during garbage collection.  But this is not the case. Therefore, you will have to come up with your own pattern.  Perhaps you can just have a cleanup routine that you can either call explicitly or call it in a handler for the Disposed event - which I believe happens when Dispose() is called - NOT Dispose(bool), so it is on the main thread.  And you could also call it in the ABL destructor.

Posted by Laura Stern on 16-Nov-2009 15:53

After some discussion, we have decided to modify the code that the Visual Designer generates.  The new code pattern will be like this (new lines are bold).  This accomplishes the same thing as the .NET IDisposable pattern.  Again - to recap - we cannot do it the same way .NET does (by overriding Dispose(boolean)) because that is called on another thread.  This will not happen in 10.2B, but should happen in the first service pack.  In the meantime, you could write this code yourself.

Thank you very much for you input on this.   :-)

CONSTRUCTOR PUBLIC form1():

   SUPER().

   InitializeComponent().

   THIS-OBJECT:Disposed:Subscribe(disposedHandler).

   ...

END.

METHOD PRIVATE VOID disposedHandler(INPUT sender AS System.Object, INPUT args AS System.EventArgs):

   cleanup(TRUE).

END.

METHOD PRIVATE VOID cleanup(disposing AS LOGICAL):

   IF disposing AND VALID-OBJECT(components) THEN DO:

      CAST(component, System.IDisposable):Dispose().

   END.

   /* You can put ABL cleanup code here */

END.

DESTRUCTOR PUBLIC form1():

   cleanup(FALSE).

END.

Posted by jquerijero on 16-Nov-2009 17:37

Seems to work OK for both Form and UserControl.

Can you also find some way to do the same thing for non-UI helper classes?

Thanks,

Posted by Admin on 17-Nov-2009 00:26

This will not happen in 10.2B, but should happen in the first service pack.

In the meantime, you could write this code yourself.

Does this also mean that we need to manually upgrade all our forms once that SP is out to match that new pattern (I guess so)?

In that case it needs to be release noted in big, big letters (but unfortunately too few developers read the RL anyway).

Posted by Laura Stern on 17-Nov-2009 09:07

I assume you mean a non-UI helper class that you've overridden in the ABL but does not implement IDisposable.  In this case, there really isn't any conventional pattern to follow.  So you would really need to come up with your own convention if you have ABL resources that need freeing.  You can always put cleanup code in the destructor, but, as has been stated, that is not guaranteed to happen in a timely manner.  If there is no ABL cleanup to do, then you really don't need to worry about it.

Posted by Laura Stern on 17-Nov-2009 09:12

It would probably be good for application consistency to modify your existing forms.  But if your application is working fine as it, you don't NEED to do anything.  And really, it is more of an impetus to do this modification if you have ABL resources to clean up.  But in that case, I assume you've already devised a way for this to work with the existing code.  So it is really up to you if you want to modify it or not.

Posted by Laura Stern on 17-Nov-2009 10:16

Oops - just had a typo in here.  It used to be:

THIS-OBJECT:Disposed:Subscribe(cleanup).

It should have been this (as it is now):

THIS-OBJECT:Disposed:Subscribe(disposedHandler).

Posted by jquerijero on 17-Nov-2009 15:31

I figured that much.

Just in case, component needs an "s";

CAST(component, System.IDisposable):Dispose().

Posted by jquerijero on 17-Nov-2009 17:06

Do you mean you can implement IDisposable in ABL? It is not a violation of single thread rule?

Posted by Laura Stern on 18-Nov-2009 09:24

I have to say I'm a little confused as to why you're asking this question.  I don't see its relationship to the other postings.  Nevertheless, I will answer it.  In fact, the only element in the IDisposable interface is a single method - Dispose() - with no parameters.  The interface does not even have the Disposed event.  The standard code that is used to implement the IDisposable pattern (notably the overloaded version of Dispose(boolean)) is not part of this interface.  It is the Dispose(boolean) method that generally gets called on another thread - not the Dispose() method.  So, sure you could implement IDisposable.  But that is not sufficient to implement the pattern.  Hope that answered the question.

This thread is closed