你能同时使用两个同名的类型吗?

Can you simultaneously use two identically named types?

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

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

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

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

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

因为我不能期望所有的插件实现者立即更新他们的插件,所以我想创建一个适配器,将 "Unknown" 的 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 或其名称空间的情况下实现此类适配器?

你的要求没有意义。将 PluginName 添加到接口会破坏实现该接口的所有现有代码。您不为新接口指定新名称或新名称空间的原因是您希望自动使用该新接口。但是您不想破坏现有代码。不能两全其美。

最简单的工作方法是

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

public interface IPlugin2 : IPlugin
{
    string PluginName { get; }
}

在某些时候,如果您发布了一个包含不再支持现有插件的重大更改的新版本,您可以选择合并接口。

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

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

参考Interfaces.dll

的两个版本
  • 创建一个项目来放置适配器 class。
  • 照常在 Visual Studio 中添加对最新版本的引用。
  • 右击项目然后select"Unload Project"
  • 再次右击项目,select"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>
    
  • 保存并关闭项目文件
  • 右击项目然后select"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";
    }