What you are describing is an add-in or plug-in architecture. In C# it is easy to load types from DLLs dynamically. Usually you would have a separate class library (DLL) containing only interface declarations that will be referenced by the main application as well as by the add-ins. Of course you can also inherit from base classes, but you should preferably program against interfaces (where any base classes implement the interfaces). This gives you the option to inherit from an existing implementation or to provide a completely new, independent implementation.
In C# it makes no difference, whether a class inherits from a class in the same assembly or a class in another assembly. Types and members must however be public
, in order to be accessible from another assembly (EXE, DLL). Types and members declared as internal
are only visible within their own assembly.
In your project you must add a reference to the other assembly or project in order to use its public types. In the solution explorer right click your project and select "Add Reference... " In the "Browse" tab select another DLL or EXE. If you are referencing a project within the same solution, select the other project from the "Projects" tab. Alternatively, you can "Copy As Project Reference" in the project's context menu and in the other project "Paste Reference" in the context menu of the project/References folder. Project references automatically switch the reference between Debug/Release as you change the configuration. If you reference a DLL directly, you would typically reference the "Release" version.
It is a good idea to have an IAddIn
interface which has to be implemented by all the add-ins. You can place whatever you want in this interface (add-in name, initialization method, etc.). Additional interfaces that can be implemented optionally are a good idea. Examples are interfaces allowing add-ins to provide menu entries, to consume user settings, to provide user controls that will be presented in tab pages by the main application etc.
public interface IAddIn
{
// Allows you to list the loaded add-ins in an "About" dialog.
string Name { get; }
string Version { get; set; } // Set by the add-in loader.
}
public interface IMenuItemProvider
{
IEnumerable<ToolStripMenuItem> MenuItems { get; }
}
public interface IContent
{
string Name { get; }
Control Content { get; } // Usually a UserControl containing other controls
}
public interface IContentProvider
{
IEnumerable<IContent> Contents { get; }
}
This is the add-in loader that I am using in one of my applications. It scans all DLLs found in a directory for classes implementing the IAddIn
interface and returns instantiated add-in objects in a list.
public class AddInLoader
{
public IList<IAddIn> Load(string folder)
{
var addIns = new List<IAddIn>();
string[] files = Directory.GetFiles(folder, "*.dll");
foreach (string file in files) {
addIns.AddRange(LoadFromAssembly(file));
}
return addIns;
}
private static IEnumerable<IAddIn> LoadFromAssembly(string fileName)
{
Assembly asm = Assembly.LoadFrom(fileName);
string addInInterfaceName = typeof(IAddIn).FullName;
foreach (Type type in asm.GetExportedTypes()) {
Type interfaceType = type.GetInterface(addInInterfaceName);
if (interfaceType != null && (type.Attributes & TypeAttributes.Abstract) != TypeAttributes.Abstract) {
IAddIn addIn = (IAddIn)Activator.CreateInstance(type);
addIn.Version = asm.GetName().Version.ToString();
yield return addIn;
}
}
}
}
The main application can then load the add-ins and test whether they implement interfaces and act accordingly.
var loader = new AddInLoader();
IList<IAddIn> addIns = loader.Load(myAddInFolder);
foreach (IAddIn addIn in addIns) {
var menuProvider = addIn as IMenuItemProvider;
if (menuProvider != null) {
foreach (ToolStripMenuItem menuItem in menuProvider.MenuItems) {
//TODO: Add menu item to application menu
}
}
var contentProvider = addIn as IContentProvider;
if (contentProvider != null) {
foreach (IContent content in contentProvider.Contents) {
//TODO: Add content to new tab pages
}
}
}