tags:

views:

832

answers:

5

Hi. A common pattern in C++ is to create a class that wraps a lock - the lock is either implicitly taken when object is created, or taken explicitly afterwards. When object goes out of scope, dtor automatically releases the lock. Is it possible to do this in C#? As far as I understand there are no guarantees on when dtor in C# will run after object goes out of scope.

Clarification: Any lock in general, spinlock, ReaderWriterLock, whatever. Calling Dispose myself defeats the purpose of the pattern - to have the lock released as soon as we exit scope - no matter if we called return in the middle, threw exception or whatnot. Also, as far as I understand using will still only queue object for GC, not destroy it immediately...

+3  A: 

It's true that you don't know exactly when the dtor is going to run... but, if you implement the IDisposable interface, and then use either a 'using' block or call 'Dispose()' yourself, you will have a place to put your code.

Question: When you say "lock", do you mean a thread lock so that only one thread at a time can use the object? As in:

lock (_myLockKey) { ... }

Please clarify.

Timothy Khouri
+11  A: 

To amplify Timothy's answer, the lock statement does create a scoped lock using a monitor. Essentially, this translates into something like this:

lock(_lockKey)
{
    // Code under lock
}

// is equivalent to this
Monitor.Enter(_lockKey)
try
{
     // Code under lock
}
finally
{
    Monitor.Exit(_lockKey)
}

In C# you rarely use the dtor for this kind of pattern (see the using statement/IDisposable). One thing you may notice in the code is that if an async exception happens between the Monitor.Enter and the try, it looks like the monitor will not be released. The JIT actually makes a special guarantee that if a Monitor.Enter immediately precedes a try block the async exception will not happen until the try block thus ensuring the release.

dpp
+5  A: 

Your understanding regarding using is incorrect, this is a way to have scoped actions happen in a deterministic fashion (no queuing to the GC takes place).

C# supplies the lock keyword which provides an exclusive lock and if you want to have different types (e.g. Read/Write) you'll have to use the using statement.

P.S. This thread may interest you.

Motti
+2  A: 

For completeness there is another way to achieve a similar RAII effect without using using and IDisposable. In C# using is usually clearer (see also here for some more thoughts), but in other languages (e.g. Java), or even in C# if using is not appropriate for some reason, it's useful to know.

It's an idiom called "Execute Around" and the idea is that you call a method that does the pre and post stuff (e.g. locking/unlocking your threads, or setting up and committing/ closing your DB connection etc), and you pass into that method a delegate that will implement the operations you want to occur in between.

e.g.:


funkyObj.InOut( delegate{ System.Console.WriteLine( "middle bit" ); } );
Depending on what the InOut method does, the output might be something like:
first bit
middle bit
last bit

As I say, this answer is for completeness only, the previous suggestions of using with IDisposable, as well as the lock keyword, are going to be better 99% of the time.

It's a shame that, while .Net has gone further than many other modern OO languages in this regards (I'm looking at you, Java), it still places the responsibility for RAII to work on the client code (ie the code that uses using), whereas in C++ the destructor will always run at the end of the scope.

Phil Nash
A: 

I've been really bothered by the fact that using is up to the developer to remember to do - at best you get a warning, which most people never bother to promote to an error. So, I've been toying with an idea like this - it forces the client to at least TRY to do things correctly. Fortunately and unfortunately, it's a closure, so the client could still keep a copy of the resource, and try to use it again later - but this code at least tries to push the client in the right direction...

public class MyLockedResource : IDisposable
{
    private MyLockedResource()
    {
        Console.WriteLine("initialize");
    }

    public void Dispose()
    {
        Console.WriteLine("dispose");
    }

    public delegate void RAII(MyLockedResource resource);

    static public void Use(RAII raii)
    {
        using (MyLockedResource resource = new MyLockedResource())
        {
            raii(resource);
        }
    }

    public void test()
    {
        Console.WriteLine("test");
    }
}

Good usage:

MyLockedResource.Use(delegate(MyLockedResource resource)
{
    resource.test();
});

Bad usage! (Unfortunately, this can't be prevented...)

MyLockedResource res = null;
MyLockedResource.Use(delegate(MyLockedResource resource)
{
    resource.test();
    res = resource;
    res.test();
});
res.test();
Matt Cruikshank