6

I need to make an application that needs to be highly modular and that can easily be expanded with new functionality.

I've thought up a design where I have a main window and a list of actions that are implemented using a strategy pattern.

I'd like to implement the base classes/interfaces in a DLL and have the option of loading actions from DLL's which are loaded dynamically when the application starts. This way the main window can initiate actions without having to recompile or redistribute a new version. I just have to (re)distribute new DLL's which I can update dynamically at runtime. This should enable very easy modular updating from a central online source. The 'action' DLL's all inherit their structure from the code defined in the the DLL which defines the main strategy pattern structure and it's abstract factory.

I'd like to know if C# /.Net will allow such a construction.

I'd also like to know whether this construction has any major problems in terms of design.

8
  • 2
    If you need modularity then class inheritance is the opposite of what you want; you want to use interfaces (interface inheritance is OK) and composition to build up implementations of those interfaces out of smaller independent chunks. Class inheritance creates tight coupling and on top of that opens you up to the fragile base class problem if you do it wrong.
    – Doval
    Commented Aug 21, 2014 at 16:50
  • I need a universal way of accessing and running the actions, so I chose the strategy pattern for that. If that's not the way to go, which pattern would work instead? The DLL part is primarily for easy distribution and expansion of new functionalities.
    – Onno
    Commented Aug 21, 2014 at 16:53
  • In case you're wondering: an action basically has to do one thing: run and complete. Command/Strategy pattern seems the pattern of choice to me at this moment. Is that correct?
    – Onno
    Commented Aug 21, 2014 at 17:08
  • Yes, that'll work fine, but using delegates is simpler than rolling your own custom Command/Strategy class/interface if you ask me.
    – Doval
    Commented Aug 21, 2014 at 18:37
  • @Doval thank you for clarifying that. I guess I'll have to see how I will use a delegate in this way. I've always used them more in the role of the observer pattern.
    – Onno
    Commented Aug 21, 2014 at 18:38

2 Answers 2

6

Sure, C# will support that. Take a look at this example that uses the System.Collections.Generic.Dictionary as the base class.

public class MyDictionary : Dictionary<string, int> { }

public class MyMain
{
    public MyMain()
    {
        MyDictionary dictionary = new MyDictionary();
        dictionary.Add("hello", 1);
    }
}

There are some potential problems. Any inheriting class will be subject to changes within the base class. So you don't want to use this approach with a base class that is likely to be changing on a frequent basis.

The biggest challenge I have seen is when the base class changes function signatures. That wreaks havoc upon almost all of the inheriting classes.

1
  • 1
    This answer almost makes me feel stupid, but I realise now that it's pretty much the way how much of the base .net library (and other code of course) is constructed :) I just felt that asking it before building it would prevent any major pitfalls without investing a lot of time.
    – Onno
    Commented Aug 21, 2014 at 17:12
4

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
        }
    }
}
4
  • 1
    +1 That's exactly what I'm trying to do. I'm trying to add a plug-in based task list which will allow me to execute whatever I distribute. This means that certain people, say beta testers and such, will be able to add extra functionality without any rewrites to the code by supplying them with more DLL's, as well as being able to precisely target certain pieces of code without affecting the rest.
    – Onno
    Commented Aug 21, 2014 at 18:30
  • 1
    I knew it should be possible to dynamically load DLL's but you showed me how. Thank you for showing me this, as it will make my life a lot easier.
    – Onno
    Commented Aug 21, 2014 at 18:48
  • 1
    @Onno: You might also look into MAF. I haven't used it, but it's designed around add-ins. It's built in MEF, which can already do add-ins, of course.
    – Magus
    Commented Aug 21, 2014 at 22:07
  • @Magus I'll certainly look into that. Thanks for the suggestion :)
    – Onno
    Commented Aug 22, 2014 at 20:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.