重命名接口而不破坏代码

3
如果我想重命名接口而不破坏现有代码,这个方法可行吗?
旧接口:
namespace RenameInterfaceNicely
{
    /// <summary>
    /// The old name of the interface.
    /// </summary>
    [Obsolete("IFoo is replaced by IFooNew.")]
    public interface IFoo
    {
        void Test();
    }
}

新的、更名过的界面:
namespace RenameInterfaceNicely
{
#pragma warning disable 0618
    /// <summary>
    /// IFooNew new name for the interface IFoo.
    /// </summary>
    public interface IFooNew : IFoo
    {
    }
#pragma warning restore 0618

}

如果我有一个使用旧界面的类Foo,现在我要改用新界面。那么Foo的用户会遇到麻烦吗?

变更前的Foo:

public class Foo : IFoo
{
    public void Test()
    {    
        // Do something       
        return;
    }
}

变更后的Foo:

public class Foo : IFooNew
{
    public void Test()
    {    
        // Do something       
        return;
    }
}

现有代码示例:

// Old code
IFoo oldFoo = new Foo();
oldFoo.Test();
IFoo oldFoo2 = new Bar().GetFoo();

...

Bar bar = new Bar();
bar.DoSomethingWithFoo(oldFoo);

新代码示例:

// New code
IFooNew newFoo = new Foo();
newFoo.Test();
IFooNew newFoo2 = new Bar().GetFoo();

...

Bar bar = new Bar();
bar.DoSomethingWithFoo(newFoo);

这个问题是受到这个问题的启发: .NET API破坏性变更的明确指南 以及有人通过重命名我正在使用的一些接口来进行破坏性变更。


不知道这种类型的使用方式,我们无法确定。这是否可能破坏程序?是的。几乎任何对公共API所做的任何更改都会以某种方式成为破坏性更改。 - Servy
3个回答

2
在真正的重命名情况下:每个人都需要重新编译以获取该更改。坦白地说,这是一种危险的更改。接口是合同,不应该被重命名。但如果无法避免重命名,则至少可以让他们知道要将其更改为什么。
--
然而,实际上:你所展示的并不是一个重命名;它是一个半强制性的扩展;IFoo仍然存在并且仍然具有旧功能。假设你希望人们开始提供IFooNew实现。这是合理的,但你应该确保如果消费者只实现了IFoo,则它仍然可以工作。

1

重命名公共类型或成员是一种破坏性的更改。

  • 您无法获得源代码兼容性。明确提到接口名称的源代码需要进行更改。
  • 通过重命名重构,可以自动更改同一解决方案中的依赖代码。
  • 您可以尝试使用类型转发来使依赖于接口的二进制代码继续工作。这是为在程序集之间移动类型而设计的,而不是为了重命名,因此我不确定它是否有效。

从旧接口派生新接口仍然是破坏性的:

  • 在显式实现方法时,需要引用该方法所在的类型。
  • 实现旧接口的类将无法实现新接口。
  • 返回旧接口的代码仍将返回旧接口。

类型转发在这里没有帮助。它允许更改类型的程序集名称,但除此之外,类型的完全限定名称必须保持不变。请参阅此问答和其中的评论:https://dev59.com/kG455IYBdhLWcg3wFP9u - Palec

-1

你可能想要考虑将 IFoo 的定义复制到 IFooNew 中,而不是将其实现为子接口。这样做可以更容易地删除 IFoo,特别是如果有人明确地实现了该接口。

请考虑以下内容:

public class Foo : IFoo {
    void IFoo.Test() {}
}

在升级到您的新版本(v2)后,他们现在会收到您的Obsolete编译器警告,希望他们能在某个时候进行更改,以便实现IFooNew而不是IFoo并更改所有引用。但由于他们正在使用显式实现,所以必须将实现保留为IFoo.Test而不是IFooNew.Test

public class Foo : IFooNew {
    void IFoo.Test() {}
}

他们不再收到编译器警告,一切都很好。在您的库的下一个版本(v3)中,您将Test方法移动到IFooNew中,消除了IFoo,现在他们再次升级到最新版本——他们现在又必须更改其代码。

public class Foo : IFooNew {
    void IFooNew.Test() {}
}

理想情况下,您只需要让他们修改一次代码而不是两次。

如果这些接口在语义上是相同的但技术上是无关的,那么一个实现了新接口的对象将永远不能传递给任何期望旧接口的方法,反之亦然。这几乎肯定会成为一个广泛分布的库无法启动的问题,而且比 OP 的方法更具有破坏性。 - Servy
@Servy 或许我误解了。我认为旧接口最终会被删除,那么,在某个时候,您肯定会造成巨大的破坏性变化,并迫使人们切换到您的新接口以进行升级。在这种情况下,我宁愿进行一轮更改而不是两轮。但如果原始接口将永远存在,那么我同意您的看法,并且会将其保持不变。 - Joe Enos
想法是将Test()方法保留在旧接口中,以便现有代码可以正常工作。当编译代码时,所有旧接口的用户当然会收到警告。我还考虑了一个场景,即在稍后版本中(在用户有机会修复其代码之后),可以通过删除IFoo接口并将Test()方法移动到新接口IFooNew中来进行重大更改。 - Nils Lande

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接