API更改 - 类型公开可见定义的更改,包括其任何公共成员。这包括更改类型和成员名称,更改类型的基础类型,在类型的实现接口列表中添加/删除接口,添加/删除成员(包括重载),更改成员可见性,重命名方法和类型参数,为方法参数添加默认值,以及在类型和成员上添加/删除属性,以及在类型和成员上添加/删除泛型类型参数(我漏掉了什么吗?)。这不包括成员主体的任何更改,也不包括对私有成员的任何更改(即我们不考虑反射)。
二进制级别断裂 - 一种API更改,导致针对旧版API编译的客户端程序集可能无法加载新版本。例如: 更改方法签名,即使它允许以与之前相同的方式调用(即从void返回类型/参数默认值的重载)。
源代码级别断裂 - 一种API更改,导致旧版API编写的现有代码可能无法编译为新版本。但已经编译的客户端程序集的工作方式与以前相同。例如: 添加一个新的重载,可能导致以前不明确的方法调用出现歧义。
源代码级别静默语义更改 - 一种API更改,导致编写旧版API的现有代码在调用不同的方法时会静默更改其语义。但是,该代码应继续编译而无需警告/错误,并且以前编译的程序集应像以前一样工作。例如: 在现有类上实现新接口,导致在重载决策期间选择不同的重载。
最终目标是尽可能地整理出许多破坏性和静默语义API更改,并描述破坏效果的确切影响,以及哪些语言受到影响和不受影响。关于后者的扩展: 尽管某些更改会普遍影响所有语言(例如,向接口添加新成员将打破任何语言中的该接口实现),但某些更改需要非常特定的语言语义才能进入播放以获得中断。这通常涉及方法重载和与隐式类型转换有关的任何事情。即使对符合CLS规范的语言(即至少符合CLI规范中所定义的"CLS使用者"规则的语言)也没有定义"最小公分母"的方法 - 尽管如果有人纠正我在这里的错误,我会很感激 - 因此,这将按语言排序。最感兴趣的是自带.NET的语言:C#、VB和F#; 但其他语言,如IronPython、IronRuby、Delphi Prism等也相关。它越是边角案例,就越有趣——例如,在方法重载、可选/默认参数、lambda类型推断和转换运算符之间的微妙交互方面,某些东西有时会非常令人惊讶。
以下是一些示例:
添加新方法重载
种类:源代码级别中断
受影响的语言:C#、VB、F#
更改前的API:
public class Foo
{
public void Bar(IEnumerable x);
}
更改后的API:
public class Foo
{
public void Bar(IEnumerable x);
public void Bar(ICloneable x);
}
更改前可以使用的示例客户端代码,更改后不可用:
new Foo().Bar(new int[0]);
添加新的隐式转换运算符重载
类型:源代码层面的变更。
受影响的编程语言:C#、VB。
未受影响的编程语言:F#。
变更前的API:
public class Foo
{
public static implicit operator int ();
}
更改后的API:
public class Foo
{
public static implicit operator int ();
public static implicit operator float ();
}
变更前有效,变更后无效的示例客户端代码:
void Bar(int x);
void Bar(float x);
Bar(new Foo());
注意:F#没有破坏,因为它没有任何语言级别支持重载运算符,既不是显式的也不是隐式的 - 两者都必须直接调用op_Explicit
和op_Implicit
方法。
添加新实例方法
类型:源代码层面的安静语义变化。
受影响的语言:C#,VB
未受影响的语言:F#
更改前的API:
public class Foo
{
}
更改后的API:
public class Foo
{
public void Bar();
}
存在静默语义更改的示例客户端代码:
public static class FooExtensions
{
public void Bar(this Foo foo);
}
new Foo().Bar();
注意:F#并没有破坏,因为它没有语言级别支持ExtensionMethodAttribute
,并且需要将CLS扩展方法作为静态方法调用。