基于Roslyn提取C#代码中分组单行注释的技术问询
合并连续单行注释的Roslyn实现方案
我之前也遇到过类似的需求,用Roslyn处理注释的时候,连续的单行//注释被拆成了多个单独的SingleLineCommentTrivia,确实有点麻烦。这里分享一个我验证过的实现思路,核心是通过注释的位置关系来分组合并:
核心思路
Roslyn会将每一行//注释标记为独立的语法 trivia,但连续的单行注释在文本上是相邻的(中间只有换行、空格这类空白字符)。我们可以通过以下步骤来分组:
- 遍历语法树,收集所有的
SingleLineCommentTrivia; - 按注释在文本中的出现顺序,检查相邻两个注释之间的文本是否仅包含空白字符;
- 如果是,则将它们归为同一组,否则开启新的分组;
- 最后将每组的单行注释内容合并成一个完整的块。
具体代码实现
下面是一个完整的代码片段,假设你已经有了SyntaxTree实例:
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using System.Collections.Generic; using System.Linq; using System.Text; public static class CommentExtractor { public static List<string> GetCombinedSingleLineComments(SyntaxTree tree) { var root = tree.GetRoot(); // 收集所有单行注释trivia,并按文本出现顺序排序 var singleLineComments = root.DescendantTrivia() .Where(t => t.IsKind(SyntaxKind.SingleLineCommentTrivia)) .OrderBy(t => t.Span.Start) .ToList(); var combinedComments = new List<string>(); if (!singleLineComments.Any()) return combinedComments; // 初始化第一个分组的内容构建器 var currentGroupBuilder = new StringBuilder(); currentGroupBuilder.AppendLine(singleLineComments[0].ToString().TrimStart('/').Trim()); for (int i = 1; i < singleLineComments.Count; i++) { var prevComment = singleLineComments[i - 1]; var currentComment = singleLineComments[i]; // 获取两个注释之间的文本范围 var betweenSpan = TextSpan.FromBounds(prevComment.Span.End, currentComment.Span.Start); var betweenText = tree.GetText().GetSubText(betweenSpan).ToString(); // 判断中间是否只有空白字符(换行、空格、制表符等) if (string.IsNullOrWhiteSpace(betweenText)) { // 加入当前分组,去掉开头的//并修剪前后空格 currentGroupBuilder.AppendLine(currentComment.ToString().TrimStart('/').Trim()); } else { // 保存当前分组,开始新分组 combinedComments.Add(currentGroupBuilder.ToString().TrimEnd()); currentGroupBuilder.Clear(); currentGroupBuilder.AppendLine(currentComment.ToString().TrimStart('/').Trim()); } } // 加入最后一个未保存的分组 combinedComments.Add(currentGroupBuilder.ToString().TrimEnd()); return combinedComments; } }
额外说明
- 这个方法会自动忽略注释之间的空白(包括多个换行、空格、制表符),只要它们没有被非空白内容隔开,就会被合并成一个块;
- 如果需要区分不同代码块中的连续注释(比如类上面的连续注释和方法内部的),可以在收集trivia的时候,额外记录它们所属的
SyntaxNode,然后按节点分组后再处理; - 如果你想保留注释的原始格式(比如
//后面的缩进),可以去掉代码中的Trim(),只保留TrimStart('/')来移除开头的注释符号。
这样处理后,你就能把连续的单行注释作为一个整体提取出来,和多行注释、文档注释的处理逻辑对齐了。
内容的提问来源于stack exchange,提问作者Makilll
相关产品推荐
相关产品推荐

