可以同时使用两个同名的类型吗?

本文关键字:两个 类型 | 更新日期: 2023-09-27 18:08:17

假设我有一个强命名程序集Interfaces.dll的版本1,它包含一个IPlugin接口:

public interface IPlugin
{
    void InstallSelf(IPluginManager pluginManager);
}

一些插件已经实现,一切运行顺利。

有一天,我们决定插件应该有名字,这样我们就可以看到我们加载了哪些插件。所以我们产生了Interfaces.dll的版本2,包含:

public interface IPlugin
{
    string PluginName { get; }
    void InstallSelf(IPluginManager pluginManager);
}

因为我不能期望所有的插件实现者都立即更新他们的插件,我想创建一个适配器,在旧的插件版本中添加一个"未知"的PluginName。有没有实现这样一个适配器的方法?我希望它看起来像这样:

public sealed class PluginAdapter_v1_v2 : IPlugin[v2]
{
    private readonly IPlugin[v1] _plugin;
    public PluginAdapter_v1_v2(IPlugin[v1] plugin)
    {
        _plugin = plugin;
    }
    public string PluginName => "Unknown";
    public void InstallSelf(IPluginManager pluginManager)
    {
        _plugin.InstallSelf(pluginManager);
    }
}

但是因为它们都叫IPlugin,这是行不通的。另一种方法是将IPlugin重命名为IPlugin_v1IPlugin_v2,或者对其名称空间进行类似的重命名,但这将要求每次出现新版本的Interfaces.dll时都重命名接口(或名称空间),并且要求实现者在更新到最新的程序集版本时更改对接口(或名称空间)的所有引用。那么,问题又来了:

是否有可能实现这种类型的适配器,而不重命名IPlugin或其名称空间?

可以同时使用两个同名的类型吗?

有一种方法可以做到这一点,但它相当冗长。在此过程结束时,我们将结束从IPlugin [v1]到IPlugin [v2]的适配器类。

参考Interfaces.dll的两个版本

  • 创建一个项目来放置适配器类。
  • 在Visual Studio中正常添加对最新版本的引用。
  • 右键单击项目,选择"卸载项目"
  • 再次右键单击项目,选择"Edit [projectname].csproj"
  • 查找如下的部分:

    <Reference Include="Interfaces">
      <HintPath>path'to'library'v2'Interfaces.dll</HintPath>
    </Reference>
    

    并替换为:

    <Reference Include="Interfaces_v1">
      <HintPath>path'to'library'v1'Interfaces.dll</HintPath>
      <Aliases>Interfaces_v1</Aliases>
    </Reference>
    <Reference Include="Interfaces_v2">
      <HintPath>path'to'library'v2'Interfaces.dll</HintPath>
      <Aliases>Interfaces_v2</Aliases>
    </Reference>
    
  • 保存并关闭项目文件
  • 右键单击项目,选择"Reload project"

参考IPlugin的两个版本

Aliases元素添加到项目文件中意味着类型不再导入到全局命名空间中,而是导入到Interfaces_v1Interfaces_v2命名空间中。这意味着我们可以通过不同的名称访问两个IPlugin版本:

  • 在适配器源文件的顶部(在任何using语句之前),添加以下内容:

    extern alias Interfaces_v1;
    extern alias Interfaces_v2;
    

  • 可选地,为了整理代码,也添加一些类型别名:

    using IPlugin_v1 = Interfaces_v1::Interfaces.IPlugin;
    using IPlugin_v2 = Interfaces_v2::Interfaces.IPlugin;     
    

实现适配器

    public sealed class PluginAdapter_v1_v2 : IPlugin_v2
    {
        private readonly IPlugin_v1 _pluginV1;
        public PluginAdapter_v1_v2(IPlugin_v1 pluginV1)
        {
            _pluginV1 = pluginV1;
        }
        public string Name => _pluginV1.Name;
        public string Version => "Unknown";
    }

你的要求没有意义。将PluginName添加到接口将破坏实现该接口的所有现有代码。不给新接口指定新名称或新名称空间的原因是希望自动使用新接口。但是你不想破坏现有的代码。鱼和熊掌不可兼得。

最简单的工作方法是使用

public interface IPlugin
{
    void InstallSelf(IPluginManager pluginManager);
}
public interface IPlugin2 : IPlugin
{
    string PluginName { get; }
}

在某些时候,如果你发布了一个新的版本,对现有的插件进行了突破性的修改,那么你可能会选择合并接口。

当DLL的新版本准备好了新的接口定义时,它也包括所有旧的接口版本。所有现有的代码将继续编译和运行。您可以动态检测插件实现是否只实现了IPlugin,还是也实现了IPlugin2