MDB
MDB

Reputation: 75

C# Generic Method vs Casting

I'm writing a program in C# (3.5 at the moment, but likely to be adaptable to other versions as the need arises) that uses a simple plugin architecture to control input and output. Each plugin is a DLL that is loaded when the user chooses the plugins to use.

Since the actual plugin class isn't known until run time, I'm using reflection in a wrapper class to call methods and access properties of the plugins.

Up until now, I've been using the following to call methods on a plugin:

public object Method(string methodName, params object[] arguments) {
  // Assumed variables/methods/exceptions:
  //   Dictionary<string, MethodInfo> Methods: a cache of MethodInfo's
  //     of previously called methods.
  //   NoSuchMethodException: thrown if an unknown/unreachable method is
  //     requested. The message member contains the invalid method name
  //   void LoadMethod(string methodName, params object[] arguments): responsible
  //     for retrieving the MethodInfo's, or throw a NoSuchMethodException
  //   object Plugin: an instance of the dynamically loaded class.
  if (!Methods.ContainsKey(methodName)) {
    LoadMethod(methodName, arguments);
  }
  if (arguments != null && arguments.Length == 0) {
    arguments = null;
  }
  return Methods[methodName].Invoke(Plugin, arguments);
}

Which is used like:

string[] headers = (string[]) Plugin.Method("GetHeaders", dbName, tableName);

This works nicely, as long as the caller correctly casts the return value to the expected type. Plugins must implement certain interfaces, so the caller should know this type.

After doing some further work with reflection however, the following alternate form occurred to me:

public T Method<T>(string methodName, params object[] arguments) {
  if (!Methods.ContainsKey(methodName)) {
    LoadMethod(methodName, arguments);
  }
  if (Methods[methodName].ReturnType != typeof(T)) {
    // Could also move this into LoadMethod to keep all the throwing in one place
    throw new NoSuchMethodException(methodName);
  }
  if (arguments != null && arguments.Length == 0) {
    arguments = null;
  }
  return (T) Methods[methodName].Invoke(Plugin, arguments);
}

This one is used like:

string[] headers = Plugin.Method<string[]>("GetHeaders", dbName, tableName);

This version essentially moves the casting into the Method method. The caller obviously still needs to know the expected return type, but that was always going to be the case. It doesn't work for void methods, but I can include a version of Method for that:

public void Method(string methodName, params object[] arguments) {
  // Good for void methods, or when you're going to throw away the return
  // value anyway.
  if (!Methods.ContainsKey(methodName)) {
    LoadMethod(methodName, arguments);
  }
  if (arguments != null && arguments.Length == 0) {
    arguments = null;
  }
  Methods[methodName].Invoke(Plugin, arguments);
}

My question is - is one of these intrinsically better than the other (for a given value of 'better')? For instance, is one notably faster? Easier to understand? More supported?

I personally like the look of the latter, although I am a bit worried that my return type testing (Methods[methodName].ReturnType != typeof(T)) might be too simplistic. Interestingly, it was originally !(Methods[methodName].ReturnType is T), but that always seemed to fail.

The closest existing question to this I could find was Generic method to type casting, and some of the answers suggested that the latter method is more expensive than the former, but there's not much in the way of detail there (the question there is more towards implementation of the method rather than which is better).

Clarification: This is a hand-rolled, very limited plugin system, not using IPlugin. I'm more interested in the question of whether generic methods are inherently better/worse than expecting the caller to cast in this situation.

Upvotes: 4

Views: 1951

Answers (1)

ChaosPandion
ChaosPandion

Reputation: 78262

As to your question I think you should provide both. Simply have the generic version invoke the non-generic version. You get the best of both worlds. Regarding performance, consider how small the time it takes to cast an object is in comparison to dynamically invoking a method and building up and object array. It really is not worth taking performance into consideration here. I for one prefer the generic approach from a stylistic perspective but also consider that you could apply type constraints should the need arise.

public T Method<T>(string methodName, params object[] arguments)
{
    return (T)Method(methodName, arguments);
}

Side Bar

I'm thinking that your implementation should be casting to the expected interface if I understand your design. You really should not be using reflection if you know what methods the plugins should support.

var plugin = (IPlugin)Activator.CreateInstance(pluginType);
var headers = plugin.GetHeaders(dbName, tableName);

You could try something a bit different and require the plugin to implement an interface that allows them to register custom runtime behavior.

public interface IPlugin
{
     void Load(IAppContext context);
     void Unload();
}

Your loading method could look like this.

void LoadPlugins(Assembly a)
{
    var plugins = 
        a.GetTypes()
        .Where(t => typeof(IPlugin).IsAssignableFrom(t))
        .Select(t => (IPlugin)Activator.CreateInstance(t))
        .ToList();
     Plugins.AddRange(plugins);
     foreach (var p in plugins)
     {
         p.Load(Context);
     }
}

Upvotes: 3

Related Questions