Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do Sentry pattern in C#?

In C++, I'm used to using the sentry pattern to ensure that resources acquired are properly released when the block or function exits (see here for example). I used this, for example, for grabbing the graphics state, and then I can do whatever I want to that state, and it gets put back when the sentry object (reference) goes out of scope.

Now I'm using C#, and the same trick doesn't work, because the destructor doesn't get called until who-knows-when later.

Is there some OTHER method that is guaranteed to fire when an object's last reference is released? Or do I just have to remember to call some Restore() or Apply() or DoYourThing() method before returning from any method where I would otherwise use a sentry?

like image 207
Joe Strout Avatar asked Sep 24 '14 15:09

Joe Strout


3 Answers

C# has finalizers, like C++, that are called when an object is destroyed. They are in the same form as C++, that is:

~ClassName()
{
}

As you know, though, C# does not have deterministic memory management, and so this isn't really a great guarantee. AS a direct result, you should not use finalizers for releasing unmanaged resources in C#. Instead, we use the IDisposable interface. This exposes a single method on the implementor, Dispose, which is intended to be called when you want to release unmanaged resources.

public class MyDisposable : IDisposable
{
    public void Dispose()
    {
        // get rid of some expensive unmanaged resource like a connection
    }
}

We can also use the using sugar to allow semantic invocation of Dispose() when the using block terminates (gracefully or not).

using(var disposable = new MyDisposable)
{
    // Do something with the disposable
} // Dispose is invoked here

Should you find yourself using finalizers and Dispose, you can consider using the GC.SuppressFinalize method, although that's a bit out of my scope. There was a really good discussion elsewhere on StackOverflow about this here

This can be used to perform RAII-esque trickery, of course, such as

using(var guard = new Guard(resource))
{

}
like image 99
Dan Avatar answered Nov 12 '22 21:11

Dan


C# provides the using block for this case, which works on any object that implements IDisposable.

For example, if you have a type class Foo : IDisposable then you can do this:

using (new Foo(/* ... */)) {
    // Your code that depends on this resource.
}

This is equivalent to:

Foo x = new Foo(/* ... */);
try {
    // Your code
} finally {
    if (x != null) {
        ((IDisposable)x).Dispose();
    }
}

Except, of course, that you don't have access to x. You can, however, gain such access if you need it by creating a variable in the using block:

using (Foo foo = new Foo(/* ... */)) {
    // Your code that uses "foo"
}

The standard way of creating a class that can be disposed of this way or by the garbage collector is:

class Foo : IDisposable {
    protected virtual void Dispose(bool disposing) {
        // Your cleanup code goes here.
    }

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

    ~Foo() {
        Dispose(false);
    }
}
like image 4
cdhowie Avatar answered Nov 12 '22 22:11

cdhowie


To implement deterministic deallocation of unmanaged resources, you would use a Dispose pattern. For many cases you can combine this with a using block if you want this to occur within a specific scope.

File handles or handles to a graphics device would usually be considered a unmanaged resource. These are resources that the .NET framework does not track references for and does not automatically deallocate.

If it is managed resource, just references to C# objects/lists, then usually deterministic deallocation is not necessary, and the garbage collector can usually handle these more efficiently than you can. Don't worry so much about when managed resources are deallocated. If you no longer have a reference to them, then they should be of no concern. The concern comes when you are done with something but somehow have a lingering reference to it that is preventing the GC from deallocating it.

like image 2
AaronLS Avatar answered Nov 12 '22 22:11

AaronLS