我正在使用 Reflector 查看 Roslyn 2012 年 9 月 CTP,我注意到语法树的以下深度优先遍历:
private IEnumerable<CommonSyntaxNode> DescendantNodesOnly(TextSpan span,
Func<CommonSyntaxNode, bool> descendIntoChildren, bool includeSelf)
{
if (includeSelf && IsInSpan(span, FullSpan))
{
yield return this;
}
if ((descendIntoChildren != null) && !descendIntoChildren(this))
{
yield break;
}
var queue = new Queue<StrongBox<IEnumerator<CommonSyntaxNode>>>();
var stack = new Stack<StrongBox<IEnumerator<CommonSyntaxNode>>>();
stack.Push(new StrongBox<IEnumerator<CommonSyntaxNode>>(ChildNodes().GetEnumerator()));
while (stack.Count > 0)
{
var enumerator = stack.Peek();
StrongBox<IEnumerator<CommonSyntaxNode>> childEnumerator;
if (enumerator.Value.MoveNext())
{
var current = enumerator.Value.Current;
if (IsInSpan(span, current.FullSpan))
{
yield return current;
if ((descendIntoChildren == null) || descendIntoChildren(current))
{
childEnumerator = queue.Count == 0
? new StrongBox<IEnumerator<CommonSyntaxNode>>()
: queue.Dequeue();
childEnumerator.Value = current.ChildNodes().GetEnumerator();
stack.Push(childEnumerator);
}
}
}
else
{
childEnumerator = stack.Pop();
childEnumerator.Value = null;
queue.Enqueue(childEnumerator);
}
}
}
我猜测队列是为了缓解运行时分配和取消分配这么多
IEnumerator<CommonSyntaxNode>
实例的情况。
但是,我不确定为什么
IEnumerator<CommonSyntaxNode>
被包裹在StrongBox<>
中。将通常是值类型的 IEnumerator<CommonSyntaxNode>
包装在引用类型 StrongBox<>
中涉及到什么样的性能和安全权衡?
CommonSyntaxNode
是一个抽象类,包含很多值类型,可以继承到一个大对象中。
IEnumerator<CommonSyntaxNode>
仅包含对CommonSyntaxNode
的引用,因此看起来CommonSyntaxNode
的大小不会影响Enumerator
的大小,因为它只是一个引用,但是:
由于
IEnumerator<T>
的方法MoveNext()
使用yield return;
,Enumerator
中的每次迭代都会导致该方法保存其状态直到下一次迭代。
由于整个方法状态足够重,并且它可能包含
CommonSyntaxNode
的属性以便执行 MoveNext()
逻辑,因此整个 IEnumerator<CommonSyntaxNode>
可能会占用大量内存。
使用
StrongBox<>
会导致 Queue
或 Stack
只保留一个小的引用对象(StrongBox<>
而不是潜在的占用大量内存的 IEnumerator<CommonSyntaxNode>
- 因此 - GC 正在清理 Queue
或Stack
包含内存中的 IEnumerator<CommonSyntaxNode>
,速度可能更快 - 减少应用程序总内存消耗。
请注意,
CommonSyntaxNode
的枚举器是一个结构体,直接使用它意味着深度复制整个结构体,它是一个小结构体,所以它不是很重,但仍然......
StrongBox<T>
的优点是一旦一个项目出列,StrongBox就会清除它的内部内容 - 因此GC可以收集StrongBox持有的T实例,而Queue<T>
最终只持有StrongBox的一个实例(而不是 T 的实例)。
使用 IEnumerator 是一个错误。该代码应该使用 ChildSyntaxList.Enumerator,它是一个结构。 StrongBox 的使用是为了性能,以避免在枚举器发生变化时需要从堆栈末尾推送和弹出枚举器。