JAL Computing

C++COMProgramming .NET Mac Palm CPP/CLI Hobbies

 

Home
Up

Chapter 26 "RAII  In C#"

One of the most useful and powerful idioms in C++ is RAII (Resource Acquisition Is Initialization). In the RAII pattern you create resources in the constructor and release them in the destructor. In standard C++, the destructor is called even if an exception is thrown. This frees the programmer from worrying about resource leaks within the RAII pattern. 

One of the advantages of the RAII idiom is that in C++ destructors are deterministic, meaning that the destructor is called in a predictable and timely manner, thus releasing resources in a predictable and timely manner. This is possible since C++ classes by default use value semantics. When the C++ object on the stack goes out of scope the destructor is called. In C#, destructors are not deterministic since objects on the heap are collected only when the garbage collector is invoked... either by the runtime or by a call to GC.Collect(). In C# the destructor ~MyClass() is mapped to Finalize(), which is called when the object is collected by the garbage collector.

To address the shortcomings of a non-deterministic destructor, C# offers the IDisposable interface and using construct. In this idiom, you implement the IDisposable interface by providing a Dispose() method and then release managed and unmanaged resources in the Dispose() method. You then create the disposable object within the using construct so that the Dispose method is automatically called when the object exits the using scope. So to a first approximation RAII in C# looks like this:

class Program : IDisposable
    {
        private int i;
        private bool disposed = false;
        public Program()
        {
            i = 2;
            Console.WriteLine("Constructor called.");
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this); // remove this from gc finalizer list
            Console.WriteLine("Dispose called.");
        }
        // call Dispose(true) from Dispose, call Dispose(false) from finalizer
        // dispose once only
        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing) // called from Dispose
                {
                    // Dispose managed resources.
                }
                // Clean up unmanaged resources here.
            }
            disposed = true;
        }
        ~Program() // maps to finalize
        {
            Dispose(false);
        }
        // ASSERT disposed is false
        public void SayHello()
        {
            if (disposed)
            {
                throw new ObjectDisposedException("Program");
            }
            Console.WriteLine("Hello");
        }
        public int I 
        {
            get{return i;}
            set{i = value;}
        }
        static void Main(string[] args)
        {
        }
 }

You could then call the methods as in:

Program p = new Program();  // allocate unmanaged resources
p.SayHello();

Console
.WriteLine(p.I);
p.Dispose();  // release unmanaged resources

One problem with this explicit use of Dispose is that it is possible to forget to call Dispose on an object, so that the application leaks unmanaged resources. It is also possible for a caller to invoke a method in an object that has already been Disposed. In the example above, a call to a method in a disposed object throws an ObjectDisposedException:

// ASSERT disposed is false
public void SayHello()
{
    if (disposed)
    {
        throw new ObjectDisposedException("Program");
    }
    Console.WriteLine("Hello");
}

Finally, the code sample is not exception safe. If an exception is thrown before the call to Dispose, the application could leak unmanaged resources. A more complex, but exception safe construct is to wrap the call to Dispose in the finally clause of a try finally construct as in:

Program p = null;
try
{
      p = new Program();
      p.SayHello();
      Console.WriteLine(p.I); 
}
finally
{
      if (p != null)
      {
         p.Dispose();
         p = null;
      }
}

This type of complexity can be relegated to the compiler through the using construct as in:

using (Program p = new Program())
{
    p.SayHello();

    Console
.WriteLine(p.I);
}

That's about as close as we can get to RAII in C#, but it is effective! 

Note: C++/CLI does provide semantics for objects on the stack and deterministic behavior. For the curious, here is the C++/CLI version of the Program class:

public ref class RAII 
{
private:
	int i;
	bool isDeleted;
public:
	RAII() : i(2), isDeleted(false)
	{
		Console::WriteLine(L"Constructor called.");
	}
	~RAII() // maps to Dispose
	{ 
		if (!isDeleted) { // release once only
			// clean up managed resources here.
			Console::WriteLine(L"Releasing managed resources."); 
			this->!RAII(); // clean up unmanaged resources here.
		}
		this->isDeleted= true;
		Console::WriteLine(L"Destructor called.");
		// finalize (!RAII) is automagically suppressed
	};
	!RAII() // maps to Finalize
	{
		if (!isDeleted) 
		{
			// Clean up unmanaged resources here.
			Console::WriteLine(L"Releasing unmanged resources.");
		}
		Console::WriteLine(L"Finalizer called.");
	}
	property int I {
		void set(int value) {this->i = value;}
		int get() {return i;}
	}
	// this can be called even after this object has been disposed!
	// ASSERT isDeleted is false
	void SayHello() 
	{
		if (isDeleted) {
			throw gcnew System::InvalidOperationException();
		}
		Console::WriteLine(L"Hello");
	}
	void Close() {
		if (!isDeleted) {
			this->~RAII();
		}
	}
};

In C++/cli you can instantiate and call methods and properties on an object with stack based semantics. In the following code snippet the destructor will be called when the object r goes out of scope. The destructor will be called even if an exception is thrown.

RAII r;
r.SayHello();
Console::WriteLine(r.I);

Have fun,
Jeff

Send mail to [email protected] with questions or comments about this web site. Copyright © 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 © 
Last modified: 08/04/09
Hosted by www.Geocities.ws

1