简体   繁体   English

C#通用方法与强制转换

[英]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. 我正在使用简单的插件架构控制输入和输出,用C#编写一个程序(目前是3.5,但很可能适应其他需要的版本)。 Each plugin is a DLL that is loaded when the user chooses the plugins to use. 每个插件都是在用户选择要使用的插件时加载的DLL。

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. 该版本实质上将转换移动到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: 它不适用于void方法,但我可以包含一个Method版本:

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. 我个人喜欢后者的外观,虽然我有点担心我的返回类型测试( Methods[methodName].ReturnType != typeof(T) )可能过于简单了。 Interestingly, it was originally !(Methods[methodName].ReturnType is T) , but that always seemed to fail. 有趣的是,它最初是!(Methods[methodName].ReturnType is T) ,但似乎总是失败。

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. 澄清:这是一个手动,非常有限的插件系统,不使用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. 我更感兴趣的是泛型方法本质上是否比预期调用者在这种情况下投射更好/更差的问题。

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);
     }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM