从C#中删除行注释的正则表达式

48
我正在编写一种程序来从一些C#代码中删除块注释或行注释。我已经查看了该网站上的其他示例,但没有找到我要寻找的确切答案。
我可以使用此正则表达式(带有RegexOptions.Singleline)完全匹配块注释(/* comment */):
(\/\*[\w\W]*\*\/)
我也可以使用此正则表达式(带有RegexOptions.Multiline)完全匹配行注释(// comment):

(//((?!\*/).)*)(?!\*/)[^\r\n]

注意:我正在使用[^\r\n]而不是$,因为$也包括匹配中的\r
但是,这并不完全符合我的要求。
这是我要匹配的测试代码:
// remove whole line comments
bool broken = false; // remove partial line comments
if (broken == true)
{
    return "BROKEN";
}
/* remove block comments
else
{
    return "FIXED";
} // do not remove nested comments */ bool working = !broken;
return "NO COMMENT";

这个短语的意思是“块表达式匹配”。
/* remove block comments
else
{
    return "FIXED";
} // do not remove nested comments */

这很好,但是行表达式匹配。
// remove whole line comments
// remove partial line comments

and

// do not remove nested comments

另外,如果我在行表达式中没有两次使用“*/”正向预查,则匹配。
// do not remove nested comments *

我真的不想要那个。

我想要的是一个表达式,它将匹配从//开始到行末的字符,但在//和行末之间不包含*/

另外,只是为了满足我的好奇心,有人能解释一下为什么我需要两次前瞻吗?(//((?!\*/).)*)[^\r\n](//(.)*)(?!\*/)[^\r\n]都会包括星号,但(//((?!\*/).)*)(?!\*/)[^\r\n](//((?!\*/).)*(?!\*/))[^\r\n]不会。


4
你是否也考虑过这种情况:string foo = "http://stackoverflow.com;" - Anthony Pegram
1
你的 /* ... */ 模式由于贪婪性而过度匹配,例如考虑 /* comment1 */ not-a-comment! /* comment2 */ - polygenelubricants
你可以考虑使用C#的解析器:https://dev59.com/3HVD5IYBdhLWcg3wHnyd - TrueWill
哈哈... 对于这个问题,使用一个完整的 C# 解析器绝对是杀鸡焉用牛刀。 - Timwi
1
一个绝对无价的用于设计、理解和测试正则表达式的工具是 Expresso:http://www.ultrapico.com/Expresso.htm。 - eidylon
我很惊讶为什么没有人能够简单地创造出Visual Studio本身、Resharper或其他许多需要解析和识别代码中的注释的强大工具使用的正则表达式? - Alain
6个回答

101

你的块注释和行注释的正则表达式都存在缺陷。如果需要,我可以描述一下这些缺陷,但我觉得写新的正则表达式可能更有生产力,特别是我想写一个能够匹配两者的表达式。

事实上,每当/*//和字符串文字“干扰”彼此时,始终是最先开始匹配的那个占主导地位。这非常方便,因为这正是正则表达式的工作方式:先找到第一个匹配项。

因此,让我们定义一个正则表达式来匹配这四个标记中的每一个:

var blockComments = @"/\*(.*?)\*/";
var lineComments = @"//(.*?)\r?\n";
var strings = @"""((\\[^\n]|[^""\n])*)""";
var verbatimStrings = @"@(""[^""]*"")+";
为了回答标题中的问题(去除注释),我们需要:
  • 将块注释替换为空白
  • 将行注释替换为新行(因为正则表达式会吃掉新行)
  • 保留字面字符串不变。
Regex.Replace可以使用MatchEvaluator函数轻松完成这项任务。
string noComments = Regex.Replace(input,
    blockComments + "|" + lineComments + "|" + strings + "|" + verbatimStrings,
    me => {
        if (me.Value.StartsWith("/*") || me.Value.StartsWith("//"))
            return me.Value.StartsWith("//") ? Environment.NewLine : "";
        // Keep the literal strings
        return me.Value;
    },
    RegexOptions.Singleline);

我在Holystream提供的所有示例以及我能想到的其他各种情况上运行了此代码,它完美地工作。如果您能提供一个导致其失败的示例,我很乐意为您调整代码。


3
@Welton:你可以在结果后运行Regex.Replace(@"^(\s*\r?\n){2,}", Environment.Newline, RegexOptions.Multiline),但这将删除所有没有注释的空白双行。 - Timwi
当注释直接跟在代码后面时,不起作用:"MY_ENUM_CONSTANT=0//comment" - stackPusher
1
非常优雅的解决方案。基于您的解决方案,我在这里制作了类似的用于删除SQL注释:https://dev59.com/WGsz5IYBdhLWcg3wv6ju#33947706 - drizin
1
正如@Holystream所描述的那样,这个正则表达式将会移除urls。 - Mazdak Shojaie
@mazdak 你能给个例子吗?URL在C#中不是语法元素,所以我不知道你的意思。这个正则表达式确实可以正确处理包含URL的注释和字符串字面量。 - Timwi
显示剩余10条评论

9
您可以使用类似以下表达式的方法对代码进行分词:

您可以使用类似以下表达式的方法对代码进行分词:

@(?:"[^"]*")+|"(?:[^"\n\\]+|\\.)*"|'(?:[^'\n\\]+|\\.)*'|//.*|/\*(?s:.*?)\*/

它还可以匹配一些无效的转义/结构(例如'foo'),但可能会匹配所有感兴趣的有效令牌(除非我忘记了什么),因此适用于有效代码。

在替换中使用它并捕获您想要保留的部分将为您提供所需的结果。即:

static string StripComments(string code)
{
    var re = @"(@(?:""[^""]*"")+|""(?:[^""\n\\]+|\\.)*""|'(?:[^'\n\\]+|\\.)*')|//.*|/\*(?s:.*?)\*/";
    return Regex.Replace(code, re, "$1");
}

Example app: 例子应用
using System;
using System.Text.RegularExpressions;

namespace Regex01
{
    class Program
    {
        static string StripComments(string code)
        {
            var re = @"(@(?:""[^""]*"")+|""(?:[^""\n\\]+|\\.)*""|'(?:[^'\n\\]+|\\.)*')|//.*|/\*(?s:.*?)\*/";
            return Regex.Replace(code, re, "$1");
        }

        static void Main(string[] args)
        {
            var input = "hello /* world */ oh \" '\\\" // ha/*i*/\" and // bai";
            Console.WriteLine(input);

            var noComments = StripComments(input);
            Console.WriteLine(noComments);
        }
    }
}

输出:

hello /* world */ oh " '\" // ha/*i*/" and // bai
hello  oh " '\" // ha/*i*/" and

1
等等,为什么我在问题已经被提出、回答和接受两年后才回答它?而且还是给出了几乎相同的答案?这怎么会出现在我的列表中呢?一定是有些 bug 或者其他问题,我可不会做这种事情。(哈哈) - Qtax
我发现这是我(C#)的完美答案,但是正则表达式在JavaScript上不起作用。 - Gongdo Gong

8
在实施此操作之前,您需要先创建测试用例。
接下来是需要翻译的内容:
  1. 简单的注释形式 /* */, //, ///
  2. 多行注释 /* This\nis\na\ntest*/
  3. 代码行后的注释 var a = "apple"; // test or /* test */
  4. 嵌套注释 /* This // is a test /, or // This / is a test */
  5. 非注释形式但看起来像注释,并出现在引号内的内容 var comment= "/* This is a test*/", 或 var url = "http://stackoverflow.com";
  6. 复杂的非注释形式并看起来像注释:var abc = @" this /* \n is a comment in quote\n*/",带或不带双引号中的空格,以及 /* 或 */ 之间的空格。
可能还有其他情况存在。
一旦您掌握了所有情况,就可以为每种情况创建解析规则,或者将其中一些情况分组。
仅使用正则表达式可能会非常困难和容易出错,难以测试,并且对您和其他程序员的维护也很困难。

@Timwi:实际上,.NET使用词法分析器。注释符号只是标记。http://en.wikipedia.org/wiki/Lexical_analysis - chilltemp
@Timwi:你能否给我一个符合上述情况的示例?我非常想知道一个可以通过这些测试用例的正则表达式。 /*(.?)*/|//.?\r?\n 无法通过许多测试用例。 - Holystream
@Holystream:你试过我回答中的正则表达式吗?你似乎从中删除了两个反斜杠。如果我的正则表达式失败,请提供一个具体的示例,说明它失败了,并在我的答案上发表评论,而不是这个答案。谢谢! - Timwi
@chilltemp:就是我说的,“lexer”是“lexical analyzer”的缩写。 - Timwi
@Holystream:我尝试了这两个例子,它们对我来说都很好用。这是我用过的完整代码,你可以玩一下。 - Timwi
显示剩余4条评论

2

我在http://gskinner.com/RegExr/上找到了这个(名为“.Net Comments aspx”)

(//[\t|\s|\w|\d|\.]*[\r\n|\n])|([\s|\t]*/\*[\t|\s|\w|\W|\d|\.|\r|\n]*\*/)|(\<[!%][ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t%]*)\>)

当我测试它时,它似乎删除了所有的//注释和/*注释*/,并且保留了引号内的内容。
虽然这是一行可怕的庞大正则表达式,但我还没有做过太多测试,但似乎运行得相当不错。

好的,经过一些测试,我注意到它在包含减号(-)和多个多行注释(/* comment / not comment / comment again*/)的注释中存在问题。但是如果有人愿意修复这个问题,我认为这是一个相当不错的解决方案。 - einord

1

对于块级注释(/* ... */),您可以使用以下表达式:

/\*([^\*/])*\*/

它也适用于多行注释。


我可以问一下为什么要降低这个答案的评分吗? - Guy P

0

还有请看我关于C#代码压缩的项目:CSharp-Minifier

除了删除注释、空格和换行符之外,目前它能够压缩局部变量名并进行其他压缩。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接