簡體   English   中英

將代碼從反射移植到使用接口,某些 API 未實現

[英]Porting code from Reflection to using an Interface, some API's not implemented

我有一些舊的 C# 插件代碼,它們是用反射嚴格實現的。 在修復一些 C# 2.0 -> 4.0 兼容性問題( “從遠程源加載” )時,我決定去掉舊的反射代碼並用接口替換它。 需要該接口是因為插件現在需要加載到它們自己的 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
 }

請記住:我正在尋找修復舊代碼的正確方法和手動編輯最少代碼之間的良好平衡。

我主張創建兩個額外的接口:

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方法是從一開始就添加到此設計中的致命弱點。 這是一個不穩定的設計決定,處理起來會很困難。

一種可能性是將兩個重載都添加到IPlugin ,並讓插件通過拋出NotImplementedException指示它們沒有實現的那個。 那可能不會給你帶來太多好處,它可能不會給你帶來任何東西。

另一種可能性是兩個接口, IPluginIPluginWithArgs ,並檢測給定插件正在實現哪個接口並從那里開始。 也丑。

另一種可能性是擴展方法public static void RunPlugin(this IPlugin plugin) ,它基本上隱藏和整理你已經開始的反射。 我也不認為這能給你帶來什么。

你可以有一個像這樣的基本界面:

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

所以你說...

為了避免我可以為插件創建一個包裝器類,該包裝器實現了接口,但是我必須大量編輯每個插件以包含對 API 的許多方法中的每一個的覆蓋。

我不太確定我是否理解包裝類的問題,但我們可能正在考慮不同的事情。

我正在考慮的包裝器類將采用具有零參數的接口,並使其看起來像具有一個參數的接口。 調用“RunPlugin”時,該參數將被忽略。

這將刪除您的接口類型的任何分支,您只需要創建這一類 - 它可以包裝無參數接口的任何實例。 所以每個插件都不應該有任何特殊的代碼。 只需在創建插件實例時創建包裝器即可。

(順便說一下,這是適配器模式。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM