我有一些C ++代码,我正在尝试移植到C#。原作者有一个很酷的缩进调试C ++类,其工作原理如下:
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();
}
}
你在输出中看到的是:
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
这使得很容易看到不仅调用函数的顺序,而且调用谁的函数。压痕(这是关键的东西)由结构和工具自动处理。破坏“indented_debug”对象。每次构造一个“indented_debug”对象时,它会递增一个“我应该缩进多少”的计数器;每次“indented_debug”对象被破坏时,它会递减该计数器。这是自动计算缩进,这是该类的关键。
当然,C#完全不喜欢这个。 C#不遗余力地确保您完全无法知道变量何时超出范围。是的,我知道垃圾收集是如何工作的,我确实喜欢它,但似乎微软可以给我们一个函数IsThisObjectUnreachable()或类似的东西。或者,属性关键字[RefCount]表示“对此对象进行引用计数而不是垃圾回收”。
我找不到任何方法来了解一个对象,知道它是否超出范围,是否有一些聪明的方法在C#中提供相同的功能?
我还应该抛出这个设计限制:我真的不想把我的所有函数都包装在“using(indented_debug id = new id){}”中,我的想法就是拥有这个调试功能,对它的影响很小代码及其可读性是可能的。
[稍后添加]
这有点棘手,稍后会像这样添加原始问题,但我需要编写一些代码而不能在评论中这样做。
StackTrace方法与我正在寻找的解决方案非常接近,让我解释一下它的样子。
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);
}
}
这是非常酷的,因为您甚至不需要构造一个类型为indented_debug的对象 - 缩进完全由您在堆栈中的深度控制。当然,缺点是在我的例子中,当c()调用d()时,在那里有两个额外的堆栈帧,其中没有发生跟踪,因此缩进将超过所需的。 Rob已经通过向方法添加自定义属性来提出解决方法,这确实解决了这个问题(我的示例中没有包含他的代码,您可以在下面阅读)。
但还有另一个问题,StackTrace概念不允许在函数内部进行额外的缩进(就像我在原始的c()函数中那样)。我认为代码在函数内部有额外缩进的次数非常少,因此在这些情况下添加“using”块可能是可以接受的。这意味着C#代码如下所示:
[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();
}
}
然后构造对象'id'并且&amp;以确定的方式完成,我可以创建一个数据结构,在构造时将'id'与当前堆栈帧相关联,并在最终确定时将其解除关联。
答案 0 :(得分:3)
当需要在不再需要对象时确定性地执行某些操作时,IDisposable
接口是在C#中使用的机制。所以它不像C ++那样精简,但它确实可以做类似的事情:
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();
}
}
并将引用计数添加到IndentedDebug.Dispose
方法。
使用AOP或其他模式可能有更好的方法,但当变量超出范围时,可能做某事。
答案 1 :(得分:2)
根据这个社区的所有重要意见,这就是我要去的地方(至少目前为止)。对不起,如果匈牙利的符号冒犯了你,我在MFC-land上花了很多时间,仍然有点喜欢它。
internal class IndentFrame
{
public string m_type, m_function;
public bool m_bOutput = false;
public int m_nExtra = 0;
public IndentFrame(StackFrame frame)
{
m_type = frame.GetMethod().DeclaringType.Name;
m_function = frame.GetMethod().ToString();
}
public bool Matches(StackFrame frame)
{
return (m_type == frame.GetMethod().DeclaringType.Name)
&& (m_function == frame.GetMethod().ToString());
}
}
public class IndentDebug : IDisposable
{
internal static List<IndentFrame> m_frames = new List<IndentFrame>();
public static void WriteLine(string text)
{
UpdateFrames();
// Remember that this frame produced output.
m_frames[m_frames.Count - 1].m_bOutput = true;
// How much indent?
int nIndent = 0;
foreach (IndentFrame frame in m_frames)
nIndent += (frame.m_bOutput ? 1 : 0) + frame.m_nExtra;
String indent = new String(' ', (nIndent - 1) * 3);
Debug.WriteLine(indent + text);
}
internal static void UpdateFrames()
{
StackTrace stackTrace = new StackTrace();
StackFrame[] frames = stackTrace.GetFrames();
// frames[] are ordered such that the current frame is at [0] but we
// want the topmost frame in [0]
Array.Reverse(frames);
// Remove any obsolete frames from our list
int i;
for (i = 0; i < Math.Min(m_frames.Count, frames.Length); i++)
{
if (frames[i].GetMethod().DeclaringType == typeof(IndentDebug))
break;
if (!m_frames[i].Matches(frames[i]))
break;
}
if (i < m_frames.Count)
m_frames.RemoveRange(i, m_frames.Count - i);
// Add any new frames
while (m_frames.Count < frames.Length)
{
if (frames[m_frames.Count].GetMethod().DeclaringType == typeof(IndentDebug))
break;
IndentFrame frame = new IndentFrame(frames[m_frames.Count]);
m_frames.Add(frame);
}
}
internal static void UpdateIndent(int add)
{
UpdateFrames();
m_frames[m_frames.Count - 1].m_nExtra += add;
}
public IndentDebug(string text)
{
IndentDebug.UpdateIndent(1);
IndentDebug.WriteLine(text);
}
public void Dispose()
{
IndentDebug.UpdateIndent(-1);
}
}
}
这非常接近我原来的愿望,在大多数情况下,您只需在函数中添加一行(无需额外的自定义属性):
void a()
{
IndentDebug.WriteLine("I'm in A");
}
但是如果你想在函数中加入额外的缩进,你可以通过添加“using”语句来获得它:
void c()
{
IndentDebug.WriteLine("I'm in C");
a();
b();
using (IndentDebug id = new IndentDebug("I'm still in C"))
{
d();
}
}
答案 2 :(得分:1)
这是我在LinqPad中编写的一些代码,它可以满足您的需求:
void Main()
{
c();
}
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();
}
}
void e()
{
a();
}
void d()
{
e();
}
void c()
{
using(var id = new IndentedDebug())
{
id.Trace("I'm in C");
a();
b();
{
using(var id2 = new IndentedDebug())
{
id2.Trace("I'm still in C");
d();
}
}
}
}
class IndentedDebug : IDisposable
{
const int indentSize = 2;
const char indentChar = ' ';
static int indentLevel = 0;
private string _indentSpaces;
public IndentedDebug()
{
_indentSpaces = new string(indentChar, indentSize * indentLevel);
++indentLevel;
}
public void Trace(string message)
{
Console.WriteLine("{0}{1}", _indentSpaces, message);
}
public void Dispose()
{
--indentLevel;
}
}
所以你的代码看起来并不相同,但另一方面,它没有做任何&#34;魔术&#34;或者 - 代码本身会在缩进调试结束时显示。
答案 3 :(得分:1)
就像我在上一段中所说的那样,这不是理想的行为。与在“使用”块中包装整个函数相比,在函数顶部插入几行的影响要小得多。使人们对调试代码对整体功能的影响不那么担心。
来自OP的评论。
现在,在C#8中,您可以使用使用声明。
示例:
using System;
using System.Runtime.CompilerServices;
namespace SO_36796820
{
public class OutOfScopeEvent
{
public string MethodName { get; set; }
public int StackLevel { get; set; }
}
public delegate void NotifyInScope(OutOfScopeEvent obj);
public delegate void NotifyOutOfScope(OutOfScopeEvent obj);
public class OutOfScopeToken : IDisposable
{
private readonly string _methodName;
public event NotifyOutOfScope NotifyOutOfScope;
private readonly int _stackLevel;
public OutOfScopeToken(string methodName = "", int stackLevel = 0)
{
_methodName = methodName;
_stackLevel = stackLevel;
}
public void Dispose()
{
NotifyOutOfScope?.Invoke(new OutOfScopeEvent() { MethodName = _methodName, StackLevel = _stackLevel });
}
}
public class OutOfScope : IDisposable
{
private readonly OutOfScopeToken _outOfScopeToken;
public event NotifyOutOfScope NotifyOutOfScope;
public event NotifyInScope NotifyInScope;
private int _stackLevel = 0;
public OutOfScope()
{
NotifyOutOfScope += OnOutOfScope;
}
public OutOfScopeToken GetToken([CallerMemberName] string methodName = nameof(GetToken))
{
NotifyInScope?.Invoke(new OutOfScopeEvent()
{
MethodName = methodName,
StackLevel = _stackLevel
});
var outOfScopeToken = new OutOfScopeToken(methodName, _stackLevel);
outOfScopeToken.NotifyOutOfScope += NotifyOutOfScope;
_stackLevel++;
return outOfScopeToken;
}
private void OnOutOfScope(OutOfScopeEvent obj)
{
_stackLevel--;
}
public void Dispose()
{
_outOfScopeToken?.Dispose();
}
}
class Program
{
private static OutOfScope _outOfScope;
static void a()
{
using var v = _outOfScope.GetToken();
b();
c();
}
static void b()
{
using var v = _outOfScope.GetToken();
}
static void c()
{
using var v = _outOfScope.GetToken();
d();
}
static void d()
{
using var v = _outOfScope.GetToken();
}
static void Main(string[] args)
{
_outOfScope = new OutOfScope();
_outOfScope.NotifyOutOfScope += OutOfScope;
_outOfScope.NotifyInScope += InScope;
a();
}
private static void InScope(OutOfScopeEvent obj)
{
Console.WriteLine(new String('\t', obj.StackLevel) + " I'm in " + obj.MethodName);
}
private static void OutOfScope(OutOfScopeEvent obj)
{
Console.WriteLine(new String('\t', obj.StackLevel) + " I'm out of " + obj.MethodName);
}
}
}
输出:
I'm in a
I'm in b
I'm out of b
I'm in c
I'm in d
I'm out of d
I'm out of c
I'm out of a
答案 4 :(得分:0)
为什么不看看StackTrace类
https://msdn.microsoft.com/en-us/library/system.environment.stacktrace%28v=vs.110%29.aspx
它确切地告诉您调用哪些方法来获取当前点以及许多其他内容。
如果您仍然可以访问成员变量,那么从技术上讲,它仍然在范围内,除非您手动对其进行描述或处理。因此,如果您了解代码中的变量,那么它不会超出范围。
答案 5 :(得分:-1)
有一种非常聪明的方法可以做到这一点:
除了首先了解范围如何工作之外,您的&#34; id.trace()&#34;方法看起来更像是控制台输出。你可以用Console.WriteLine轻松替换它(&#34;我在...&#34;);并获得相同的结果。