正则表达式:解析C#方法声明

4

请帮我解析以下C#方法声明中的内容:作用域、是否静态、名称、返回类型以及参数列表及其类型。例如给定如下方法声明:

public static SomeReturnType GetSomething(string param1, int param2)

等等,我需要能够解析它并获取上面的信息。 在这种情况下,

  • 名称=“GetSomething”
  • 范围=“public”
  • isStatic = true
  • returnType =“SomeReturnType”

然后是参数类型和名称对的数组。

哦,几乎忘记了最重要的部分。 它必须考虑所有其他作用域(protected,private,internal,protected internal),缺少“static”,void返回类型等。

请注意,反射不是解决方案。 我需要正则表达式。

到目前为止,我有这两个:

 (?:(?:public)|(?:private)|(?:protected)|(?:internal)|(?:protected internal)\s+)*

(?:(?:static)\s+)*

我想对于问题的其余部分,我可以只使用字符串操作而不需要正则表达式。


我不认为通过反射来获取这个会起作用,因为你只能查看代码源文件而无法访问已编译的程序集? - Nick Craver
是的,实际上我有一段代码,但我正在从中创建元数据。 - epitka
1
不要忘记,internal protected 和 protected internal 一样有效。那么,声明为 /* public */ void foo() 的方法不是公共的,对吧?你确定你还想使用正则表达式吗?如果你确实想使用,我可以帮助你,但你必须提供更多关于你的限制的细节,因为使用正则表达式解析 完整的 C# 方法声明是绝对不可能的,正如许多人已经告诉过你的那样。 - Igor Korkhov
1
那么关于直接标识符呢?public void @static(bool @private) - kvb
很抱歉,@NickCraver,我没有找到你的例子。你能告诉我在哪里吗?谢谢! - Amadeus Sanchez
显示剩余6条评论
5个回答

8
关于你的问题,我有一些想法:
可以被特定正则表达式匹配的字符串集合称为“正则语言”。在任何版本的C#中,合法方法声明的字符串集合都不是正则语言。如果你试图找到一个正则表达式,它可以匹配每个合法的C#方法声明并拒绝每个非法的C#方法声明,那么你可能会失望。
更普遍地说,除了最简单的匹配问题,正则表达式几乎总是一个坏主意。(抱歉Jeff。)更好的方法是先编写一个词法分析器,将字符串分解成一系列标记,然后分析标记序列。(使用正则表达式作为词法分析器的一部分并不是一个可怕的想法,尽管你可以不用它们。)
我还注意到,你忽略了解析方法声明中的很多复杂性。你没有提到:
- 泛型/数组/指针/可空返回类型和形式参数类型 - 泛型类型参数声明 - 泛型类型参数约束 - 不安全的/外部的/新的/重写的/虚拟的/抽象的/密封的方法 - 显式接口实现方法 - 方法/参数/返回值属性 - 部分方法——稍微棘手一点,部分是一个上下文关键字 - 注释
我还注意到,你没有说明你是否保证方法签名已经正确,或者你需要识别错误的方法并生成有关其错误原因的诊断信息。那是一个更难的问题。
首先,你为什么要这样做?正确地完成这个任务需要相当多的工作。也许有更简单的方法来得到你想要的东西?

感谢Eric提供如此详细的帖子,介绍了我可能遇到的问题,但是你提到的所有复杂情况都不需要处理,因为在这种情况下它们都是无效的(我在评论区澄清了这一点,我的错)。我根本不需要保证方法签名是正确的,因为它会在编译时失败。我认为使用上述两个正则表达式和一些字符串分割就可以了。 - epitka
至于更简单的方法,我认为除了创建一个虚拟类,在单独的应用程序域中编译它,然后对其进行反射等操作外,没有其他方法。但这对我在这里所需的内容来说只是过度杀伤力。而且所有这些都需要在中等信任的 Web 环境中工作。 - epitka
1
所以,@epitka,你的意思是你正在定义自己的C#子集,并为其编写识别器。由于我和其他任何人都不知道您的新语言规范是什么,因此我们很难帮助您编写识别器。我的建议是,您首先编写要识别的语言的语法,证明它是C#语言的子集,然后编写新语法的识别器。 - Eric Lippert
@epitka:如果在您的情况下这些输入是无效的,那么您需要做出选择。您要么(1)检测无效输入并给出错误提示,要么(2)允许已知无效的用户提供数据进入您的系统。后者似乎很危险;我更喜欢设置一个识别器来识别和拒绝无效输入,而不是一直进行并希望一切顺利。如果您选择更安全的选项,则需要编写一个可以将有效输入与无效输入分开的识别器;同样,您需要为一组有效输入编写语法。 - Eric Lippert
我会让无效的输入进入系统。这里有一点背景信息,我正在构建一个元数据驱动的建模工具,基本上会根据元模型为用户生成所有内容(UI、领域、DAO、NH映射等)。其中一个目标是C#,因此用户必须遵守一些简单的规则,以便在这个阶段成功编译代码。我还有很多其他事情需要做,写一个识别器只会让我事倍功半,所以我可以接受无效的输入,之后要么忽略它们,要么让C#编译器对其进行警告。 - epitka

1

我不建议使用正则表达式。当你需要解释方法参数时,它会变得非常混乱(例如refout关键字)。我不知道你是否还需要支持属性表示法,但这可能会让整个过程变得一团糟。

也许 C# 解析库可以帮到你。我在互联网上找到了几个:

或者,你可以先将代码传递给编译器,在运行时使用反射来操作新创建的程序集。虽然速度较慢,但几乎保证是正确的。即使你似乎反对使用反射,这仍然可能是一个可行的解决方案。

像这样:

List<string> referenceAssemblies = new List<string>()
{
    "System.dll"
    // ...
};

string source = "public abstract class TestClass {" + input + ";}";

CSharpCodeProvider codeProvider = new CSharpCodeProvider();

// No assembly name specified
CompilerParameters compilerParameters =
    new CompilerParameters(referenceAssemblies.ToArray());
compilerParameters.GenerateExecutable = false;
compilerParameters.GenerateInMemory = false;

CompilerResults compilerResults = codeProvider.CompileAssemblyFromSource(
    compilerParameters, source);

// Check for successful compilation here

Type testClass = compilerResults.CompiledAssembly.GetTypes().First();

然后在testClass上使用反射。

编译应该是安全的,不需要输入验证,因为您没有执行任何代码。您只需要进行非常基本的检查,例如确保只输入了一个方法签名。


“ref”和“out”也不受支持。 - epitka

0
string test = @"public static SomeReturnType GetSomething(string param1, int param2)";
var match = Regex.Match(test, @"(?<scope>\w+)\s+(?<static>static\s+)?(?<return>\w+)\s+(?<name>\w+)\((?<parms>[^)]+)\)");
Console.WriteLine(match.Groups["scope"].Value);
Console.WriteLine(!string.IsNullOrEmpty(match.Groups["static"].Value));
Console.WriteLine(match.Groups["return"].Value);
Console.WriteLine(match.Groups["name"].Value);
List<string> parms = match.Groups["parms"].ToString().Split(',').ToList();
parms.ForEach(x => Console.WriteLine(x));
Console.Read();

对于带有逗号的参数出现错误,但也很可能处理它。


0
(?<StringRepresentation>\A\s*(?:(?:(?<Comment>(?://.*\n)|(?:/\*(?:[\w\d!@#$%^&*()\[\]<>,.;\\"':|{}`~+=-_?\s]*)?\*/))|(\[\s*(?<Attributes>\w*)[^\[\]]*?\]))\s*)*?(?:(?:(?<Access>protected\s+internal|internal\s+protected|private|public|protected|internal)\s+)?(?:(?<InheritanceModifier>new|abstract|override|virtual)\s+)?(?:(?<Static>static)\s+)?(?:(?<Extern>extern)\s+)?(?:partial\s+)?)+(?:(?<Type>\w+(?:[\w,.\?\[\]])*?(?:\<.*>)*?)\s+)?(?<Operator>operator\s+)?\s*(?<Name>~?(?:[\w\=+\-\!\~\d\.])+?)\s*(?:\<(?:\w\.*\d*\,*\s*)+\>)*\s*\((?<Parameters>(?:[^()])*?)\)\s*(?:where\s+.+)?\s*(?:\:\s*(?:this|base)\s*(?:\(?[^\(\)]*(?:(?:(?:(?<OpenC>\()[^\(\)]*)+(?:(?<CloseC-OpenC>\))[^\(\)]*?)+)*(?(OpenC)(?!))\)))\s*)?(?:;|(?<ah>\{[^\{\}]*(?:(?:(?:(?<Open>\{)[^\{\}]*)+(?:(?<Close-Open>\})[^\{\}]*?)+)*(?(Open)(?!))\}))))

我个人不能为此而自豪,但是制作 Regionerate(开源)的那个人想出了这个方法,对于一般的解析方法来说效果非常好。


0

根据您提供的规则,最好使用一系列正则表达式而不是尝试想出一个单一的表达式。那个表达式会非常庞大。

如果您坚持要使用单一表达式,则需要使用一个使用分组、前瞻和后顾的正则表达式。

http://www.regular-expressions.info/lookaround.html

即使您只想从中解析出有限的范围,仍需要一些非常具体的指南来涵盖所有可能性。

我可以使用多个正则表达式。 - epitka

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