.NET 脚本,一种新的方法






3.22/5 (10投票s)
2004年11月15日
3分钟阅读

46747

422
在 .NET 应用程序中采用脚本的新方法(?)。
引言
这个项目演示了如何在你的应用程序中提供脚本支持,而无需使用脚本宿主或创建新的应用程序域。
背景
我看到过许多关于如何使用 C# 作为脚本语言的文章。所有文章都集中在 shell 或运行时脚本。我所说的运行时脚本是指可以在运行时修改并在应用程序中立即生效的脚本。我的方法略有不同。我会在运行时编译脚本,但只编译一次。如果用户想要更改脚本,则必须重新启动应用程序才能使更改生效。这是一个限制,但我不认为这是一个严重的限制。好处如下:
- 无需通过应用程序边界传递对象
- 无需指定脚本和应用程序之间的接口
- 熟悉的 OO 模型用于脚本
这个脚本解决方案只是允许用户扩展你的应用程序中任何类的功能。所有写在脚本文件中的代码都将被视为原始代码的一部分。
使用代码
假设你有一个名为 ScriptedClass
的类,它非常简单,没有充分利用我们提供给它的所有信息。
public class ScriptedClass
{
private string name;
public string Name
{
get
{
return name;
}
}
private int age;
public int Age
{
get
{
return age;
}
}
public ScriptedClass()
{
}
public ScriptedClass(string name, int age)
{
this.name=name;
this.age=age;
}
public virtual string Message
{
get
{
return string.Format("Hi {0}!", name);
}
}
}
要使用 ScripterEngine
,你首先必须初始化它。Init
方法接受三个参数:包含脚本文件的目录的路径、用于临时文件的目录的路径以及用于进度的委托。
ScripterEngine.Init(
string.Format("{0}/script", Application.StartupPath),
string.Format("{0}/tmp", Application.StartupPath),null);
然后,你可以使用任何构造函数创建你的类的实例。
//Default constructor
ScriptedClass clsa=
(ScriptedClass)ScripterEngine.CreateObject(typeof(MyClass),
Type.EmptyTypes,null);
//Default constructor, shorter form
ScriptedClass clsb =
(ScriptedClass)ScripterEngine.CreateObject(typeof(MyClass));
//Constructor with arguments
ScriptedClass clsc =
(ScriptedClass)ScripterEngine.CreateObject(typeof(MyClass),
new Type[]{typeof(string),typeof(int)},
new object[]{"Hugo",23});
如果,你或用户,想要扩展这个类的功能,你必须编写一个脚本。
首先,我们需要一个脚本的配置文件。这个文件必须和源文件一起放在脚本目录下。
<?xml version="1.0" encoding="utf-8" ?>
<ScriptConfiguration name="Intelligent messenger" load="true">
<References>
<Reference>System.Web</Reference>
</References>
<Files>
<File>messenger.cs</File>
</Files>
<Types>
<Type>ScriptingArchitecture.ScriptedClass,
ScriptingArchitecture</Type>
</Types>
</ScriptConfiguration>
正如你所看到的,它是普通的 XML。References
标签包含编译脚本所必需的程序集的名称。我们实际上不需要 System.Web 程序集,但我把它放在那里是为了说明语法。所有加载的程序集在编译脚本时都会自动用作引用,因此不需要添加对任何基本程序集的引用。
Files
标签包含组成脚本的所有源文件。一个脚本中可以有一个或多个文件。
Types
标签包含脚本扩展的所有类型。请注意,必须使用类型的全名,后跟定义该类型的程序集的名称来指定该类型。
当我们得到这个文件时,我们可以创建实际脚本的文件。
using ScriptingArchitecture;
using System.Windows.Forms;
using System.Drawing;
namespace Messenger
{
public class IntelligentMessage : ScriptingArchitecture.ScriptedClass
{
public IntelligentMessage():base()
{
}
public IntelligentMessage(string name, int age):base(name,age)
{
}
public override string Message
{
get
{
return string.Format("Hi {0}! You are {1} years old!", Name, Age);
}
}
}
}
关注点
为了能够适应来自不同来源的临时脚本,脚本引擎使用继承链。如果一堆脚本扩展了你的应用程序中的特定类,它们必须全部共存。因此,在这种情况下,你的类是 A
B:A
C:A
D:A
E:A
代码将被重写为
B:A
C:B
D:C
E:D
并且当请求类型为 A
的类时,将提供类 E
。
我在我的脚本实现中缺少的是更简洁的语法来创建脚本对象。
ScriptedClass clsc=(ScriptedClass)ScripterEngine.CreateObject(
typeof(MyClass),
new Type[]{typeof(string),typeof(int)},
new object[]{"Hugo",23});
这段代码并不漂亮。我希望有更像这样的东西
ScriptedClass clsc=new ScriptedClass("Hugo",23);
这可以通过 ScriptedClass
中的 static
方法来修复,但我希望找到一种不那么hack的方式来解决这个问题。
public static ScriptedClass GetInstance(string name, int age)
{
return (ScriptedClass)ScripterEngine.CreateObject(
typeof(MyClass),
new Type[]{typeof(string),typeof(int)},
new object[]{name,age});
}
注意事项
脚本代码有一些在正常开发中不存在的限制
- 每个源文件只能有一个命名空间
- 你的脚本扩展的类必须使用其全名引用,例如,
public class Script : MyApplication.ScriptedObject
not
public class Script : ScriptedObject
- 你必须实现基类中实现的所有构造函数。
历史
- 2004-11-15
上传了第一个版本。
- 2004-11-16
源文件已更新,并添加了注意事项列表。