我想收集有关.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#
更改前的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扩展方法称为静态方法。