I've used stuff like this on projects before, but it's been a while. There are probably frameworks out there that do this kind of stuff for you.
To write your own plug-in architecture, basically you want to define an interface for all the modules to implement, and put this in an assembly shared by both your program and the modules:
public interface IModule
{
//functions/properties/events of a module
}
Then your implementors will code their modules to this assembly, preferably with a default constructor.
public class SomeModule : IModule {} ///stuff
In your program (supposing your modules will be compiled into their own assemblies) you load a reference an assembly containing a module, find the types that implement the module interface, and instantiate them:
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);
});
If you want to compile the code on the fly: you create a compiler object, tell it what assemblies to reference (System and the one that contains IModule, plus any other references needed), tell it to compile the source file into an assembly. From that, you get the exported types, filter keeping those which implement the IModule, and instantiate them.
//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));
Look up tutorials on plug-in architectures and loading dynamic assmeblies to get a better feel for doing this kind of stuff. This is just the start. Once you get the basics down, you can start doing some really cool things.
As for handling the metadata (module X is named YYY and should handle events A,B and C):
Try to work this into your type system. You could make up different interfaces for the different functions/events, or you could put all the functions on one interface, and put attributes (you'd declare these in the shared assembly) on the module classes, using the attributes to declare which events the module should subscribe to. Basically you want to make it as simple as possible for people to write modules for your system.
enum ModuleTypes { ChannelMessage, AnotherEvent, .... }
[Shared.Handles(ModuleTypes.ChannelMessage)]
[Shared.Handles(ModuleTypes.AnotherEvent)]
class SomeModule : IModule { ... }
or
//this is a finer-grained way of doing it
class ChannelMessageLogger : IChannelMessage {}
class PrivateMessageAutoReply : IPrivateMessage {}
have fun!