views:

235

answers:

3

I'm wondering what the general recommendation would be (attribute, interface, abstract class, or combination thereof) for the following implementation:

    /// <summary>
    /// Loads class specific information into a list for serialization. The class must extend PlugIn.
    /// The filenames parameter is passed from a FileDialog.
    /// </summary>
    /// <param name="filenames">Accepts any number of filenames with fully qualified paths.</param>
    public static void ExtractPlugInData(params string[] filenames)
    {
     List<Type> l;

     foreach (string f in filenames)
     {
     Assembly a = Assembly.LoadFrom(f);
     // lambda expression selects any class within a library extending the abstract PlugIn class
     l = a.GetTypes().Where(type => typeof(PlugIn).IsAssignableFrom(type)).ToList<Type>();

     if (l.Count > 0)
            //  write data to serializable class
      WritePlugInData(f , l);
     else
      // throw exception
      WriteLine("{0} :: No PlugIn Data Found" , a.FullName);
            }
    }

I realize there are advantages and disadvantages to each method. Obviously, attributes require some reflection (as do abstract extension and interface implementation). An abstract class takes our only base inheritance, and any future changes in an interface can break any existing plugins. So, as I see it, those are the disadvantages.

Performance is not an issue (unless there is something I don't see) since any reflection is only done once when a qualified class is extracted. The key pieces of data that are getting saved is a name for the plugin ("MyPlugIn"), the namespace ("SuperPlugIn.PlugInClass"), and the startup path for the .dll. Right now, with the abstract PlugIn class, the extension of the properties is enforced. This is more or less the same result if we implement an interface (IPlugIn).

We are allowing custom plugins to be written by end-users. With the plugins we are writing in-house, it is easy to teach and enforce a required structure for our application to instance a qualified class. However, I'm also considering the difficulties or inconvenience to the end-user should there be a major change.

All comments, suggestions, and questions welcome!!

Note: thanks go to Jon Skeet for the lambda expression in the snippet. :)

EDIT: I should have noted in the beginning that this is intended to be platform independent (i.e. Mono).

UPDATE: Based on the excellent recommendations, comments, and links below, a mix of attributes and interfaces is the best approach. Attributes let you load the assembly and check for required information and implementations rather safely without instancing the plugin classes/objects. This is ideal in situations where 3rd party or end users are allowed to create custom plugins. We can check to ensure that the proper contract implementation is in place where the attribute says it's suppose to be. We can check for required dependencies and resources and alert the developer of any problems before anything is instanced.

+1  A: 

Assembly.GetTypes is a very expensive call, and I would avoid it where possible. (App startup time matters)

The faster way to do this is probably (I haven't benchmarked) an assembly-level attribute, which would be used like this:

[assembly: PluginClass(typeof(MyPlugin), more info)]

You can then call GetCustomAttributes on the Assembly, which would probably be much faster than GetTypes.

Using LINQ:

filenames.SelectMany(f => 
        Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true)
        .Cast<PluginClassAttribute>()
        .Select(a => a.PluginType)
).ToList();
SLaks
What happens if there is an exception being thrown for one of them?
Hamish Grubijan
This Lambda is pretty heavy ...
Hamish Grubijan
If an exception occurs while loading an assembly, the `ToList` call will throw, and you won't get anything. If you want to use a `foreach` with a `catch` block, feel free.
SLaks
@SLaks: I've already tested for libraries that don't extend my current use of PlugIn. As you can see in the snippet I gave, I call ToList. If there are no extension of my PlugIn, the list has a count of 0. Keep in mind that the Assembly.GetTypes is only called when a library is first loaded. I extract any pertinent data for the Activator.CreateInstance to do it's job.
dboarman
+2  A: 

You want your end users to write plugins? I don't think that's a very good idea, unless your end users are programmers.

I'm going to keep my answer short this time since this is a pretty big honkin' dupe:

  • Plug-in architectures almost always involve classes in an external assembly implementing a specific interface in a common assembly;

  • There are already dozens of cookie-cutter .NET plugin implementations including Microsoft's own Managed Extensibility Framework. Don't reinvent the wheel.

Edit: For Mono, check out Mono.Addins.

Aaronaught
Apologies for the pretty big honkin' dupe. I guess I failed to search on 'plugin'. ;/ Anyhoo...I'm looking at the links you provided.
dboarman
@dboarman: Don't worry about it, I meant for it to sound light-hearted, I guess it came off as snarky instead. I'm not dishin' out downvotes or anything, just wanted to give a clear reason for my brief answer and let you know that it's a fairly common requirement and that there are many good resources on the topic.
Aaronaught
:) no worries...you had some great references. Only problem is that MEF is available in .NET 4.0 and since we are aiming towards Mono, this won't be possible. BTW, +1 for the great references!!!
dboarman
@dboarman: Didn't realize you were targeting Mono. Too bad that you won't be able to use MEF, but Mono.Addins might help you (link added to answer).
Aaronaught
Thanks for adding the link...I added it in a comment to Jay as well... :) There is also a benefit to 'rolling our own' plugin framework in that I get to learn a whole lot.
dboarman
@dboarman: Learning is a great goal and I'd absolutely encourage building this sort of thing for fun/general knowledge; just be careful not to mix educational code with production code if you don't have to.
Aaronaught
@Aaronaught: our educational code is actually maturing at a rapid pace. We are moving forward with an Attribute/interface mix. With the attributes we are able to determine a lot of configuration properties up-front without having to actually instance any classes other than the attribute class(es). Then we are able to determine inheritances/implementations and build an organized view of an end-user's custom plugin.
dboarman
@dboarman: Good to hear! I kind of figured it would turn out to be a mix, much like what Mono does. Attributes as configuration, interfaces as contracts. Hopefully the end result turns out well for you.
Aaronaught
+1  A: 

I'd probably tend to use attributes. Extending the base class system with metadata is kind of exactly what they're for, and saying 'this class is a plugin' certainly fits that bill.

kyoryu
@kyoryu: I'm beginning to lean towards the attributes. See my comment above for Mono.Addins. I think it's funny - MS says interface, Mono says attributes.
dboarman