什么是G#
G#是我在过去几个月里构思出来的一种新的程序设计语言。其目的是生成类型安全的代码,这些代码能够在编译时或运行时被注入(Inject)到一个代码基(Code Base)中。其语法是C# 2.0的一个超集。和其他代码生成技术与工具(如CodeSmith,一种伟大的工具/语言)不同,G#并不打算生成用作起始点(Starting Point)或用于消费(Consumption)的代码。取而代之,G#使用了面向方面的程序设计(AOP)技术来向客户代码中注入代码。我们会快速地介绍一下AOP,因为它对很多开发者来说还是崭新的。
AOP AOP或称面向方面的软件开发(AOSD)于1997年在Xerox Parc创建,是一种相对先进的软件典范(Paradigm)。其思想很简单,通过使开发者每次只关注一个问题域来降低软件开发的复杂性。换句话说,人们在尝试解决一个业务问题(比如在互联网上销售产品)时无需考虑安全、线程、登录、数据访问和其他领域的问题。这被称为关注点的分离(Separation of Concerns)。通过分离这些领域或者方面,某一特殊方面的专家可以开发能够解决该方面问题的最好的解决方案,因此开发者无需再去掌握所有的行业。这样就有望产生健壮并且功能完善的软件,因为开发者只需做一名“软件问题域”的专家。
AOP通过定义方面(也就是一组行为)来开始,然后将代码注入到适当的方法中去。每个代码注入点都被称作是一个结合点(Join Point)。让我们以安全为例。“所有的输入都是邪恶的”是安全界的一条曼特罗(Mantra,咒语)。对抗这一难题的一种做法是,要求所有的开发者编写代码时都要在使用数据之前检查是否有恶意的输入。开发者们很可能会开发一个辅助方法用来解决这一问题,然后所有的开发者都会在他们的代码中简单地调用这个辅助方法。AOP可以解决这一问题,它抽取这些相同的辅助方法并创建一个方面,然后将其注入到需要对用户输入进行检查的地方。这个过程称为编排(Weaving)。我们没有简单地定义一个将会收到“邪恶输入”方面的位置列表,而是定义了将要使用的一组标准(Criteria)。既然是这样,我们就希望除方面之外能够注入所有带有参数的公共属性、方法和构造器。比起创建一个列表,这样做的好处是开发者们无需再凭借他们的记忆来将需要对输入进行检查的方法添加到列表中。
相对于你所熟悉的AOP语言如AspectJ,G#并没有单独的编排文件:编排被集成到了语法当中。对于大多数程序员来说,别人可以将代码注入到他们的代码基之中,这无疑是一种容易引起恐慌的建议。为了解决这一问题,G#包含了一个用来处理这一问题的安全模型,并且允许程序员来控制哪些人可以注入代码以及可以注入什么样的代码,这将放在后面进行讨论。在我们深入之前先来看一些基础要素:
基础
public class Client { public Client() { Messenger(“Hello World”); }
private void Messenger(string message) { Console.WriteLine(message); } }
public generator Rename { static generation ChangeIt : target Client.Messenger(string message) { pre { string oldMessage = message; message = “Hello G#”; }
post { message = oldMessage; } } } |
尽管这个例子没有任何用途,但它演示了G#的大量特性。首先,Client类使用了标准的C#语法——这在G#中是有效的,它只是简单地向控制台输出了消息“Hello World”。这个类定义下面是G#中新增的语言构造,称作生成器(Generator)。现在只需认为生成器是所有用于定义“如何生成代码”的代码的容器即可,这和类(Class)类似。Rename是这个生成器的名字,就好像Client是类的名字一样。接下来定义了一个名为ChangeIt的生成(Generation)。生成和方法类似,每次调用它都会执行一些动作,不同的是在调用生成的时候会通常产生代码。注意ChangeIt有一个目标(Target),在这里是来自Client类的Messenger方法。目标可以是任何(语言)构造,并且还可以包括通配符和正则表达式来指定一组项目作为目标。这表示由该生成所发出(Emit)的所有代码都将被注入到Messenger方法中。关键字pre规定了其后面花括号中定义的所有代码都将被注入到Messenger方法体中定义的代码之前。关键字post规定了其后面花括号中定义的所有代码都将被注入到Messenger方法体中定义的代码之后。因为用关键字static标记了这个生成,因此代码的实际注入是编译过程的一部分,理解这一点很重要。程序员将无法看到Messenger方法的变化,除非使用ildasm或Reflector来检查Messenger方法。此外还有一个目前还只是梦想的特性,就是能够生成动态的Region,这样在Visual Studio .NET中就能打开它来检查生成器都在客户环境中生成了哪些代码。稍后我们将讨论其他类型的生成。
private void Messenger(string message) { // From ChangeIt pre block. string oldMessage = message;
// From ChangeIt pre block. message = “Hello G#”;
// From the Messenger method body.
Console.WriteLine(message);
// From ChangIt post block. message = oldMessage; } |
这个方法因此将向控制台打印“Hello G#”,然后再将message字符串改回最初传入的消息。注意在.NET中字符串是不可变的,因此实际上是不能改变一个字符串所包含的内容的。因此通过在post块中将message改回初始的消息以保护Messenger方法外的“Hello World”消息并不是必须的,但是对于在Messenger方法体中执行的任何代码来说,后置的注入代码都是很重要的。这里出现的一个逻辑问题是,在后置条件(Post Condition)之后,Messenger方法体中的代码究竟什么时候执行呢?这个问题完美地引出了下一节。
查看本文来源