我想尽可能多地收集有关 .NET/CLR 中的 API 版本控制的信息,特别是 API 更改如何破坏或不破坏客户端应用程序。首先,让我们定义一些术语:
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#
public class Foo { public static implicit operator int (); }
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方法调用。
op_Explicit
op_Implicit
Kind:源级安静的语义变化。
public class Foo { }
public class Foo { public void Bar(); }
遭受安静语义更改的示例客户端代码:
public static class FooExtensions { public void Bar(this Foo foo); } new Foo().Bar();
注意:F# 没有损坏,因为它没有语言级别的支持ExtensionMethodAttribute,并且需要将 CLS 扩展方法作为静态方法调用。
ExtensionMethodAttribute
种类:二进制中断
受影响的语言:C#(最有可能是 VB 和 F#,但未经测试)
变更前的 API
public static class Foo { public static void bar(int i); }
变更后的API
public static class Foo { public static bool bar(int i); }
更改前工作的示例客户端代码
Foo.bar(13);