Лучший способ настроить модульную программу на С#

Мой друг и я пишем бот IRC С#, и мы ищем способ включить модульную систему, чтобы пользователи могли писать специальные модули для расширения функции.

Бот использует Regex для разделения всех необработанных данных с сервера, а затем запускает соответствующее событие с данными. Например, типичный обработчик событий может выглядеть так:

OnChannelMessage(object sender, ChannelMessageEventArgs e)
{
}

В ChannelMessageEventArgs будет имя канала, ник отправителя, сообщение и т.д.

Я хотел бы иметь систему плагинов, чтобы люди могли создавать модули и загружать/выгружать их по своему усмотрению, когда бот загружается или работает.

В идеале я хотел бы скомпилировать файлы .cs на лету, а в файле plugin.cs было бы несколько вещей:

  • какое событие захватить, например, OnChannelMessage и Channeldata в OnChannelEventArgs
  • что делать, когда эта информация предоставляется,
  • текст справки (который я могу вызвать изнутри главного бота.. так скажите строку, help = "это помощь для этого плагина", которая может быть возвращена в любое время без фактического вызова плагина)
  • имя плагина и т.д.

Спасибо за любые идеи о том, с чего начать для тех, кто относительно новичок в программировании.

Ответы

Ответ 1

Раньше я использовал такие вещи на проектах, но это было давно. Есть, вероятно, рамки, которые делают такие вещи для вас.

Чтобы написать собственную архитектуру плагина, в основном вы хотите определить интерфейс для всех модулей для реализации и поместить это в сборку, совместно используемую как вашей программой, так и модулями:

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);
            });

Если вы хотите скомпилировать код "на лету": вы создаете объект компилятора, рассказываете ему, какие сборки ссылаются (System и тот, который содержит 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 {} 

получайте удовольствие!

Ответ 2

Managed Extensibility Framework (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 IEnumerable<IModule> Modules { get; set; }

Ответ 5

MEF - Managed Extensibility Framework - это модное слово прямо сейчас - я думаю, что это было бы лучше всего.

Если это не делает трюк, вы также можете посмотреть Prism, у них есть другой подход к реализации модулей.