我有一些旧的C#插件代码,严格使用Reflection实现。在修复一些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
}
请记住:我正在寻找修复此旧代码的正确方法与手动编写最少量编辑代码之间的平衡。
答案 0 :(得分:3)
我主张创建两个额外的接口:
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();
}
请注意,技术上没有限制,允许开发人员只实现其中一个版本,但这应该不是问题。在此代码中,您首先检查参数化版本是否存在,然后检查无参数版本,如果两者都未找到则抛出异常。
答案 1 :(得分:1)
不幸的是,使用一种或其他RunPlugin
方法是阿基里斯治愈,从getgo添加到此设计中。这是一个不稳定的设计决定,很难处理它。
一种可能性是将两个重载添加到IPlugin
,并让插件通过抛出NotImplementedException
来指示他们没有实现的重载。这可能不会给你买太多,它可能不会给你买任何东西。
另一种可能性是两个接口IPlugin
和IPluginWithArgs
,并检测给定插件正在实现哪个接口并从那里开始。也很难看。
另一种可能性是扩展方法public static void RunPlugin(this IPlugin plugin)
,它基本上隐藏并整理了你已经开始的反射。我不认为这会给你买任何东西。
答案 2 :(得分:0)
你可以有一个这样的基本界面:
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();
答案 3 :(得分:0)
所以,你说......
为了避免这种情况,我可以为插件创建一个包装类,即包装器 实现接口,但是我必须大量编辑每个接口 插件包含每个API的许多方法的覆盖。
我不太确定我理解包装类的问题,但我们可能会考虑不同的东西。
我正在考虑的包装类将接口带有零参数,并使其看起来像带有一个参数的接口。当调用“RunPlugin”时,该参数将被忽略。
这将删除接口类型的任何分支,你只需要创建这个类 - 它可以包装任何无参数接口的实例。因此,每个插件不应该有任何特殊代码。只需在创建插件实例时创建包装器。
(顺便说一下,这是Adapter pattern。)