很多时候,我们需要通过插件(add-in)方式来载入不同的策略。举个例子,在垂直搜索引擎开发中,爬虫抓取回来的数据必须进行分析获取元数据后才能用于搜索。我们很难用通用文本特征抽取算法来获取不同网站的精确数据,此时我们必须为某些站点编写特定的 "算法"。由于目标种子站点在运营过程中随时都可能发生变化,从设计角度来说,我们自然不能把这些特殊策略全部放到核心类库里。可行的做法是 —— "外挂",将每个特定的策略做成独立插件,通过特定的手段动态载入并执行,以此来达到在不影响整体系统的情况下,进行扩充和变更的目的。
目前在 .NET 环境下,可选的 "插件模型" 包括:
1. 程序集方式
2. 脚本方式
代码
代码
代码
目前在 .NET 环境下,可选的 "插件模型" 包括:
1. 程序集方式
- System.Addin : .NET 3.5 BCL 提供的一个插件扩展模型,功能十分强大,不过整体有些偏 "重",过于复杂。
- System.Reflection : 使用反射过于繁琐了些。
- AOP : 基本和反射类似,不过可以通过配置文件来简化操作。
2. 脚本方式
- System.CodeDom : 这个不错,不过要处理的细节也不少,诸如编译过程,引用及参数设置等等。
- IronPython、Ruby.NET : 很多网络游戏服务器,都采取动态脚本来编写 "剧情",服务器本身只是一个执行环境。只是选择 Python、Ruby 需要不同的语言整合,有点麻烦。
- CS-Script : 这个东西基本上是对 CodeDom 的封装,它为我们完成了所有的细节处理。
经过对比,CS-Script 更符合我的需求。这样一来,我们只需要将策略写成文本代码文件,放到特定区域,然后由 "Shell" 载入执行即可。就算修改,也只需一个记事本就能完成,更何况使用的还是我们熟悉的 C# 语言。(记事本 + 命令行编译器 + 命令行调试器 ~~~~ 不要学我们这的某个同事,要在服务器上装一整套 VS2005。 ~~~~)
看一个例子。
Server.exe

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CSScriptLibrary;
namespace Server
{
// 服务器上下文环境
public class MyContent
{
public int I { get; set; }
}
// 插件接口
public interface IAddin
{
void Execute(MyContent content);
}
class Program
{
static void Main(string[] args)
{
// 载入策略脚本
var assembly = CSScript.Load("TestAddin.cs");
// 查找实现了插件接口的类型
var q = (from type in assembly.GetTypes()
where type.GetInterface("Server.IAddin", true) != null select type).SingleOrDefault();
// 创建环境上下文
var content = new MyContent { I = 12345 };
// 创建插件对象 (当然,我们可以缓存该对象,没必要每次执行都创建一个实例)
var addin = new AsmHelper(assembly).CreateObject(q.Name) as IAddin;
// 调用插件方法
addin.Execute(content);
}
}
}
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CSScriptLibrary;
namespace Server
{
// 服务器上下文环境
public class MyContent
{
public int I { get; set; }
}
// 插件接口
public interface IAddin
{
void Execute(MyContent content);
}
class Program
{
static void Main(string[] args)
{
// 载入策略脚本
var assembly = CSScript.Load("TestAddin.cs");
// 查找实现了插件接口的类型
var q = (from type in assembly.GetTypes()
where type.GetInterface("Server.IAddin", true) != null select type).SingleOrDefault();
// 创建环境上下文
var content = new MyContent { I = 12345 };
// 创建插件对象 (当然,我们可以缓存该对象,没必要每次执行都创建一个实例)
var addin = new AsmHelper(assembly).CreateObject(q.Name) as IAddin;
// 调用插件方法
addin.Execute(content);
}
}
}
TestAddin.cs
//css_ref Server.exe;
using System;
using Learn.CUI;
public class TestAddin : IAddin
{
public void Execute(MyContent content)
{
Console.WriteLine(content.I);
}
}
using System;
using Learn.CUI;
public class TestAddin : IAddin
{
public void Execute(MyContent content)
{
Console.WriteLine(content.I);
}
}
当然,我们还可以在另一个应用程序域中执行插件,这样就可以 Unload,从而在不关闭宿主的情况下修改插件脚本了。
Server.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CSScriptLibrary;
namespace Server
{
// 服务器上下文环境,注意 MarshalByRefObject。
public class MyContent : MarshalByRefObject
{
public int I { get; set; }
}
// 插件接口
public interface IAddin
{
void Execute(MyContent content);
}
class Program
{
static void Main(string[] args)
{
var content = new MyContent { I = 12345 };
var asmFile = CSScript.Compile("TestAddin.cs");
using (var helper = new AsmHelper(asmFile, "AddinDomain", true))
{
// 注意设置依赖程序集搜索路径
helper.ProbingDirs = new[] { AppDomain.CurrentDomain.BaseDirectory };
// 创建代理对象
var addin = helper.CreateObject("TestAddin") as IAddin;
// 调用插件方法
addin.Execute(content);
}
}
}
}
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CSScriptLibrary;
namespace Server
{
// 服务器上下文环境,注意 MarshalByRefObject。
public class MyContent : MarshalByRefObject
{
public int I { get; set; }
}
// 插件接口
public interface IAddin
{
void Execute(MyContent content);
}
class Program
{
static void Main(string[] args)
{
var content = new MyContent { I = 12345 };
var asmFile = CSScript.Compile("TestAddin.cs");
using (var helper = new AsmHelper(asmFile, "AddinDomain", true))
{
// 注意设置依赖程序集搜索路径
helper.ProbingDirs = new[] { AppDomain.CurrentDomain.BaseDirectory };
// 创建代理对象
var addin = helper.CreateObject("TestAddin") as IAddin;
// 调用插件方法
addin.Execute(content);
}
}
}
}
TestAddin.cs

//css_ref Server.exe;
using System;
using Server;
// 插件,注意 MarshalByRefObject。
public class TestAddin : MarshalByRefObject, IAddin
{
public void Test()
{
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
}
public void Execute(MyContent content)
{
Console.WriteLine(content.I);
}
}
using System;
using Server;
// 插件,注意 MarshalByRefObject。
public class TestAddin : MarshalByRefObject, IAddin
{
public void Test()
{
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
}
public void Execute(MyContent content)
{
Console.WriteLine(content.I);
}
}
由于要跨域传递,因此某些地方需要添加 MarshalByRefObject。另外,注意设置 helper.ProbingDirs,否则找不到插件依赖的程序集,抛出异常。雨痕偷懒没有把公用类型放到一个单独的程序集中,正式开发时,建议将其放到一个独立的类库项目中。
CS-Script 的功能很多,用它替代 CodeDom,可以省不少力气。更多细节,请参考其帮助文件。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述