I have some C++ code that I'm trying to port to C#. The original author had a cool indented debugging C++ class that works like this:
void a()
{
indented_debug id;
id.trace("I'm in A");
}
void b()
{
indented_debug id;
id.trace("I'm in B");
a();
}
void e()
{
a();
}
void d()
{
e();
}
void c()
{
indented_debug id;
id.trace("I'm in C");
a();
b();
{
indented_debug id2;
id2.trace("I'm still in C");
d();
}
}
And what you see in the output is this:
I'm in C
I'm in A
I'm in B
I'm in A
I'm still in C
I'm in A
Which makes it really easy to see not only the order in which functions are being called, but who's calling whom. The indentation (which is the key thing here) is automatically handled by the construction & destruction of the "indented_debug" objects. Every time an "indented_debug" object is constructed, it increments a "how much shall I indent" counter; every time an "indented_debug" object is destructed, it decrements that counter. It's this automatic calculation of the indentation that is the key to this class.
Of course, C# doesn't like this at all. C# goes out of its way to make sure that you are absolutely completely prevented from knowing when a variable has gone out of scope. And yes, I know how garbage collection works, and I do like it, but it seems like Microsoft could have given us a function IsThisObjectUnreachable() or something like that. Or, an attribute keyword [RefCount] that means "Do reference counting on this object instead of garbage collection".
I can't find any way to know enough about an object to know whether it's gone out of scope, is there some clever way to provide this same functionality in C#?
I should also throw in this design restriction: I would really rather not wrap all of my functions in "using (indented_debug id = new id) { }", the idea is to have this debugging capability with as little impact on the code and its readability is possible.
[Added later]
It's a little tricky, adding to the original question later like this, but I need to write some code and can't do that in comments.
The StackTrace method is oh-so-close to the solution I was looking for, let me explain what it looks like.
public class indented_debug
{
static int minFrame = 999;
static void trace(string text)
{
StackTrace stackTrace = new StackTrace();
StackFrame[] frames = stackTrace.GetFrames();
if (frames.Length < minFrame)
minFrame = frames.Length;
String indent = new String(' ', (frames.Length - minFrame) * 3);
Debug.WriteLine(indent + text);
}
}
This is extra cool because you don't even need to construct an object of type indented_debug - the indentation is completely controlled by how deep in the stack you are. The drawback, of course, is that in my example when c() calls d(), there are two extra stack frames in there where there is no tracing happening, so the indentation would be more than is required. Rob has suggested a way around this by adding a custom attribute to the methods, which does solve that problem (I didn't include his code in my example, you can read it below).
But there is another issue, the StackTrace concept doesn't allow for additional indentation inside a function (like I have in my original c() function). I was thinking that the number of times where the code has extra indents inside a function is quite small, so it's probably acceptable to add the "using" block in those cases. That means the C# code looks like this:
[IndentLog]
void a()
{
indented_debug.trace("I'm in A");
}
[IndentLog]
void b()
{
indented_debug.trace("I'm in B");
a();
}
void e()
{
a();
}
void d()
{
e();
}
[IndentLog]
void c()
{
indented_debug.trace("I'm in C");
a();
b();
using (indented_debug id = new indented_debug())
{
indented_debug.trace("I'm still in C");
d();
}
}
And then the object 'id' is constructed & finalized in a deterministic way, and I can create a data structure where I associate 'id' with the current stack frame when it constructs, and de-associate it when it finalizes.
The IDisposable
interface is the mechanism to use in C# when you need to do something deterministically when an object is no longer needed. So it's not as streamlined a C++ but it's certainly possible to do something similar:
void a()
{
using(var id = new IndentedDebug())
{
id.trace("I'm in A");
}
}
void b()
{
using(var id = new IndentedDebug())
{
id.trace("I'm in B");
a();
}
}
And add reference counting to the IndentedDebug.Dispose
method.
There may be better ways to do it with AOP or other patterns, but it is possible to do something when a variable goes out of scope.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With