Disposal at the End of Function Scope

This sample code demonstrates how you can have your IDisposable objects disposed automatically at the termination of function scope.

Clearly, this implementation isn't suitable to every need due to the need to derive from ContextBoundObject for the outer class whose method scope you want to have cause the disposal of disposable objects (and the performance potential concerns if in a performance-intensive area).

It is, however, an interesting pattern that might be usable in some circumstances.

The basic concept is that, by intercepting method calls through the ContextBoundObject and using our supplied classes, you can create a much cleaner method of cleanup for your disposable objects. The key is that, for this to work, you want your function scope to cause a disposal to happen in the outer class containing that member function needs to be derived from ContextBoundObject. You also need to apply the custome attribute [DeterministicDisposal()] to the class.

When this class gets instatiated, the CLR knows because it is derived from ContextBoundObject to look for the custom attributes. When it finds the custom attribute, it will make sure to use the proxy class specified to call with the constructor. The constructor of the proxy class creates the object and returns a proxy instead of the actual underlying object. This allows the proxy class to intercept any method calls.

When the proxy class is called the first time, it adds a stack to the Context that the object is bound to (a context is just a related set of stored values). Normal objects that are not contextbound can be executing in any context; ContextBound objects, when instantiated, are bound to a specific context. Because it is bound to this set of data, we create a Stack type to use anytime we are in this context.

When a method is called, our proxy gets the first crack at it. We then store the current top of the stack so we know where we were before we call the method. The method on the underlying object is then called. In the method for the object, the user may instatiate classes derived from our base class (DeterministicDisposableObject) or pass IDispsable objects to this class in the constructor so they are protected for the scope of the method.

The DeterministicDisposableObject class adds itself to the Stack that was created (in our proxy) for the context the contextbound object is in.

When the function exits, we are back in our proxy after the method call. We then would get the current stack top and call dispose on all the items added to the stack during the method by our DeterministicDisposableObject class.

The proxy is then exited.

Again I, point out the object is a bit heavy given the proxy and therefore not always suitable and care needs to be taken to only use the DeterministicDisposableObject within a METHOD of a class derived from ContextBoundObject that has our attribute [DeterministicDisposal()] applied to it.

Here is an example of the use of the code:

   /// <summary>
   /// Test class that implements IDisposable
   /// </summary>
   public class MyTestDisposableClass : IDisposable
   {
      public virtual void Dispose()
      { Console.WriteLine("MyTestDisposableClass.Dispose called"); }

   }

   /// <summary>
   /// Test class derived from DeterministicDisposableObject to
   /// show how disposal works
   /// </summary>
   public class MyTestDerivedDisposableClass :
          DeterministicDisposableObject
   {
      public override void Dispose()
      { Console.WriteLine("MyTestDerivedDisposableClass.Dispose
                           called"); }
   }


   /// <summary>
   /// Test class for DeterministicDisposal. A Contextbound class
   /// must be used with our attribute applied to it in order for
   /// deterministic disposal to work correctly.
   /// </summary>
   [DeterministicDisposal()]
   public class TestDeterministicDisposal : ContextBoundObject
   {
      public TestDeterministicDisposal()
      {
      }

      /// <summary>
      /// Test function to simulate some objects that need disposal
      /// </summary>
      public void DoWork()
      {
         //test of a derived deterministic disposable object
         //to be disposed at the end of this function
         MyTestDerivedDisposableClass dobj=
            new MyTestDerivedDisposableClass();

         //test of protecting a standard disposable object to be
         //cleaned up at the end of this function
         MyTestDisposableClass obj2=new MyTestDisposableClass();
         DeterministicDisposableObject dobj2=
            new DeterministicDisposableObject(obj2);

      }

   }


   /// <summary>
   /// Summary description for Class1.
   /// </summary>
   class Class1
   {
      /// <summary>
      /// The main entry point for the application.
      /// </summary>
      [STAThread]
      static void Main(string[] args)
      {
         TestDeterministicDisposal tdd=new
            TestDeterministicDisposal();
         tdd.DoWork();

         // note the console output shows the dispose methods
         // on both variables allocated in tdd.DoWork() were
         // disposed

      }
   }

DeterministicDisposal.cs:
---------------------------------
/* Written by David Risack (c) 2004.
 * You may use this work as long as you leave the copyright intact
 * 
 * 
 */

using System;
using System.Collections;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Services;

namespace AutoDisposal
{
   /// <summary>
   /// Used to store constant for internal name used for call
   /// context properties or other deterministic disposal constants
   /// </summary>
   internal class DeterministicConstants
   {
      internal const string
         DETERMINISTIC_DISPOSAL_PROPERTY_NAME="__DeterministicDisposal";

   }

   /// <summary>
   /// This proxy class is used to be interjected between a
   /// contextbound object so that we can intercept the method
   /// calls to track disposable items and dispose of the object
   /// when completed
   /// </summary>
   internal class DeterministicDisposalProxy : RealProxy
   {
      readonly MarshalByRefObject target;    // used to store the
                                             // target of the proxy

      /// <summary>
      /// Constructor of the proxy used to instatiate with the
      /// target object and type
      /// </summary>
      /// <param name="target"></param>
      /// <param name="type"></param>
      public DeterministicDisposalProxy(MarshalByRefObject target,
                                        Type type)
         : base(type)
      {
         this.target=target;

         //Get our stack object from the call context if it is there
         object obj=CallContext.GetData(DeterministicConstants.
            DETERMINISTIC_DISPOSAL_PROPERTY_NAME);

         if (null==obj)
         {
            //if the stack object is not already in the call
            //context create it
            CallContext.SetData(DeterministicConstants.
               DETERMINISTIC_DISPOSAL_PROPERTY_NAME,new Stack());
         }
      }

      /// <summary>
      /// The Invoke method will be called for each function call
      /// on our contextbound object where our attribute was applied
      /// </summary>
      /// <param name="request">Information about the method
      /// request</param>
      /// <returns></returns>
      public override IMessage Invoke(IMessage request)
      {
         IMessage response = null;
         IMethodCallMessage call = (IMethodCallMessage)request;
         IConstructionCallMessage ctor= call as
            IConstructionCallMessage;

         //Get the stack object that was created in our constructor
         //for the proxy so we can get the current high water mark
         //of the stack so we know the objects to dispose of after
         //the method call returns
         object obj=CallContext.GetData(DeterministicConstants.
                DETERMINISTIC_DISPOSAL_PROPERTY_NAME);
         Stack DisposalStack=(Stack)obj;

         //stack was not created if this fails
         Debug.Assert(obj!=null);

         //Get our current high water mark for the stack
         int BeforeCallStackTop=DisposalStack.Count;

         //see if this is the constructor call
         if (ctor!=null)
         {
            //We need to create and return our proxy here to return
            //it instead of the actual contextbound object itself
            RealProxy defaultProxy =
               RemotingServices.GetRealProxy(target);
            defaultProxy.InitializeServerObject(ctor);
            MarshalByRefObject tp =
               (MarshalByRefObject)this.GetTransparentProxy();
            response =
               EnterpriseServicesHelper.
               CreateConstructionReturnMessage(ctor,tp);
         }
         else
         {
            //Execute the method call
            response = RemotingServices.ExecuteMessage(target, call);
         }

         //Get the stacks current high water mark so we know how
         //many items to pop off for disposal
         int ItemsToPopOffStack=DisposalStack.Count-BeforeCallStackTop;
         Debug.Assert(ItemsToPopOffStack>=0);

         //If there were new items added that need to be disposed
         //of then
         if (ItemsToPopOffStack>0)
         {
            //Loop to pop all of the new items off the stack for
            //Deterministic Disposal
            for (int i=0; i<ItemsToPopOffStack; i++)
            {
               IDeterministicDispose id=
                  (IDeterministicDispose)DisposalStack.Pop();
               try
               {
                  //Call our interface which in turn will call the
                  //standard IDisposable interface
                  id.DeterministicDispose();
               }
               catch
               {
                  Trace.WriteLine("DeterministicDisposalProxy
                                   exception - object may have
                                   already been finalized or
                                   destroyed");
                  Debug.Assert(false);    //check to see if this
                                          //actually happens; remove
                                          //for production code
               }
            }

         }

         return response;

      }


   }

   /// <summary>
   /// DeterministicDisposalAttribute is the attribute that must be
   /// applied to contextbound derived classes in order to take
   /// advantage of using the DeterministicDisposableObject base
   /// class (or as an object)
   /// This attribute causes our proxy to be injected between the
   /// contextbound object and callers
   /// </summary>
   [AttributeUsage(AttributeTargets.Class)]
   internal class DeterministicDisposalAttribute : ProxyAttribute,
            IContextAttribute
   {

      public DeterministicDisposalAttribute()
      {
      }

      /// <summary>
      /// CreateInstance used to create our real proxy and return
      /// it to the caller
      /// </summary>
      /// <param name="t"></param>
      /// <returns></returns>
      public override MarshalByRefObject CreateInstance(Type t)
      {
         MarshalByRefObject target=base.CreateInstance(t);
         RealProxy pp=
            (RealProxy)new DeterministicDisposalProxy(target, t);
         return (MarshalByRefObject)pp.GetTransparentProxy();
      }


      /// <summary>
      /// IsContextOK just indicates to use the same context for
      /// this new object by returning true
      /// </summary>
      /// <param name="ctx"></param>
      /// <param name="msg"></param>
      /// <returns></returns>
      new public bool IsContextOK(Context ctx,
                                  IConstructionCallMessage msg)
      {
         return true;
      }


   }

   /// <summary>
   /// IDeterministicDispose our internal interface to be called
   /// for deterministic disposal
   /// </summary>
   internal interface IDeterministicDispose
   {
      void DeterministicDispose();
   }

   /// <summary>
   /// DeterministicDisposableObject this object can be used as a
   /// base class or just to pass an object to protect by passing
   /// the object to the constructor.  Should be used within a
   /// method only.
   /// The class that uses this in a method should have been
   /// derived from ContextBoundObject for it to be
   /// deterministically disposed.  Should not be used in a
   /// non-contextbound object as call context would be saved and
   /// would not be cleaned up, plus the objects would never get
   /// destroyed
   /// </summary>
   public class DeterministicDisposableObject : IDisposable,
          IDeterministicDispose
   {
      /// <summary>
      /// Default constructor puts our object in the callcontext
      /// stack so we will get disposed when the method call is done
      /// </summary>
      public DeterministicDisposableObject()
      {
         Stack DisposableStack=(Stack)CallContext.
               GetData(DeterministicConstants.
               DETERMINISTIC_DISPOSAL_PROPERTY_NAME);
         Debug.Assert(DisposableStack!=null);
         DisposableStack.Push(this);
      }

      /// <summary>
      /// Constructor used to pass an object that is IDisposable
      /// </summary>
      /// <param name="o"></param>
      public DeterministicDisposableObject(IDisposable o)
      {
         //Save reference to our object so we can call its dispose
         //later
         ReferencedObject_=o;

         Stack DisposableStack=(Stack)CallContext.
               GetData(DeterministicConstants.
               DETERMINISTIC_DISPOSAL_PROPERTY_NAME);
         Debug.Assert(DisposableStack!=null);
         DisposableStack.Push(this);
      }

      /// <summary>
      /// virtual Dispose function can be override by a derived
      /// class to implement objects own dispose
      /// </summary>
      public virtual void Dispose() { }


      /// <summary>
      /// internal DeterministicDispose dispose function calls
      /// dispose on referenced object, this object and frees
      /// interop handles to allow for garbage collection
      /// </summary>
      public void DeterministicDispose()
      {
         if (null!=ReferencedObject_)
         {
            try
            {
               ReferencedObject_.Dispose();
            }
            catch
            {
               Trace.WriteLine("DisposableObject.DeterministicDispose
                                Exception while calling passed
                                objects Dispose.");
            }

         }

         try
         {
            //call our virtual member function.  It may have been
            //overridden so we should protect from failure
            Dispose();
         }
         catch
         {
            Trace.WriteLine("DisposableObject.DeterministicDispose
                             Exception while calling passed
                             objects Dispose.");
         }

      }

      //Class members
      private IDisposable ReferencedObject_;    //used for passed-in
                                                //object

   


}


----------------------------------
Here is the output from the run:
MyTestDisposableClass.Dispose called
MyTestDerivedDisposableClass.Dispose called



Downloads

Comments

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • IBM Worklight is a mobile application development platform that lets you extend your business to mobile devices. It is designed to provide an open, comprehensive platform to build, run and manage HTML5, hybrid and native mobile apps.

  • On-demand Event Event Date: October 29, 2014 It's well understood how critical version control is for code. However, its importance to DevOps isn't always recognized. The 2014 DevOps Survey of Practice shows that one of the key predictors of DevOps success is putting all production environment artifacts into version control. In this webcast, Gene Kim discusses these survey findings and shares woeful tales of artifact management gone wrong! Gene also shares examples of how high-performing DevOps …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds