将 public 方法添加到 public class 是一项重大更改吗?
Is adding a public method to a public class a breaking change?
例如只有一个 Age
方法的 Person
:
public class Person
{
public int Age()
{
return 6;
}
}
增加了一个Height()
方法。
public class Person
{
public int Age()
{
return 6;
}
public int Height()
{
return 6;
}
}
这是一个重大变化吗?注意Person
不是密封的
tl;dr;
Eric Lippert 又是对的..
感谢 Tim Schmelter 对 Eric Lippert 的 blog post 的 link 评论,我更改了我原来的答案。
向现有 class 添加 public 方法或 属性 是 潜在的 重大更改。可能不是,但可能是!
加长版
重大更改意味着您正在更改类型的 public API,使得使用当前 API 的现有代码将不再能够编译。
明显的破坏性变化是删除 public 成员,或者以这样一种方式改变它们,使使用您的 class 的代码无法再编译。
此类更改可能是(部分列表!):
- 向现有方法添加 non-optional 参数。*
- 将现有方法的参数数据类型更改为不是原始参数基类型的数据类型
- 将 属性 的数据类型更改为不是从原始数据类型派生的数据类型
- 将方法的 return 类型更改为不是从原始类型派生的类型
正如 Eric 所展示的那样,即使添加一个新的 public 方法也是 潜在的 一个破坏性的变化,就像他在博客中提到的破坏性过载一样,改变类型也是如此出于完全相同的原因,更具体类型(意味着派生自原始类型)的属性或参数也可能是重大更改。
但是,Eric 还指出,此类情况非常具体且不太可能发生,因此可以安全地假设您不会遇到它们。
This particular "flavour" of breaking change is an odd one in that it makes almost every possible change to the surface area of a type into a potential breaking change, while at the same time being such an obviously contrived and unlikely scenario that no "real world" developers are likely to run into it.
*虽然我们讨论的是重大更改,但更改可选参数的默认值也是一个潜在的重大更改 - 因为可选参数的工作原理是将默认值注入调用方法,作为传递给调用方法的参数有一个可选参数。更改默认值意味着您必须重新编译使用该方法的所有内容。
如果派生的 class 已经声明了 Hight
属性 并且 "treat warnings as errors" 已打开,这将是一个重大更改。
除非或直到此 class 的所有者添加 new
关键字,否则此更改将产生 CS0108 warning(成为错误)。
感谢 Zohar 和 Tim 提供的链接; Zohar 的回答是正确的。如果不完全清楚中断情况是什么,请考虑:
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
的类型。
在实践中,这种重大更改非常罕见,因此我们明确决定在设计语言功能和库时不考虑它的重要性。
要向列表添加可能的重大更改,这里有一个相当讨厌的更改:
A 公司发布了宏伟的新 class 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)
.
例如只有一个 Age
方法的 Person
:
public class Person
{
public int Age()
{
return 6;
}
}
增加了一个Height()
方法。
public class Person
{
public int Age()
{
return 6;
}
public int Height()
{
return 6;
}
}
这是一个重大变化吗?注意Person
不是密封的
tl;dr;
Eric Lippert 又是对的..
感谢 Tim Schmelter 对 Eric Lippert 的 blog post 的 link 评论,我更改了我原来的答案。
向现有 class 添加 public 方法或 属性 是 潜在的 重大更改。可能不是,但可能是!
加长版
重大更改意味着您正在更改类型的 public API,使得使用当前 API 的现有代码将不再能够编译。
明显的破坏性变化是删除 public 成员,或者以这样一种方式改变它们,使使用您的 class 的代码无法再编译。 此类更改可能是(部分列表!):
- 向现有方法添加 non-optional 参数。*
- 将现有方法的参数数据类型更改为不是原始参数基类型的数据类型
- 将 属性 的数据类型更改为不是从原始数据类型派生的数据类型
- 将方法的 return 类型更改为不是从原始类型派生的类型
正如 Eric 所展示的那样,即使添加一个新的 public 方法也是 潜在的 一个破坏性的变化,就像他在博客中提到的破坏性过载一样,改变类型也是如此出于完全相同的原因,更具体类型(意味着派生自原始类型)的属性或参数也可能是重大更改。
但是,Eric 还指出,此类情况非常具体且不太可能发生,因此可以安全地假设您不会遇到它们。
This particular "flavour" of breaking change is an odd one in that it makes almost every possible change to the surface area of a type into a potential breaking change, while at the same time being such an obviously contrived and unlikely scenario that no "real world" developers are likely to run into it.
*虽然我们讨论的是重大更改,但更改可选参数的默认值也是一个潜在的重大更改 - 因为可选参数的工作原理是将默认值注入调用方法,作为传递给调用方法的参数有一个可选参数。更改默认值意味着您必须重新编译使用该方法的所有内容。
如果派生的 class 已经声明了 Hight
属性 并且 "treat warnings as errors" 已打开,这将是一个重大更改。
除非或直到此 class 的所有者添加 new
关键字,否则此更改将产生 CS0108 warning(成为错误)。
感谢 Zohar 和 Tim 提供的链接; Zohar 的回答是正确的。如果不完全清楚中断情况是什么,请考虑:
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
的类型。
在实践中,这种重大更改非常罕见,因此我们明确决定在设计语言功能和库时不考虑它的重要性。
要向列表添加可能的重大更改,这里有一个相当讨厌的更改:
A 公司发布了宏伟的新 class 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)
.