【问题标题】:Is adding a public method to a public class a breaking change?向公共类添加公共方法是一项重大更改吗?
【发布时间】:2018-03-08 10:30:50
【问题描述】:

例如一个 Person 只有一个 Age 方法:

public class Person
{
    public int Age()
    {
        return 6;
    }
}

添加了Height() 方法。

public class Person
{
    public int Age()
    {
        return 6;
    }
    public int Height()
    {
        return 6;
    }
}

这是一个突破性的变化吗?注意Person没有密封。

【问题讨论】:

  • 您对重大变化的理解是什么?
  • 相当基本。重大更改可能会改变某些客户的行为
  • 我注意到英语的拼写是——奇怪的是——Height——而且身高和年龄是整数有点奇怪。当然Height 应该是代表米的double,而Age 应该是TimeSpan,对吧?它们可能应该是属性。
  • @Eric Lippert 在等待您回答我的任何问题后我非常高兴我错过了拼写Height

标签: c# versioning


【解决方案1】:

tl;dr;

Eric Lippert 是对的,再次......

感谢 Tim Schmelter 的评论以及 Eric Lippert 的blog post 的链接,我已经更改了原来的答案。

向现有类添加公共方法或属性可能是一项重大更改。可能不是,但可能是!

加长版

重大更改意味着您正在更改您的类型的公共 API,使得使用当前 API 的现有代码将不再能够编译。

明显的重大更改是删除公共成员,或以无法再编译使用您的类的代码的方式更改它们。 此类更改可能是(部分列表!):

  • 向现有方法添加非可选参数。*
  • 将现有方法的参数的数据类型更改为不是原始参数的基本类型的数据类型
  • 将属性的数据类型更改为不是从原始数据类型派生的数据类型
  • 将方法的返回类型更改为不是从原始类型派生的类型

正如 Eric 所演示的,即使添加一个新的公共方法可能也是一个突破性的变化,就像他在博客中提到的突破性重载一样,将属性或参数的类型更改为更具体的类型也是如此(意思是源自原始类型)也可能是一个重大更改,原因完全相同。

不过,Eric 也指出,此类场景非常具体且不太可能发生,因此可以安全地假设您不会遇到它们。

这种突破性变化的特殊“味道”很奇怪,因为它使几乎所有可能对类型的表面积发生的变化都变成了潜在的突破性变化,同时又是一种明显人为且不太可能发生的情况,以至于没有“现实世界”的开发人员可能会遇到它。


*虽然我们讨论的是重大更改,但更改可选参数默认值也是一个潜在的重大更改 - 因为可选参数通过将默认值注入调用方法作为传递给具有可选参数。更改默认值意味着您必须重新编译使用该方法的所有内容。

【讨论】:

  • A breaking change means you are changing the public API of your type in such a way that existing code working with the current API will no longer be able to compile. 这并不一定意味着编译时更改。二进制兼容性也应该是其中的一部分:预先存在的编译二进制文件应该仍然可以正常工作。
【解决方案2】:

感谢 Zohar 和 Tim 的链接;佐哈尔的回答是正确的。如果不完全清楚破坏场景是什么,请考虑:

public sealed class Person
{
  public TimeSpan Age { get; set; }
}
public sealed class Building
{
  public double Height { get; set; }
}
public sealed class Weird 
{
  public static void M(Func<Person, double> f) {}
  public static void M(Func<Building, double> f) {}
  public static void N() {
    M(x => x.Height);
  }
}

这个程序编译没有错误,因为重载解析正确地推断出x 必须是Building。但是如果我们将属性public double Height {get; set;} 添加到Person,那么Weird.N 包含一个歧义错误,因为我们无法知道哪个是x 的类型。

在实践中,这种重大更改非常罕见,因此我们明确决定在设计语言功能和库时不考虑它的重要性。

【讨论】:

  • .NET 运行时库是否允许向现有密封类添加属性/方法?
【解决方案3】:

如果任何派生类已经声明了 Hight 属性并开启了“将警告视为错误”,这将是一个重大更改。

除非或直到此类的所有者添加 new 关键字,否则更改将产生 CS0108 warning(这将成为错误)。

【讨论】:

    【解决方案4】:

    要在列表中添加可能的重大更改,这里有一个相当讨厌的:

    A 公司发布了其宏伟的新课程Foo。 B 公司喜欢新东西,但错过了它决定通过完全根据他们的需求量身定制的扩展方法来实现的一些功能:

    namespace A {
         public class Foo { ... } }
    
    namespace B {
         static class Extensions {
             public static string Frob(this Foo foo) { .... } } }
    

    A 公司意识到它可以做得更好,并决定使用一些新功能升级 Foo,并决定添加一个具有通用实现的新方法 string Frob()

    A 公司发布了更新后的库,B 公司很高兴地用新版本更新了它的软件。一切都编译得很好……但同样发生了重大变化; foo.Frob() 现在解析为 Foo.Frob() 而不是扩展方法 Extensions.Frob(this Foo foo)

    【讨论】:

    • ...更糟糕的是,B 公司的客户在更新几周后才发现 tgat 数据被破坏...确实很糟糕...
    猜你喜欢
    • 2023-03-25
    • 2018-09-27
    • 2015-12-17
    • 1970-01-01
    • 2020-09-22
    • 1970-01-01
    • 2011-04-19
    • 2013-02-26
    • 1970-01-01
    相关资源
    最近更新 更多