Implementing RAII in C#
Asked Answered
A

4

14

I have an InfoPath form which I need to conditionally disable it's OnChange events. Since it's not possible to bind the event handlers after the form has loaded, I'm forced to rely on a global counter which indicates whether an OnChange event should be executed. Inside each OnChange event, I check whether SuppressEventsCount == 0 before performing any actions. To suppress events during the execution of some function or another, I simply set SuppressEventsCount++, and -- again when the function exits. The biggest problem with doing this is that it's not exception safe. So I had the bright idea to wrap the SuppressEvents counter in a class which implements iDisposable

using(SuppressEvents s = new SuppressEvents()){
   // OnChange events fired here are ignored
} // OnChange events enabled again

This is working, but it's still not as ideal as a c++ solution which doesn't require the use of the "using" directive at all.

Is there some way to either:

  1. Trigger a destructor or some function the moment an object goes out of scope, OR
  2. Prevent the SuppressEvents object from being initialised outside of a "using" directive entirely
Alumnus answered 2/4, 2012 at 8:0 Comment(1)
See this question: https://mcmap.net/q/187363/-is-it-abusive-to-use-idisposable-and-quot-using-quot-as-a-means-for-getting-quot-scoped-behavior-quot-for-exception-safety/6345 (Is it abusive to use IDisposable and “using” as a means for getting “scoped behavior” for exception safety?)Sackett
H
3

In relation to question 2, it might be possible to get around it by providing a different interface to consumers of the code. Instead of providing a public class that implements IDisposable, and hoping they wrap it in a using, you could provide a static method that takes a function to execute in a "suppressed" context:

public static class EventSuppressor {
    public void Suppress(Action action) {
        using (var s = new SuppressActions()) {
            action();
        }
    }

    private class SuppressActions : IDisposable {
        ...
    }
}

Then consumers can use this as follows:

EventSuppressor.Suppress(() => {
    // OnChange events fired here are ignored
}) // OnChange events enabled again

Of course, you have to work out whether this design is appropriate, as this will result in extra function calls, compiler generated classes and closures etc.

Howrah answered 2/4, 2012 at 8:22 Comment(0)
C
13

No and no. using is the closest you can get to RAII (more accurately, we are talking about the resource release that follows a RAII-idiom object being destructed).

To answer your points more directly:

  1. IDisposable (and by extension using) was created exactly because there is no way to do that in .NET.
  2. using is syntactic sugar that gets compiled as try/finally and only requires that the object is IDisposable, so you cannot distinguish between usage inside a using statement and out of it.
Cravat answered 2/4, 2012 at 8:4 Comment(0)
H
3

In relation to question 2, it might be possible to get around it by providing a different interface to consumers of the code. Instead of providing a public class that implements IDisposable, and hoping they wrap it in a using, you could provide a static method that takes a function to execute in a "suppressed" context:

public static class EventSuppressor {
    public void Suppress(Action action) {
        using (var s = new SuppressActions()) {
            action();
        }
    }

    private class SuppressActions : IDisposable {
        ...
    }
}

Then consumers can use this as follows:

EventSuppressor.Suppress(() => {
    // OnChange events fired here are ignored
}) // OnChange events enabled again

Of course, you have to work out whether this design is appropriate, as this will result in extra function calls, compiler generated classes and closures etc.

Howrah answered 2/4, 2012 at 8:22 Comment(0)
B
2

To answer your two questions:

  1. No, there is not, Garbage Collection in .NET is not deterministic in nature
  2. No, you can not, the using clause gets translated into a try/finally block type of code, and in any case you cannot detect that it is constructed from any of those two constructs, compared to outside
Brandes answered 2/4, 2012 at 8:5 Comment(0)
S
1

If you can move from C# to C++.NET (still 100% .NET if compiled with clr:safe), then you can use msclr::auto_handle which acts just like a smart pointer such as auto_ptr etc...

What it really does behind the scene in IL is a bunch of try/fault commands but this is all completely invisible to the developer and user. The whole process is simply better IMO.

Solo answered 15/8, 2012 at 14:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.