【问题标题】:Can you simultaneously use two identically named types?你能同时使用两个同名的类型吗?
【发布时间】:2017-01-29 15:27:21
【问题描述】:

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

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

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

有一天,我们决定插件可能应该有名字,这样我们才能看到我们加载了哪些插件。因此,我们生成了Interfaces.dll 的第 2 版,其中包含:

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

因为我不能指望所有插件实现者都立即更新他们的插件,所以我想创建一个适配器,将“未知”的插件名称添加到旧插件版本。有没有办法实现这样的适配器?我希望它看起来像这样:

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 或其命名空间的情况下实现这种适配器?

【问题讨论】:

    标签: c# api-versioning


    【解决方案1】:

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

    引用Interfaces.dll 的两个版本

    • 创建一个项目来放置适配器类。
    • 照常在 Visual Studio 中添加对最新版本的引用。
    • 右键单击项目并选择“卸载项目”
    • 再次右键单击项目并选择“编辑[项目名称].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>
      
    • 保存并关闭项目文件
    • 右键单击项目并选择“重新加载项目”

    引用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";
        }
    

    【讨论】:

      【解决方案2】:

      你的要求没有意义。将PluginName 添加到接口会破坏实现该接口的所有现有代码。您不给新接口一个新名称或新命名空间的原因是您希望自动使用该新接口。然而,您不想破坏现有代码。你不能同时拥有它。

      最简单的工作方法是拥有

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

      在某些时候,如果您在新版本中进行了重大更改,而现有插件将不再受支持,那么您可以选择合并接口。

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

      【讨论】:

      • 这有助于定义一个PluginAdapter,它将v1插件适配为名称为“Unknown”的v2插件
      • 正如@Jamiec 所说,我的计划是搜索IPlugin 两个版本的所有实现,然后调整v1 插件以使其表现得像v2 插件并在主插件系统中使用v2 插件。定义适配器是我正在努力解决的问题。
      • @PhilipC 因为在我的回答中,接口有不同的名称,所以让PluginAdapter_v1_v2 实现IPlugin2 并拥有IPlugin 类型的私有字段是没有问题的。
      • 但我的问题是:你可以制作这样一个适配器不必重命名接口?我提到在问题中为接口赋予不同的名称,并说明了我不想这样做的原因。
      • @PhilipC 我在回答中解释了为什么无论如何这都达不到你想要的效果。
      猜你喜欢
      • 2015-12-02
      • 1970-01-01
      • 2023-02-24
      • 1970-01-01
      • 2017-11-29
      • 2017-05-15
      • 1970-01-01
      • 2021-10-30
      • 2016-08-01
      相关资源
      最近更新 更多