Thursday, November 20, 2008

Plugins and abstract static methods in C#

Here is what I ran into today. I'm currently working on this service that has to be expendable in some parts of the code. Because of the way things have to work, I figured I would use a technique based on plugins. I will look in a particular folder and load any assemblies available there. Then I will get all the types that derive from a certain base class and select the write plugin class based on some statically available information and then create an instance of this class...

...and that's where it all goes boom. Here is what I wanted to write as a base class:


public abstract class PluginBase
{
public abstract static string[] SupportedOperations { get; }

public abstract void ExecuteOperation(string operation);
}


I would then just loop trough the found plugin class types and check inside SupportedOperations to find out which plugin would be able to handle the task at hand. That won't work, because C# has no support for an abstract static member (so changing it into a method won't work either).

The reason for this, as I've read from several compiler designers at Microsoft, is that the compiler considers static members in derived classes as completely unrelated and it would be hard to resolve to the correct member and not very transparent to developers.

This also crushed any hopes of solving this with an interface, as an interface is implicitly abstract.

You can read a lot on the subject on these links:
http://msmvps.com/blogs/jon_skeet/archive/2008/08/29/lessons-learned-from-protocol-buffers-part-4-static-interfaces.aspx
http://blogs.msdn.com/ericlippert/archive/2007/06/21/calling-static-methods-on-type-variables-is-illegal-part-three.aspx
http://bytes.com/forum/thread238034.html
http://weblogs.asp.net/justin_rogers/articles/61042.aspx

I guess this means I'm not the only one that would like to use this as a feature (let me know, trough the comments).

If you read the comments on the first link, you may also run into this article, that actually shows that abstract static members are possible in IL. It's just not supported in C# (or any other well known .NET language for that matter).

So basically what is it that I want? I want a way to specify that any class derived from some base class has a specific static member that I can call. This can't be done, so I now know.

How did I solve it? I really didn't, but I went and made some design decisions. First of all I will add the static member to the base class, but not make it abstract, so my base class has an implementation of it's own. The base class implementation will return an empty string[], so I know it doesn't support anything (it's the abstract base class, what did you expect?).

Then how do you handle derived classes that don't have their own implementation of the static member? This depends on the use of the plugins. In my case, they are used in a service that has to be able to run unattended. For this reason I decided to apply defensive programming and simply ignore classes that don't implement the static member themselves.
In other cases it may make perfect sense to throw an expection, or handle it differently.

I hope this makes sense. If you have questions just let me know.

2 comments:

  1. You could do exactly the same thing with attributes: SupportedOperationsAttribute placed on your IPlugin-derived class would do the job quite nicely. Moreover, it seems that you're optimizing prematurely: instead of instantiating plugins only once and then invoking get_SupportedOperations on every request you're incurring a significant overhead of using reflection again and again to iterate over exported types.

    ReplyDelete
  2. Hi Anton,
    Thanks for your comment. I like the idea of using attributes instead of a property. It would make for cleaner code. However, it doesn't solve my initial problem as I can not obligate a derived class to set this attribute.

    As for the reflection, in our design it isn't possible to keep an instance across multiple requests. These request are in fact not handled by the service directly, but are load balanced on a backend server, which will also be used for very different processes. Therefor it would not make sense to keep the plugins across multiple requests.

    Still I do get your argument. If it would be a normal request-response scenario, then it would be feasible to do this.

    ReplyDelete