关键字emit
我们已经讨论了如何使用关键字pre和post来发出代码,但G#中有更丰富的方法来指定如何以及在哪里发出代码。其中一种方法就是像使用pre和post那样使用关键字emit:
emit { Console.WriteLine(“Hello G#”); } |
代码“Console.WriteLine(“Hello G#”);”会在哪里发出?它将在其基生成的emit块中发出。[(That reminds be of the definition of a normal)]OK,那么pre和post实际上也是emit块,只不过它们定义了发出代码的位置(方法体的前面和方法体的后面)。对于上面的代码片断,我们需要提供一个上下文环境来说明一下这些代码是在哪里发出的。
...
pre { § Counter(); }
...
void Counter() { emit { Console.WriteLine(“The emit keyword in action”); } } |
当一个带有该pre块的生成被编译时,它会调用Counter方法,因为Counter()的前面有§符号。在Counter方法中,关键字emit用于注入对Console.WriteLine的调用。emit块将会用块中的代码来取代对Counter()的调用。一个方法中emit块的数量没有任何限制,并且可以在emit块中使用§。
此外,emit只是对G#框架(G# Framework)中定义的Emit类型的一个映射,因此我们可以创建emit的实例。
pre { § DisplayParts(); } ...
public emit DisplayParts() { emit partOne, partTwo; partOne { § Injector(partTwo); Console.WriteLine(“Part One”); § partTwo.Emit(); } return partOne.Emit(); }
private void Injector(emit target) { target { Console.WriteLine(“Injection...”); } } |
在上面的代码片断中,我们在DisplayParts生成的定义中创建了两个emit对象partOne和partTwo。然后我们使用partOne加花括号定义了一个emit块。花括号之间的所有代码都将被发出到partOne的局部存储(Local Store)中,当我们在partOne对象上调用Emit方法时,将会返回这个局部存储。最后,注意该代码段的pre块中调用了返回值类型为emt的DisplayParts。[Since the emitted code is not caught it is emitted into the pre block.]
目标 我们已经探讨了当以一个方法为目标时如何使用关键字pre和post,但除此之外,G#还定义了一些关键字以使用其他语言构造作为目标。下面的表格给出了其他能够发出代码的关键字和它们的描述。为这些关键字指定目标构造时也可以使用通配符,参见后面的示例:
关键字 |
描述 |
class |
注入目标命名空间中所有的类 |
namespace |
注入目标命名空间中所有的命名空间 |
set | get |
注入目标所定义的所有set和get区域 |
generator |
注入目标所定义的所有生成器 |
generation |
注入目标所定义的所有生成 |
property |
注入目标所定义的所有属性 |
method |
注入目标所定义的所有方法 |
public generator Base { protected virtual generation ChangeClient : target Client { property public string * { get { post { Console.WriteLine(value); } } set { pre { Console.WriteLine(value); } } }
method (public | protected) * Cl*(*) { Console.WriteLine(“Cl* Method Targeted”); } } } |
这里我们注入了所有类型为string而名字任意的属性。我们还在get访问器中使用了关键字value,该关键字在G#中表示由目标代码的get访问器所返回的值。在这里使用pre和post与在方法中的用法无异。接下来的关键字method定义了我们将要注入的所有公共的和受保护的方法,其中两个星号(*)分别表示返回值类型任意并且方法的名字是以“Cl”开头、后跟任意多个任意的字符。(译注:实际上是3个星号,后面括号里那个表示该方法能够带任意多的参数。)在名字中还可以使用“英镑($)”符号作为通配符,表示任意的一个字符。注意到这一点很重要:Client类中所有满足约束条件的成员都会被注入。
自适应生成 第二种生成的类型是自适应生成(Adaptive Generation),只是简单地把一个生成前面的关键字static换成adaptive。自适应生成在运行时生成并且注入代码,因此它可以检查对象的状态以指导生成。
比起静态生成,自适应生成的优势在于第三方也可以提供生成框架和组件。第三方开发者可以通过创建幻象目标(Phantom Target)来以他们一无所知的代码基作为目标。幻象目标并不存在于生成框架或目标框架中。当开发者希望使用一个第三方的生成器时,他们可以加入幻象的命名空间、类、方法并将生成的代码重定位到他们的代码基中适当的位置。 public class Client
{ protected string message; public Client() { this.message = “Hello World”; Messenger(this.message); } public string Message { get { return this.message; } }
private void Messenger(string message) { Console.WriteLine(message); } }
// Phantom Target
namespace ThirdParty.Security { public adaptive generator Input : target Client {} }
|
程序集:
// Third Party generator
public generator Security { protected adaptive generation CheckInput : target ThirdParty.Security.Input { property public string * { get { pre { value = ValidateInput(value); } } }
method public * *(all string *(input)) { pre { input = ValidateInput(input); } } } } |
在上面的代码中,我们定义了一个Client类、一个第三方生成器Security和一个幻象目标命名空间ThirdParty.Security。类和幻象目标被定义在一个程序集中,而第三方生成器在另外一个程序集中提供。第三方定义了所有类型为string的公共属性在返回之前都要调用ValidateInput方法。它还定义了所有返回值类型为string的公共方法在执行任何代码前都要对其类型为string的参数调用ValidateInput。G#中的关键字all表示对于作用域内所有符合标准的参数都要做这件事情。星号(*)表示参数的名字可以是任意的,我们必须将想要引用的实参的名字放在圆括号中,以告诉编译器我们正在使用这个名字,但我们不希望将它作为标准的一部分。
现在的CLR能够在运行时动态地注入IL代码,这发生在程序集加载时,通过Profiler API完成。然而这种途径还存在着一系列的安全问题,因为它禁用了CAS,因此还需要深入的研究才能找到一种切实可行的解决方案。我们将在下面描述这是如何完成的。 CAS和注入特性
现在已经有望解决注入代码所引发的安全问题了。G#的安全模型能够确保只有你希望他注入代码的人才能注入代码,并且这些代码只能限制在你所允许的代码访问安全(CAS,Code Access Security)许可中。通过使用元数据,你可以声明你授予注入代码的权限。这仍需要定义一种语法并加入建议[Still need to define this syntax and open to suggestions.]。所有包含生成器和生成的程序集都必须被赋予一个强密钥,然后为目标程序集添加一个带有该公共密钥记号的Injector特性。只有在Injector中指出了强密钥的程序集才能运行和注入代码。
总结 代码生成为我们提供了各种可能性,我们希望G#能够发展成为一个泛型的、类型安全的代码生成语言。根据您的意见和建议,G#的语法还会改变并且进一步精炼,因此,非常感谢您阅读G#的相关文档,如果您有任何意见、问题或想法,请给Ernie Booth发email:gsharp@erniebooth.name。
查看本文来源