我想收集尽可能多的关于。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类型推断和转换操作符之间的微妙交互有时会非常令人惊讶。
这里有几个例子:
Kind:源级中断
受影响的语言: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]);
Kind:源级中断。
受影响的语言: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
方法调用。
Kind:源级安静语义更改。
受影响的语言: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扩展方法作为静态方法被调用。