C# Roslyn对合成树结构的质疑 免责声明

C# Roslyn对合成树结构的质疑 免责声明,c#,.net,roslyn,C#,.net,Roslyn,我很确定我遗漏了一些明显的东西,但即使在阅读了官方文档之后,我也不清楚Roslyn是如何创建语法树的 例子 考虑以下简单代码: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace aopsample { class UsbReadWriter { public bool ReadFromUsb()

我很确定我遗漏了一些明显的东西,但即使在阅读了官方文档之后,我也不清楚Roslyn是如何创建语法树的

例子 考虑以下简单代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace aopsample
{
    
    class UsbReadWriter
    {
       
        public bool ReadFromUsb()
        {
            return true;
        }

        public virtual bool WriteToUsb()
        {
            return true;
        }
    }
}
我为这段代码得到了一个
SyntaxTree
,并制作了类似的东西,非常粗糙和简单,但我只需要理解

它工作得很好

麻烦 但是,向
ReadFromUsb()
方法添加注释就足够了,类似这样

/// <summary>
/// Reads a data from Usb
/// </summary>
/// <returns></returns>
public bool ReadFromUsb()
{
    return true;
}
//
///从Usb读取数据
/// 
/// 
公共bool ReadFromUsb()
{
返回true;
}
ChildNodes()
调用{1}标记行,对于(?)返回0

问题: 为什么向成员函数添加注释会重置父类子语法节点的集合


我遗漏了什么?

由于注释可以出现在源代码中的任何地方,因此它们没有建模为
子节点,而子节点是为真正的语法元素保留的。相反,它们被视为
SyntaxTrivia
。在您的示例中,您应该能够查看该方法的
LeadingTrivia
,并查看注释


此外,由于这是一个XML文档注释,它可能有自己有趣的结构,因此它将被建模为自己的小树,您可以在聊天讨论之后使用
SyntaxTrivia
GetStructure()
方法获得它,我们确定问题在于要解析的代码的构造方法:

string[]lines = System.IO.File.ReadAllLines(filename); 
string tocompile = string.Join(string.Empty, lines);

将所有代码放在一行上。因此,
/
之后的所有内容都成为注释。解决方案就是使用
环境。换行符
作为连接字符,或者使用
ReadAllText
而不是
ReadAllLines
对于这种情况,使用
CSharpSyntaxWalker
会有很大帮助

以下是从博客中提取的示例,有助于在控制台上打印语法树:

 public class CustomWalker : CSharpSyntaxWalker
{
    static int Tabs = 0;
    public override void Visit(SyntaxNode node)
    {
        Tabs++;
        var indents = new String('\t', Tabs);
        Console.WriteLine(indents + node.Kind());
        base.Visit(node);
        Tabs--;
    }
}

static void Main(string[] args)
{
    var tree = CSharpSyntaxTree.ParseText(@"
        public class MyClass
        {
            public void MyMethod()
            {
            }
            public void MyMethod(int n)
            {
            }
       ");

    var walker = new CustomWalker();
    walker.Visit(tree.GetRoot());
}

您还可以通过调用语法节点上的
GetLeadingTrivia
getRailingTrivia
方法来打印琐事。

作为旁注,我认为使用LINQ会使zour代码更加清晰。我认为这通常适用于在Roslyn中选择节点。@svick:是的,好的。正如我所说的,这段代码只是“快速和肮脏”。好吧,但问题不是:我如何在孩子们之间找到注释,而是为什么添加新注释会(显然)影响语法树结构。事实上,考虑到
注释
例如
空格
预处理器dirrectives
不构成语法树结构的一部分,为什么添加/删除它们会以任何方式影响父SyntaxNode的子级?对文件的每次编辑都会使其进入不同的树。在Roslyn
SyntaxTree
SyntaxNode
中,对象是不可变的,并且完全具有代表性。一旦构建,它们就永远不会更改,并且对文件中的每一位文本进行建模。这意味着,如果对代码进行更改,每次都会返回不同的树,因为
ChildNodes
Span
属性值不同。请注意,如果在Visual Studio中编辑,我们使用增量解析策略,这样我们实际上在解析之间共享树的大部分内部存储,它只是在访问时创建的一层薄薄的包装器对象,实际上每次都是新的。白皮书很好地解释了我们在这里使用的“重新旋转”过程,但我每次都会自然地重新构建它。当我更改一个源文件时,我会停止并重建我的程序,因此,据我所知,我从一个“新”文件重建了一个语法树?你是以编程方式做的吗?如果是这样,并且您正在使用类似于
SyntaxNode.Replace
,则需要保留返回值并查看其树以查看更改的节点。
 public class CustomWalker : CSharpSyntaxWalker
{
    static int Tabs = 0;
    public override void Visit(SyntaxNode node)
    {
        Tabs++;
        var indents = new String('\t', Tabs);
        Console.WriteLine(indents + node.Kind());
        base.Visit(node);
        Tabs--;
    }
}

static void Main(string[] args)
{
    var tree = CSharpSyntaxTree.ParseText(@"
        public class MyClass
        {
            public void MyMethod()
            {
            }
            public void MyMethod(int n)
            {
            }
       ");

    var walker = new CustomWalker();
    walker.Visit(tree.GetRoot());
}