Implementing a plug-in architecture in .NET can be problematic due to the inability to unload an assembly from the primary AppDomain. Here are two possible alternatives:-
1. Reflection on Application Startup
Use the CodeDom compilers (i.e. CSharpCodeProvider) to programmatically generate assemblies (automate with FileSystemWatcher), which are output to a folder on disk, and loaded (into the primary AppDomain) on application start. Reflecting across the types in the loaded assemblies provides a list of "commands" which can then be invoked from within your application.
This alternative doesn't robustly support the replacement/update/deletion of commands on the fly, because of the implications of being unable to unload assemblies from the primary AppDomain. However, this alternative allows the "command" to access rich types within your application, and works well if restarting the application isn't a problem in your scenario.
2. Assemblies Loaded into Secondary AppDomain
Use the CodeDom compilers to programmatically generate assemblies (automate with FileSystemWatcher), which are output to a folder on disk, and loaded into a new SECONDARY AppDomain on application start. On detecting any change to the assembly folder, the secondary AppDomain is unloaded, then a NEW secondary AppDomain is created, and all assemblies in the folder are reloaded.
Loading an assembly and creating a class instance from it in a different AppDomain requires a number of steps - all necessary if one is going to access it without using any of the object's type information. If you do access type information by reflection, the assembly WILL load in the primary AppDomain - which is exactly what this scenario is intended to avoid.
The process involves creating an object factory in a separate assembly, which is loaded into the secondary AppDomain and which returns an interface rather than a physical object reference, to the primary AppDomain. One then uses the interface to call functionality in remote assemblies indirectly.
One can use the interface to invoke "commands" in assemblies loaded in the secondary AppDomain. However, this approach means that one doesn't have access to the objects in your application (because they exist in another AppDomain) except through Remoting. It does work well if, however, if you wanted to have algorithmic extensions, for example, if you had a mathematics application and you wanted to enable the user to add/update/call on the fly, user defined mathematical equations, and return the result to the main application.
If you can afford the restart, use method 1. If you only want to invoke methods and pass simple types across the AppDomain wall, use method 2.