我的朋友和我正在编写一个IRC C#bot,我们正在寻找一种包含模块系统的方法,以便用户可以编写自定义模块来扩展功能.
机器人使用Regex从服务器中分离所有原始数据,然后使用数据触发相应的事件.例如,典型的事件处理程序可能如下所示:
OnChannelMessage(object sender, ChannelMessageEventArgs e) { }
In ChannelMessageEventArgs
将是频道名称,发送者的昵称,消息等...
我想拥有一个插件系统,以便人们可以随意构建模块并加载/卸载它们,当机器人加载时,或者运行时.
理想情况下,我希望能够动态编译.cs
文件,并在plugin.cs文件中,将是一些事情:
要捕获的事件,例如OnChannelMessage和OnChannelEventArgs中的Channeldata
给出这些信息后该怎么办
一个帮助文本(我可以从主机器人内部调用..所以说一个字符串,帮助="这是这个插件的帮助",可以随时返回,而无需实际调用插件)
插件名称是什么等
感谢您对于在编程方面相对较新的人的起点.
我之前在项目中使用过这样的东西,但已经有一段时间了.可能有框架为你做这种事情.
要编写自己的插件体系结构,基本上您需要为要实现的所有模块定义一个接口,并将其放在程序和模块共享的程序集中:
public interface IModule { //functions/properties/events of a module }
然后,您的实现者将其模块编码到此程序集,最好使用默认构造函数.
public class SomeModule : IModule {} ///stuff
在您的程序中(假设您的模块将被编译到它们自己的程序集中),您加载一个包含模块的程序集的引用,找到实现模块接口的类型,并实例化它们:
var moduleAssembly = System.Reflection.Assembly.LoadFrom("assembly file"); var moduleTypes = moduleAssembly.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(IModule))); var modules = moduleTypes.Select( type => { return (IModule) Activator.CreateInstance(type); });
如果你想动态编译代码:你创建一个编译器对象,告诉它要引用的程序集(系统和包含IModule的程序集,以及所需的任何其他引用),告诉它将源文件编译成程序集.从那里,您获得导出的类型,过滤保留实现IModule的那些,并实例化它们.
//I made up an IModule in namespace IMod, with string property S string dynamicCS = @"using System; namespace DYN { public class Mod : IMod.IModule { public string S { get { return \"Im a module\"; } } } }"; var compiler = new Microsoft.CSharp.CSharpCodeProvider().CreateCompiler(); var options = new System.CodeDom.Compiler.CompilerParameters( new string[]{"System.dll", "IMod.dll"}); var dynamicAssembly= compiler.CompileAssemblyFromSource(options, dynamicCS); //you need to check it for errors here var dynamicModuleTypes = dynamicAssembly.CompiledAssembly.GetExportedTypes() .Where(t => t.GetInterfaces().Contains(typeof(IMod.IModule))); var dynamicModules = dynModType.Select(t => (IMod.IModule)Activator.CreateInstance(t));
查看有关插件架构的教程并加载动态assmeblies,以便更好地了解这类内容.这只是开始.一旦你掌握了基础知识,你就可以开始做一些非常酷的事情了.
至于处理元数据(模块X名为YYY,应该处理事件A,B和C):尝试将其用于您的类型系统.您可以为不同的函数/事件组成不同的接口,或者您可以将所有函数放在一个接口上,并将属性(您在共享程序集中声明这些)放在模块类上,使用属性声明哪些事件该模块应订阅.基本上你想让人们为你的系统编写模块尽可能简单.
enum ModuleTypes { ChannelMessage, AnotherEvent, .... } [Shared.Handles(ModuleTypes.ChannelMessage)] [Shared.Handles(ModuleTypes.AnotherEvent)] class SomeModule : IModule { ... }
要么
//this is a finer-grained way of doing it class ChannelMessageLogger : IChannelMessage {} class PrivateMessageAutoReply : IPrivateMessage {}
玩得开心!
该托管扩展性框架(MEF)提供您正在寻找什么,除了他们称之为一个插件架构.您可以提供用户可以构建的通用接口,然后MEF可以扫描目录中的dll并动态加载任何导出.
例如,您的插件作者可以创建类似的东西
[Export(typeof(IModule))] public class MyModule : IModule { void HandleMessage(ChannelEventArgs e) {} }
然后你的代码看起来像这样:
void OnChannelMessage(ChannelEventArgs e) { foreach(IModule module in Modules) { module.HandleMessage(e); } } [Import] public IEnumerableModules { get; set; }