【问题标题】:Porting code from Reflection to using an Interface, some API's not implemented将代码从反射移植到使用接口,某些 API 未实现
【发布时间】:2011-03-07 09:28:00
【问题描述】:

我有一些严格使用反射实现的旧 C# 插件代码。在修复一些 C# 2.0 -> 4.0 兼容性问题 ("Load from Remote Source") 时,我决定摆脱旧的反射代码并用接口替换它。需要该接口是因为现在需要将插件加载到它们自己的 AppDomain 中,然后进行编组。

我可以浏览数百个插件的源代码,并通过简单的搜索和替换让它们全部实现新的IPlugin 接口。除了在一个关键的地方外,这很好用。 我正在寻找一个轻松的出路。

RunPlugin() 方法可以通过以下两种方式之一实现,但不能同时实现:带参数或不带参数。如果我在界面中包含它,我必须在每个插件中都实现。调用者根据实现哪一个来调用一个参数方法或无参数方法。调用程序集现在通过反射解决了这个问题。

为了避免我可以为插件创建一个包装器类,该包装器实现了接口,但是我必须对每个插件进行大量编辑,以便为 API 的许多方法中的每一个包含一个覆盖。

一些示例代码(这不一定有效!现在都在转换中!):

界面(示例):

// In IPlugin.cs / IPlugin.dll
namespace Plugin
{
    public interface IPlugin
    {
           // Many, many happy API things like this...
           void SetupOptions(Hashtable options);
           // (examples elided)

           // And then these two... either one or the other
           // is implemented, but not both.
           XmlDocument RunPlugin(Updater u);
           XmlDocument RunPlugin();
    }
}

所谓的大会...我有很多这些。我可以很容易地添加“:IPlugin”。这显然不会编译,因为它没有实现单参数RunPlugin()。

namespace Plugin
{
      public class FileMaintenance : IPlugin
      {
          public void SetupOptions(Hashtable options)
          {  // Code elided
          } 

          public XmlDocument RunPlugin()
          {  // Code elided
          }
      }
}

最后是调用代码。这实际上是它过去的样子,回到反射代码中:

public XmlDocument RunPlugin(PluginRunner.Updater u)
{
    Type [] paramTypes = new Type [0];
    MethodInfo runInfo = repType.GetMethod("RunPlugin", paramTypes);
    if (runInfo == null)
    {
        paramTypes = new Type [1];
        paramTypes[0] = u.GetType();
        runInfo = repType.GetMethod("RunPlugin", paramTypes);
        if (runInfo == null)
            throw new Exception;
    }
    Object[] parameters;
    if ( paramTypes.Length == 0)
        parameters = new Object[0];
    else
    {
        parameters = new Object[1];
        parameters[0] = u;
    }
    Object returnVal;
    try 
    {
        returnVal = runInfo.Invoke(repObj,parameters);
    }
    catch (Exception e)
    {
            }
         // Rest of code omitted
 }

请记住:我正在寻找正确的方法来修复这个旧代码和手动编辑代码的最少数量之间的平衡。

【问题讨论】:

  • 在反射代码中,您会发现一个方法接受一个参数,该参数的类型是提供的更新程序实例的运行时类型;这很重要吗? (是否有Updater的子类,有些插件只处理特定的子类?)
  • 在旧的反射代码中,它只是在寻找一种方法,要么不实现参数,要么真正实现。在这种情况下,类型(“updater”)并不重要。

标签: c# api reflection interface


【解决方案1】:

我主张创建两个额外的接口:

public interface IRunnablePlugin : IPlugin
{
    XmlDocument RunPlugin();
}

public interface IParamRunnablePlugin : IPlugin
{
    XmlDocument RunPlugin(object parameter);
}

然后让您的所有插件实现其中一个。唯一需要区分的是在实际调用RunPlugin 时。所有其他时间您都可以将其称为简单的IPlugin。

例如,要执行调用,您需要执行以下操作:

IPlugin plugin = ...;

IRunnablePlugin runnable = plugin as IRunnablePlugin;
IRunnableParamPlugin param = plugin as IRunnableParamPlugin;

XmlDocument output;

if(param != null)
{
    output = param.RunPlugin(parameter);
}
else if(runnable != null)
{
    output = runnable.RunPlugin();
}
else
{
    throw new InvalidOperationException();
}

请注意,技术上没有限制允许开发人员仅实现其中一个版本,但这应该不是问题。在这段代码中,您首先检查是否存在参数化版本,然后是无参数版本,如果都没有找到则抛出异常。

【讨论】:

  • 那么在调用RunPlugin的时候,我如何判断哪些接口被实现了呢?因为除非我从插件中以某种方式收集到,否则我实际上不会知道调用者。
【解决方案2】:

不幸的是,有一个或另一个 RunPlugin 方法是从一开始就添加到这个设计中的阿喀琉斯治疗。这是一个不稳定的设计决定,将很难处理。

一种可能性是将两个重载都添加到IPlugin,并让插件通过抛出NotImplementedException 来指示他们没有实现的重载。那可能不会给您带来太多收益,也可能不会给您带来任何收益。

另一种可能性是两个接口,IPlugin 和IPluginWithArgs,并检测给定插件正在实现的接口并从那里开始。也很丑。

另一种可能性是扩展方法public static void RunPlugin(this IPlugin plugin),它基本上隐藏和整理你已经开始的反射。我认为这也不会给你带来任何好处。

【讨论】:

    【解决方案3】:

    你可以有一个像这样的基本接口:

    public interface IPlugin {
               // Many, many happy API things like this...
               void SetupOptions(Hashtable options);
               // (examples elided)
    
               XmlDocument RunPlugin();
    }
    
    //
    // And another interface extending the IPlugin that defines the update version
    //
    
    public interface IUpdaterPlugin : IPlugin {
               XmlDocument RunPlugin(Updater u);
    
    }
    

    要运行适当的插件...

    if( plugin is IUpdaterPlugin)
        plugin.RunPlugin(updater);
    else if(plugin is IPlugin)
        plugin.RunPlugin();
    

    【讨论】:

      【解决方案4】:

      所以,你说...

      为了避免我可以为插件创建一个包装器类,该包装器 实现了接口,但是我必须大量编辑每个 插件为 API 的许多方法中的每一个都包含一个覆盖。

      我不太确定我是否理解包装类的问题,但我们可能正在考虑不同的事情。

      我正在考虑的包装类将采用带有零参数的接口,并使其看起来像带有一个参数的接口。当调用“RunPlugin”时,该参数将被忽略。

      这将删除接口类型上的任何分支,您只需要创建一个类 - 它可以包装无参数接口的任何实例。所以每个插件不应该有任何特殊的代码。只需在创建插件实例时创建包装器即可。

      (顺便说一句,这是Adapter pattern。)

      【讨论】:

      • 包装类仍然需要做一些恶作剧来确定最终的实际实现是否具有该方法的零参数版本或一参数版本。因此,我仍然需要反射、不同的接口或编辑每个实现类来添加这个额外的调用。使用包装器,我只是将复杂性从原始调用者转移到包装器类中。
      • @clintp:我可能误解了这个问题,但我的建议是包装类只包装零参数的实现。你是对的,在某些时候,你必须知道你是在处理单参数接口还是无参数接口。我希望当插件实际实例化时你应该能够非常简单地做到这一点......但也许我只是不太了解你的情况。
      • @clintp:如果不清楚...包装器实现正常的单参数接口。一旦零参数接口通过包装器暴露为一参数接口,一切看起来就像一个单参数接口。
      猜你喜欢
      • 2015-10-21
      • 2021-12-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-24
      相关资源
      最近更新 更多