Introduction
What the Heck is RAII?
RAII stands for "Resource Acquisition Is Initialization". It is a programming idiom which ensures that there is no resource leak (like lost memory, open handles, dangling critical section monitors, etc.), even if an exception is thrown. For languages like C#, the lost memory is not of an issue, but the problem of "open handles" and "critical section monitors" is equally important.
Some languages provide a mechanism to deterministically and implicitly call one specific function at the end of the lifetime of an object instance (e.g. the destructor in C++). The RAII idiom in C++ takes benefit of that fact and places the "acquisition" of the resource into the constructor (= initialization), and releases the resource in the destructor.
E.g. the C++ auto pointer as well as any guard or sentry classes are implementing the RAII idiom. For more insight, see RAII Idiom.
In C++, a reporting sentry (RAII class) would log the text in its constructor and log again some text in its destructor (that is called when leaving the function body):
void f()
{
ReportSentry reportSentry("function f");
...
}
C# does not know this deterministic and implicit destructor call, that's why I explain how I approach this issue in C#.
RAII in the C# World
Coming from a C++ background, I often did apply the RAII idiom. This was not only used for resources but for any kind of action that required symmetric "open-close", "add-remove", "set-reset", "open tag - close tag", "enter critical section - leave critical section", "report start-of-function - report end-of-function", etc. actions - no matter how the control flow left the current scope.
C# provides the well known IDisposable
interface. Each class that implements this interface can be considered as "RAII-ready", i.e., one can use an instance of this class in a...
using (...) { ... }
...statement.
But what to do if you have the need of RAII, but no class at hand for the using (...) { ... } statement?
This article shows some simple helper classes that provide an IDisposable
wrapper class for an init
and a cleanup delegate.
Of course, this is not at all rocket science - actually, it is very simple. It should help to clear up the mist of exception handling involved and focus on the normal control flow.
Using the Code
First, I show the two usages of RAII versus hand crafted side by side.
With RAII support classes |
Hand crafted |
using (var objGuard = new RAIIGuard<int>(
() =>objStack.Pop(),
(e)=>objStack.Push(e)))
{
...
if (objGuard.Item != 0)
{
return;
}
...
if (...)
{
throw new ...;
}
...
}
|
int iTaken;
bool bTaken = false;
try
{
iTaken = objStack.Pop();
bTaken = true;
...
if (iTaken == 0)
{
return;
}
...
if (...)
{
throw new ...;
}
...
}
finally
{
if (bTaken)
{
objStack.Push(iTaken);
}
}
|
I consider the RAII supported approach more legible. If the lambda expressions hurt your eyes, you can always use delegates or methods, e.g.
private int Pop() { return Stack.Pop(); }
private void Push(int i) { Stack.Push(i); }
using (var objGuard = new RAIIGuard<int>(Pop, Push))
{
...
}
Conveniently, there are two classes to provide that function:
- one that has an
init
action and a cleanup action (e.g. login, logout)
- one that has an
init
function returning an object and a cleanup function taking that object (e.g. login and logout with a session object)
public sealed class RAIIGuard: IDisposable
{
private Action Cleanup { get; set; }
public RAIIGuard(Action init, Action cleanup)
{
Cleanup = cleanup;
if (init != null) init();
}
void IDisposable.Dispose() { if (Cleanup != null) Cleanup(); }
]
public sealed class RAIIGuard<T>: IDisposable
{
private Action<T> Cleanup { get; set; }
public T Item { get; private set; }
public RAIIGuard(Func<T> init, Action<T> cleanup)
{
Cleanup = cleanup;
Item = (init != null) ? init() : default(T);
}
void IDisposable.Dispose() { if (Cleanup != null) Cleanup(Item); }
]
Conclusion
It would still be nice if the committee "guarding" ;-) the C# language could re-consider to implement a native RAII support (e.g. where one can declare a guard as an instance of some kind of "value" type and at the end of the scope, a "Dispose
" method is implicitly and deterministically called).
The approach above still requires a certain amount of cooperation by the client where as the pure RAII solution would not need cooperation by the client, i.e., in C#, one can still screw up the RAII idiom by not calling the Dispose()
method :-( .
In the meantime, the approach shown above is good enough for me...
History
- 2010-10-27 Initial version
- 2010-10-27 Fixed some typos
- 2010-10-29 Added more background on RAII, explicit implementation of
Dispose()
(Thanks to Roberto Collina)