原文地址:
http://www.nikhilk.net/ScriptSharpIntro.aspx
Script#原型
Script#把C#开发者的体验(编程和工具)带到JavaScript/Ajax世界.这篇帖子分享一个通过C#创建脚本的原型工程。
Script#把C#开发者的体验(编程和工具)带到JavaScript/Ajax世界.Yep,我最终公布了一段时间以来利用业余时间做的项目。
一个更好的写脚本的环境
这个工程的最根本的目标是提高脚本的技术发展水平。这个做法超出了惯例,我最初的想法是:编译时检查和智能感应或者自动语句完成,我确实认为这是非常有用的东东,并且他们本身也是很有价值的,当我们在Atlas上工作时也想像一种好的工具体验,为此尝试了不同的方法和技术,Script#就是这些探索中的一个原型,它尝试达到一些编程环境的要求:
1.一个具有天然结构的纯粹的语言。今天用脚本,可以在模拟OOP(类,接口和继承等)方面走得很远,但这些往往被开发者抛弃。各种各样的C#结构属性,事件,和定义继承的语法这些使代码更加可读。类似的修饰符public vs. protected vs. internal, sealed vs. virtual 等可以帮助建立更好的对象模型,所有的这些都可以带入到脚本世界。
2.更容易的Refactoring和exploration(译注:其实翻译成重构和导航没什么不好,Refactoring的英文解释是Refactoring is a program transformations that reorganize a program without changing its behavior,不过像这种通用词语还是不翻译吧,免得产生歧义)。脚本开发可以从Refactoring和在IDE以及.NET Reflector中支持的类浏览功能中受益匪浅,这两个东东会使代码便于管理。
3.产生文档的能力。同样C#的文档功能已经存在的基础架构将会大有裨益。
4.更加容易地定制脚本代码。比如,创建Dubug和Release最小版本。相同的主意可以用在创建一个用于诊断的脚本版本,该版本包含更多的错误检测和日志功能。这种方法可以用来包含用于探查和测试代码覆盖度的工具。另外这个项目可以产生出不同版本的脚本来适应JavaScript的当前版本,以及将来的JavaScript2.
示例
我将采用Ajax经典的Hello World来阐述这个模型。假设一个简单的页面,包含textbox, button 和label。
<input type="text" id="nameTextBox" />
<input type="button" id="okButton" value="OK" />
<span id="helloLabel"></span>
![](/Images/OutliningIndicators/None.gif)
现在我想写一些脚本给这个页面加一些UI逻辑。创建一个HelloWorld.CS文件,提取在Text中输入的值,用XMLHttp发出一个请求,最后在Label中显示结果。
using System;
using ScriptFX;
using ScriptFX.UI;
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
namespace HelloWorld
{
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public class HelloWorldScriptlet : IScriptlet
{
![](/Images/OutliningIndicators/InBlock.gif)
private Button _okButton;
private TextBox _nameTextBox;
private Label _helloLabel;
![](/Images/OutliningIndicators/InBlock.gif)
private XMLHttpRequest _request;
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public void Start()
{
_okButton = new Button(Document.GetElementById("okButton"));
_nameTextBox = new TextBox(Document.GetElementById("nameTextBox"));
_helloLabel = new Label(Document.GetElementById("helloLabel"));
![](/Images/OutliningIndicators/InBlock.gif)
_okButton.Click += new EventHandler(OnOKButtonClick);
}
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
private void OnOKButtonClick(object sender, EventArgs e)
{
Callback completedCallback = new Callback(this.OnRequestComplete);
![](/Images/OutliningIndicators/InBlock.gif)
_request = new XMLHttpRequest();
_request.Onreadystatechange = Delegate.Unwrap(completedCallback);
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_request.Open("GET", "Hello.axd?name=" + _nameTextBox.Text, /**//* async */ true);
_request.Send(null);
}
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
private void OnRequestComplete()
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if (_request.ReadyState == 4)
{
_request.Onreadystatechange = null;
![](/Images/OutliningIndicators/InBlock.gif)
string greeting = _request.ResponseText;
_helloLabel.Text = greeting;
}
}
}
}
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
这段代码显示了极其简单的HelloWorldScriptlet 类,用C#来写是一件非常愉悦的事情,现在是体验Script#魔力的时刻了。
ssc /ref:sscorlib.dll /ref:Script.ScriptFX.Core.dll /debug /out:HelloWorld.js HelloWorld.cs
![](/Images/OutliningIndicators/None.gif)
运行Script#的编译器(SCS.exe),产生如下所示的HelloWorld.js 文件:
Type.registerNamespace('HelloWorld');
![](/Images/OutliningIndicators/None.gif)
////////////////////////////////////////////////////////////////////////////////
// HelloWorld.HelloWorldScriptlet
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
HelloWorld.HelloWorldScriptlet = function Scenarios_HelloWorldScriptlet()
{
}
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
HelloWorld.HelloWorldScriptlet.prototype =
{
_okButton: null,
_nameTextBox: null,
_helloLabel: null,
_request: null,
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
start: function Scenarios_HelloWorldScriptlet$start()
{
this._okButton = new ScriptFX.UI.Button(document.getElementById('okButton'));
this._nameTextBox = new ScriptFX.UI.TextBox(document.getElementById('nameTextBox'));
this._helloLabel = new ScriptFX.UI.Label(document.getElementById('helloLabel'));
this._okButton.add_click(new Delegate(this, this._onOKButtonClick));
},
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_onOKButtonClick: function Scenarios_HelloWorldScriptlet$_onOKButtonClick(sender, e)
{
var completedCallback = new Delegate(this, this._onRequestComplete);
this._request = new XMLHttpRequest();
this._request.onreadystatechange = Delegate.unwrap(completedCallback);
this._request.open('GET', 'Hello.axd?name=' + this._nameTextBox.get_text(), true);
this._request.send(null);
},
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_onRequestComplete: function Scenarios_HelloWorldScriptlet$_onRequestComplete()
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if (this._request.readyState == 4)
{
this._request.onreadystatechange = null;
var greeting = this._request.responseText;
this._helloLabel.set_text(greeting);
}
}
}
![](/Images/OutliningIndicators/None.gif)
HelloWorld.HelloWorldScriptlet.registerClass('HelloWorld.HelloWorldScriptlet', null, ScriptFX.IScriptlet);
![](/Images/OutliningIndicators/None.gif)
注意一个类是如何转换成JavaScript等价物的:一个函数和原型上的方法。更进一步,注意如何自动产生调用以便将函数注册作为类。编译器提供一个良好的自然模型来声明继承体系。其他需要一提的是JavaScript如何模拟C#中的事件和属性访问。基本上,让编译器去做脏活累活,你自己不用去费神劳力地去做OOP的模拟。最后,既然使用了/debug编译选项,它为每个方法提供了一个名字(debugger时不能用匿名方法)这个也是自动产生的。一个简单的例子不能讲清楚它的转换能力,需要展示更多的内容。
产生的代码可以包含在通常的<script>标签中使用了。
<script type="text/javascript" src="sscorlib.js"></script>
<script type="text/javascript" src="ssfxcore.js"></script>
<script type="text/javascript" src="HelloWorld.js"></script>
<script type="text/javascript">
ScriptFX.Application.Current.run(new HelloWorld.HelloWorldScriptlet());
</script>
![](/Images/OutliningIndicators/None.gif)
Script#可以生成一个包含.JS文件作为资源的Assembly,这样联合服务器控件可以使用ASP.NET的WebResources属性以一种简单的方式在页面中包含脚本。
ssc /ref:sscorlib.dll /ref:Script.ScriptFX.Core.dll /debug /assembly:HelloWorld.dll /out:HelloWorld.js HelloWorld.cs
![](/Images/OutliningIndicators/None.gif)
<nStuff:Scriptlet runat="server"
ScriptAssembly="HelloWorld" ScriptletType="HelloWorld.HelloWorldScriptlet" />
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
编译器有debug和release的概念,
ssc /ref:sscorlib.dll /ref:Script.ScriptFX.Core.dll /minimize /out:HelloWorld.js HelloWorld.cs
![](/Images/OutliningIndicators/None.gif)
用/minimize参数替代/debug将会生成Release版本,如下所示(为了显示我故意增加了一些线)
Type.registerNamespace('HelloWorld');
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
HelloWorld.HelloWorldScriptlet=function()
{}
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
HelloWorld.HelloWorldScriptlet.prototype=
{$0:null,$1:null,$2:null,$3:null,
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
start:function()
{
this.$0=new ScriptFX.UI.Button(document.getElementById('okButton'));
this.$1=new ScriptFX.UI.TextBox(document.getElementById('nameTextBox'));
this.$2=new ScriptFX.UI.Label(document.getElementById('helloLabel'));
this.$0.add_click(new Delegate(this,this.$4));},
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
$4:function(sender,e)
{
var $2=new Delegate(this,this.$5);
this.$3=new XMLHttpRequest();
this.$3.onreadystatechange=Delegate.unwrap($2);
this.$3.open('GET','Hello.axd?name='+this.$1.get_text(),true);
this.$3.send(null);},
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
$5:function()
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if(this.$3.readyState==4)
{
this.$3.onreadystatechange=null;
var $0=this.$3.responseText;
this.$2.set_text($0);}}}
HelloWorld.HelloWorldScriptlet.registerClass('HelloWorld.HelloWorldScriptlet',null,ScriptFX.IScriptlet);
![](/Images/OutliningIndicators/None.gif)
正如你看到的,所有无关紧要的空格都被去掉了,并且标识符都是尽可能的小(这个对脚本的最小化贡献最大)用$0,$1等来表示,最令人激动的是Script#编译器可以利用C#代码中的信息最大限度地使代码最小化。这些内容包括哪些成员是internal/private,哪些成员是public的,比如类中的_okButton现在是$0,OnRequestComplete 方法现在是$5.
除了用一个聪明的方式产生脚本外,Script#也用IDE增强了脚本的创建。尽管已经用DOM写了几个月的程序,我还是要一直把MSDN打开查看有关DOM的参考资料,用Script#可以做到如下的样子:
![](/images/cnblogs_com/farseer1215/Intellisense1.jpg)
![](/images/cnblogs_com/farseer1215/Intellisense2.jpg)
应该关注的是智能感应和语句的自动完成以及C#编辑器提供的一些工具。它们反射sscorlib 这个工程采用了这种机制。
基本上我已经介绍了Script#的术语,以及编译的目标文件和与VS2005编程体验的整合,编译可以生成.dll文件和.JS文件,可以部署到你的工程当中。
原理
从本质上来说,Script#编译器是一个C#编译器,只不过生成的是JavsScript而不是IL。最主要的目标是产生可读的JavaScript脚本,这些脚本你自己也可以手动创建,并且可以将其部署到实际的应用当中。因此这个转换是C#级别的,而不是从IL转化成脚本,这个转换本身并不会增加任何抽象。
C#中的一些东西没有意义(比如lock,Unsafe)还有一些东西目前我不支持(比如范型)。这个转换器支持C#主要东东,比如foreach,委托以及debug条件编译。相关的类库(sscorlib,mscorlib的等价物)也被转化成JavaScript.它跟mscorlib包含的东东不同。我采用的方式不是写一个转化器,将任意用C#写得应用转化成JavaScript类似于Winform的抽象层。相反,正如Atlas,Script#是提供一个引擎和高级的环境,在这个环境下可以用Htlm/Css,JavaScript以一种更加高效,稳定和可维护的方式开发应用。
还有少部分JavaScript的东东(特别是闭包以及更多功能风格的编程)在这个转化器中不被支持。我想一定会有可选的等价物,但我更有兴趣等待反馈。这种实现允许手动写JavaScript并以Assembly元数据的方式暴露给C#。这为与已经存在的代码交互提供了良好的解决方案,并给一些需要手动调整的东东提供了机会,我会在后面的帖子中继续论述。
一些历史介绍和可下载的东东
很多人想知道我们在Atlas应用的基于OOP的类型系统是否是在浏览器中基于C#开发的先驱,现在我将正面回答这个问题。用C#创建脚本的想法在我们开始积极写脚本的时候就有了,我错过了一切,因为我认为C#实现这些是理所当然的(译注:不会是怪俺的偶像Anders没提供这个功能吧?呵呵)12月假期的时候,我最终决定认真考虑写一个编译器来实现这个功能。
从那开始,工作进入下一个阶段,已经可以用来生成有用的脚本。就在上个星期我打算写一个帖子宣布这件事情。非常巧合的是,Google的伙计们也想到了类似的东东,我想我应该提前一些日子写这个帖子的:)不管怎么样,俺已经有了一个初步的发布版本。你可以下载Script#的编译器和例子。注意这只是一个很弱弱的版本(如果你想试用的话,请先确保你的代码可以作为C#的代码编译)。俺需要更多的事情来让它变得强一些,更好地与Web工程整合,并且简化一些关键的C#场景。这个可能需要蛮多的时间,因为这只是一个业余时间的原型,同时,这个下载会给你一些体验,俺期待反馈,请多多评注。
脚本挑战和下一个版本
最近我对浏览器和脚本世界写了一个期望列表,我希望阐述从一个全面的视角看相关的和一起到来的东东,到今天为止,转换的结果还是脚本。仍然有很多没有解决的东东,这些受限于浏览器的运行环境以及不同浏览器之间的差异等等。另外,基于转换的模式给调试带来了麻烦。就目前来说,Script#提供了一个方便的创建脚本的手段,我们希望能够提供一个伟大的工具和平台可以直接处理原生(native)脚本,如今天Atlas中存在的东东一样。长期来看,真正的胖Web应用的解决方案是与WPF/E(正像MIX06展示的那样)的结合,并且跨平台的CLR可以作为宿主在浏览器中直接创建和执行托管代码从而充分从这种模型中受益。