The simple answer to your question is: you can't.
using
is pure syntactic sugar. A typical using statement:
using (MyDisposableClass a = GetMyDisposableClass())
{
// ...
}
Gets translated directly into this:
MyDisposableClass a = null;
try {
a = GetMyDisposableClass();
// ...
}
finally {
if (a != null) a.Dispose();
}
In general, in .NET you can't know from whence your code has been called much beyond the function level by reflecting the callstack. The StackFrame
object from [System.Diagnostics][1]
will tell you the IL offset for the current method, so I suppose if you are really determined you could take apart the IL for the current method and try and figure out where you are within any try/finally
code, but that's sounding really flimsy and gross to me.
What on earth are you trying to do that you feel you must manage it like this?
To me it feels like what you want is a factory object of some kind:
public interface IMyClass { int Level { get; } } // whatever
public class MyClassFactory {
private delegate void Notifier(int level);
public class MyClass : IDisposable
{
public MyClass(int level, Notifier notifier)
{
_level = level;
_notifier = notifier;
}
private int _level;
private Notifier _notifier;
~MyClass() { Dispose(false); }
public int Level { get { return level; } }
public Dispose() { Dispose(true); GC.SuppressFinalize(this); }
private bool _disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!_disposed) {
if (disposing) {
notifier(Level);
_disposed = false;
}
else { throw new Exception("My class used outside using block."); }
}
}
}
private int _level = 0;
public IMyClass Make()
{
return new MyClass(_level++,
childLevel => {
if (childLevel == _level)
--_level;
else throw new Exception("Disposed out of order.");
});
}
}
What this does is build a factory that hides the constructor to MyClass and exposes a factory method to make IMyClass objects that you can use. It does a strict ordering such that objects are constructed and disposed in order, which more or less meets your nesting requirement. You can make the objects do something different based on their Level, if you so choose.
And I would still hesitate to use this code. It doesn't feel right, but at least it's harder to do something wrong as long as you only use one factory per method.
I'm guessing that what you really want to do is to have begin/end semantics in a code block that tracks nesting, but you want the compiler to manage that for you. That I've done with an object that takes two functions to call, one on begin and one on end:
public class BeginEnd<T> : IDisposable
{
private Action<T> _end;
private bool _disposed;
private T _val;
public BeginEnd(T val, Action<T> begin, Action<T> end)
{
_end = end;
_val = val;
begin(val);
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
~BeginEnd() { Dispose(false); }
protected virtual void Dispose(bool disposing)
{
if (!_disposed) {
if (disposing) {
_disposed = true;
_end(_val);
}
}
}
}
Which can then be used in a context like this:
public class Tracker {
private int _level;
public BeginEnd<int> Track()
{
return new BeginEnd<int>(_level++,
lev1 => {
Debug.WriteLine("Begin " + lev1);
},
lev2 => {
Debug.WriteLine("End " + lev2);
--_level;
});
}
}
//...
Tracker t = new Tracker();
using (var n0 = t.Track()) {
using (var n1 = t.Track()) {
}
using (var n2 = t.Track()) { }
}
// prints:
// Begin 0
// Begin 1
// End 1
// Begin 1
// End 1
// End 0
which correctly tracks the nesting of the using blocks by enforcing a begin/end rule.
Whether or not this is an appropriate use of the language construct has been debated before. I feel like your problem can be solved in another more appropriate way.