在本文中,将深入探讨Visual FA的源代码生成器,这是一个强大的工具,它允许开发者在编译时注入额外的动态生成的代码。这种技术被称为源代码生成器,它为C#语言提供了额外的功能扩展,尽管使用起来可能有些奇特。
C#9引入了一种能力,允许在C#编译过程中挂钩编译器,并在编译二进制文件的过程中注入额外的动态创建的代码。这项技术被称为源代码生成器,它们是增强语言功能的强大方式,即使偶尔使用起来有些古怪。
在源代码生成器的核心是FARuleAttribute
:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
class FARuleAttribute : System.Attribute
{
public FARuleAttribute() { }
public FARuleAttribute(string expression)
{
if (expression == null)
throw new ArgumentNullException(nameof(expression));
if (expression.Length == 0)
throw new ArgumentException("The expression must not be empty", nameof(expression));
Expression = expression;
}
public string Expression { get; set; } = "";
public string BlockEnd { get; set; } = null;
public int Id { get; set; } = -1;
public string Symbol { get; set; } = null;
}
请注意,不会在库中或在源代码中找到这个属性。它是在第一次使用它之后注入到代码中的,任何支持类型也是如此。因此,当第一次使用这些时,代码中会出现红色波浪线。只需构建,因为只要代码没有问题,波浪线就会自行解决。
在代码中,定义了MyLexer
方法,它有四个规则。每个规则都有一个正则表达式——通常是第一个未命名参数,然后是几个可选的命名参数:BlockEnd
、Id
和Symbol
。每个参数代表词法分析器中的一个规则。
[FARule(@"\/\*", Id = 0, Symbol = "commentBlock", BlockEnd = @"\*\/")]
[FARule(@"\/\/[^\n]*", Id = 1, Symbol = "commentLine")]
[FARule(@"[ \t\r\n]+", Id = 2, Symbol = "whiteSpace")]
[FARule(@"[A-Za-z_][A-Za-z0-9_]*", Id = 3, Symbol = "identifier")]
internal partial FAStringRunner MyLexer(string text);
这里,定义了commentBlock
、commentLine
、whiteSpace
和identifier
作为符号,并为每个规则分配了ID,尽管不必这样做——如果未提供,它们将在生成过程中填充。如果不记得块结束的目的,请再次查看系列的第一篇文章,但基本上,它是一个用于匹配多字符结束条件的附加表达式。
方法签名本身必须是一个部分方法,在部分类中。它必须要么不接受参数,接受单个string
参数,或者接受单个TextReader
参数。它必须返回FAStringRunner
、FATextReaderRunner
、FAStringDfaTableRunner
或FATextReaderDfaTableRunner
。如果函数接受一个参数,也可以返回FARunner
。
本质上,返回类型用于确定要生成哪种类型的运行器,例如,它应该在字符串上操作还是在文本读取器上操作,以及它是编译的还是表格驱动的。
另一种选择是标记一个类。这样做的好处是让可以访问最终运行器的类型,包括它的符号常量(如果在定义中使用了Symbol
):
[FARule(@"\/\*", Id = 0, Symbol = "commentBlock", BlockEnd = @"\*\/")]
[FARule(@"\/\/[^\n]*", Id = 1, Symbol = "commentLine")]
[FARule(@"[ \t\r\n]+", Id = 2, Symbol = "whiteSpace")]
[FARule(@"[A-Za-z_][A-Za-z0-9_]*", Id = 3, Symbol = "identifier")]
partial class MyLexer : FAStringRunner { }
然后可以像这样使用它:
var exp = "the 10 quick brown #@%$! foxes jumped over 1.5 lazy dogs";
var runner = new MyLexer();
runner.Set(exp);
foreach (var match in runner)
{
Console.WriteLine(match);
}
还可以执行类似if(match.SymbolId == FooLexer.whiteSpace)的操作。