.NET 中 API 重大变化的权威指南
我想收集尽可能多的有关 .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
方法调用。
添加新的实例方法
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
ExtensionMethodAttribute< 的语言级别支持/code>,并且要求 CLS 扩展方法作为静态方法调用。
I would like to gather as much information as possible regarding API versioning in .NET/CLR, and specifically how API changes do or do not break client applications. First, let's define some terms:
API change - a change in the publicly visible definition of a type, including any of its public members. This includes changing type and member names, changing base type of a type, adding/removing interfaces from list of implemented interfaces of a type, adding/removing members (including overloads), changing member visibility, renaming method and type parameters, adding default values for method parameters, adding/removing attributes on types and members, and adding/removing generic type parameters on types and members (did I miss anything?). This does not include any changes in member bodies, or any changes to private members (i.e. we do not take into account Reflection).
Binary-level break - an API change that results in client assemblies compiled against older version of the API potentially not loading with the new version. Example: changing method signature, even if it allows to be called in the same way as before (ie: void to return type / parameter default values overloads).
Source-level break - an API change that results in existing code written to compile against older version of the API potentially not compiling with the new version. Already compiled client assemblies work as before, however. Example: adding a new overload that can result in ambiguity in method calls that were unambiguous previous.
Source-level quiet semantics change - an API change that results in existing code written to compile against older version of the API quietly change its semantics, e.g. by calling a different method. The code should however continue to compile with no warnings/errors, and previously compiled assemblies should work as before. Example: implementing a new interface on an existing class that results in a different overload being chosen during overload resolution.
The ultimate goal is to catalogize as many breaking and quiet semantics API changes as possible, and describe exact effect of breakage, and which languages are and are not affected by it. To expand on the latter: while some changes affect all languages universally (e.g. adding a new member to an interface will break implementations of that interface in any language), some require very specific language semantics to enter into play to get a break. This most typically involves method overloading, and, in general, anything having to do with implicit type conversions. There doesn't seem to be any way to define the "least common denominator" here even for CLS-conformant languages (i.e. those conforming at least to rules of "CLS consumer" as defined in CLI spec) - though I'll appreciate if someone corrects me as being wrong here - so this will have to go language by language. Those of most interest are naturally the ones that come with .NET out of the box: C#, VB and F#; but others, such as IronPython, IronRuby, Delphi Prism etc are also relevant. The more of a corner case it is, the more interesting it will be - things like removing members are pretty self-evident, but subtle interactions between e.g. method overloading, optional/default parameters, lambda type inference, and conversion operators can be very surprising at times.
A few examples to kickstart this:
Adding new method overloads
Kind: source-level break
Languages affected: C#, VB, F#
API before change:
public class Foo
{
public void Bar(IEnumerable x);
}
API after change:
public class Foo
{
public void Bar(IEnumerable x);
public void Bar(ICloneable x);
}
Sample client code working before change and broken after it:
new Foo().Bar(new int[0]);
Adding new implicit conversion operator overloads
Kind: source-level break.
Languages affected: C#, VB
Languages not affected: F#
API before change:
public class Foo
{
public static implicit operator int ();
}
API after change:
public class Foo
{
public static implicit operator int ();
public static implicit operator float ();
}
Sample client code working before change and broken after it:
void Bar(int x);
void Bar(float x);
Bar(new Foo());
Notes: F# is not broken, because it does not have any language level support for overloaded operators, neither explicit nor implicit - both have to be called directly as op_Explicit
and op_Implicit
methods.
Adding new instance methods
Kind: source-level quiet semantics change.
Languages affected: C#, VB
Languages not affected: F#
API before change:
public class Foo
{
}
API after change:
public class Foo
{
public void Bar();
}
Sample client code that suffers a quiet semantics change:
public static class FooExtensions
{
public void Bar(this Foo foo);
}
new Foo().Bar();
Notes: F# is not broken, because it does not have language level support for ExtensionMethodAttribute
, and requires CLS extension methods to be called as static methods.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(18)
更改方法签名
种类:二进制级中断
受影响的语言:C#(最有可能是 VB 和 F#,但未经测试)
更改前的 API
更改后的 API
更改前工作的示例客户端代码
Changing a method signature
Kind: Binary-level Break
Languages affected: C# (VB and F# most likely, but untested)
API before change
API after change
Sample client code working before change
添加具有默认值的参数。
中断类型:二进制级中断
即使调用源代码不需要更改,仍然需要重新编译(就像添加常规参数时一样)。
这是因为 C# 将参数的默认值直接编译到调用程序集中。这意味着如果不重新编译,您将得到 MissingMethodException,因为旧程序集尝试调用带有较少参数的方法。
更改前的 API
更改后的 API
之后损坏的示例客户端代码
客户端代码需要重新编译为
Foo(5, null )
在字节码级别。被调用的程序集将仅包含Foo(int, string)
,而不包含Foo(int)
。这是因为默认参数值纯粹是一种语言功能,.Net 运行时对它们一无所知。 (这也解释了为什么默认值必须是 C# 中的编译时常量)。Adding a parameter with a default value.
Kind of Break: Binary-level break
Even if the calling source code doesn't need to change, it still needs to be recompiled (just like when adding a regular parameter).
That is because C# compiles the default values of the parameters directly into the calling assembly. It means that if you don't recompile, you will get a MissingMethodException because the old assembly tries to call a method with less arguments.
API Before Change
API After Change
Sample client code that is broken afterwards
The client code needs to be recompiled into
Foo(5, null)
at the bytecode level. The called assembly will only containFoo(int, string)
, notFoo(int)
. That's because default parameter values are purely a language feature, the .Net runtime does not know anything about them. (This also explain why default values have to be compile-time constants in C#).当我发现它时,这一点非常不明显,尤其是考虑到与接口相同情况的差异。这根本不是休息,但令人惊讶的是我决定将其包括在内:
将类成员重构为基类
种类:不是休息!
受影响的语言: 无(即没有损坏)
更改前的 API:
更改后的 API:
在整个更改过程中保持工作的示例代码(即使我预计它会损坏):
注意:
C++/CLI 是唯一具有构造类似于虚拟基类成员的显式接口实现 - “显式覆盖”。我完全期望这会导致与将接口成员移动到基本接口时相同的破坏(因为为显式覆盖生成的 IL 与显式实现相同)。令我惊讶的是,情况并非如此 - 即使生成的 IL 仍然指定
BarOverride
覆盖Foo::Bar
而不是FooBase::Bar
,程序集加载器足够聪明,可以正确地用一个程序集替换另一个程序集,而不会产生任何抱怨 - 显然,Foo
是一个类这一事实才是造成差异的原因。去算算...This one was very non-obvious when I discovered it, especially in light of the difference with the same situation for interfaces. It's not a break at all, but it's surprising enough that I decided to include it:
Refactoring class members into a base class
Kind: not a break!
Languages affected: none (i.e. none are broken)
API before change:
API after change:
Sample code that keeps working throughout the change (even though I expected it to break):
Notes:
C++/CLI is the only .NET language that has a construct analogous to explicit interface implementation for virtual base class members - "explicit override". I fully expected that to result in the same kind of breakage as when moving interface members to a base interface (since IL generated for explicit override is the same as for explicit implementation). To my surprise, this is not the case - even though generated IL still specifies that
BarOverride
overridesFoo::Bar
rather thanFooBase::Bar
, assembly loader is smart enough to substitute one for another correctly without any complaints - apparently, the fact thatFoo
is a class is what makes the difference. Go figure...这可能是“添加/删除接口成员”的一个不太明显的特殊情况,我认为根据我接下来要发布的另一个案例,它应该有自己的条目。因此:
将接口成员重构为基本接口
种类:在源代码和二进制级别上中断
受影响的语言:C#、VB、C++/CLI、F#(用于源代码中断;二进制语言自然会影响任何语言)
更改前的 API:
更改后的 API:
因源代码级别的更改而损坏的示例客户端代码:因
二进制级别的更改而损坏的示例客户端代码;
注意:
对于源代码级别中断,问题在于 C#、VB 和 C++/CLI 都要求在接口成员实现的声明中提供准确接口名称;因此,如果成员移动到基接口,代码将不再编译。
二进制中断是因为接口方法在生成的 IL 中完全限定为显式实现,并且接口名称也必须准确。
可用的隐式实现(即 C# 和 C++/CLI,但不是 VB)将在源代码和二进制级别上正常工作。方法调用也不会中断。
This one is a perhaps not-so-obvious special case of "adding/removing interface members", and I figured it deserves its own entry in light of another case which I'm going to post next. So:
Refactoring interface members into a base interface
Kind: breaks at both source and binary levels
Languages affected: C#, VB, C++/CLI, F# (for source break; binary one naturally affects any language)
API before change:
API after change:
Sample client code that is broken by change at source level:
Sample client code that is broken by change at binary level;
Notes:
For source level break, the problem is that C#, VB and C++/CLI all require exact interface name in the declaration of interface member implementation; thus, if the member gets moved to a base interface, the code will no longer compile.
Binary break is due to the fact that interface methods are fully qualified in generated IL for explicit implementations, and interface name there must also be exact.
Implicit implementation where available (i.e. C# and C++/CLI, but not VB) will work fine on both source and binary level. Method calls do not break either.
重新排序枚举值
中断类型:源级/二进制级安静语义更改
受影响的语言:所有
重新排序枚举值将保持源级兼容性,因为文字具有相同的名称,但它们的序数索引将是已更新,这可能会导致某些无提示的源代码级别中断。
更糟糕的是,如果客户端代码未针对新的 API 版本重新编译,则可能会引入无提示的二进制级别中断。枚举值是编译时常量,因此它们的任何使用都会被纳入客户端程序集的 IL 中。这种情况有时特别难以发现。
API 更改之前
API 更改之后 可以正常
工作但之后损坏的示例客户端代码:
Reordering enumerated values
Kind of break: Source-level/Binary-level quiet semantics change
Languages affected: all
Reordering enumerated values will keep source-level compatibility as literals have the same name, but their ordinal indices will be updated, which can cause some kinds of silent source-level breaks.
Even worse is the silent binary-level breaks that can be introduced if client code is not recompiled against the new API version. Enum values are compile-time constants and as such any uses of them are baked into the client assembly's IL. This case can be particularly hard to spot at times.
API Before Change
API After Change
Sample client code that works but is broken afterwards:
这在实践中确实是一件非常罕见的事情,但当它发生时仍然令人惊讶。
添加新的非重载成员
种类:源代码级别中断或安静语义更改。
受影响的语言:C#、VB
不受影响的语言:F#、C++/CLI
更改前的 API:
更改后的 API:
因更改而损坏的示例客户端代码:
注意:
此处的问题是由 C# 和 VB 中存在 lambda 类型推断引起的过载决议。这里采用鸭子类型的有限形式来打破多个类型匹配的关系,方法是检查 lambda 的主体对于给定类型是否有意义 - 如果只有一种类型产生可编译的主体,则选择该类型。
这里的危险是客户端代码可能有一个重载的方法组,其中一些方法采用自己类型的参数,而其他方法则采用您的库公开的类型的参数。如果他的任何代码依赖类型推断算法仅根据成员的存在或不存在来确定正确的方法,那么向您的类型之一添加与客户端类型之一同名的新成员可能会引发推断关闭,导致重载决策期间出现歧义。
请注意,此示例中的类型
Foo
和Bar
不以任何方式相关,无论是通过继承还是其他方式。仅在单个方法组中使用它们就足以触发这种情况,如果这种情况发生在客户端代码中,您将无法控制它。上面的示例代码演示了一种更简单的情况,其中这是源代码级别的中断(即编译器错误结果)。然而,如果通过推理选择的重载具有其他参数,这也可能是无声的语义更改,否则会导致其排名靠后(例如,具有默认值的可选参数,或者需要隐式声明和实际参数之间的类型不匹配)转换)。在这种情况下,重载决策将不再失败,但编译器将悄悄选择不同的重载。然而,在实践中,如果不仔细构建方法签名来故意导致这种情况,就很难遇到这种情况。
This one is really a very rare thing in practice, but nonetheless a surprising one when it happens.
Adding new non-overloaded members
Kind: source level break or quiet semantics change.
Languages affected: C#, VB
Languages not affected: F#, C++/CLI
API before change:
API after change:
Sample client code that is broken by change:
Notes:
The problem here is caused by lambda type inference in C# and VB in presence of overload resolution. A limited form of duck typing is employed here to break ties where more than one type matches, by checking whether the body of the lambda makes sense for a given type - if only one type results in compilable body, that one is chosen.
The danger here is that client code may have an overloaded method group where some methods take arguments of his own types, and others take arguments of types exposed by your library. If any of his code then relies on type inference algorithm to determine the correct method based solely on presence or absence of members, then adding a new member to one of your types with the same name as in one of the client's types can potentially throw inference off, resulting in ambiguity during overload resolution.
Note that types
Foo
andBar
in this example are not related in any way, not by inheritance nor otherwise. Mere use of them in a single method group is enough to trigger this, and if this occurs in client code, you have no control over it.The sample code above demonstrates a simpler situation where this is a source-level break (i.e. compiler error results). However, this can also be a silent semantics change, if the overload that was chosen via inference had other arguments which would otherwise cause it to be ranked below (e.g. optional arguments with default values, or type mismatch between declared and actual argument requiring an implicit conversion). In such scenario, the overload resolution will no longer fail, but a different overload will be quietly selected by the compiler. In practice, however, it is very hard to run into this case without carefully constructing method signatures to deliberately cause it.
将隐式接口实现转换为显式接口实现。
中断类型:
受影响的源语言和二进制语言:全部
这实际上只是更改方法可访问性的一种变体 - 它只是更微妙一些,因为很容易忽视这样一个事实:并非所有对接口方法的访问都必须通过引用接口的类型。
更改前的 API:
更改后的 API:
更改前有效但更改后损坏的示例客户端代码:
Convert an implicit interface implementation into an explicit one.
Kind of Break: Source and Binary
Languages Affected: All
This is really just a variation of changing a method's accessibility - its just a little more subtle since it's easy to overlook the fact that not all access to an interface's methods are necessarily through a reference to the type of the interface.
API Before Change:
API After Change:
Sample Client code that works before change and is broken afterwards:
将显式接口实现转换为隐式接口实现。
中断类型:
受影响的源语言:全部
将显式接口实现重构为隐式接口实现在破坏 API 方面更加微妙。从表面上看,这似乎应该是相对安全的,但是,当与继承结合使用时,它可能会引起问题。
更改前的 API:
更改后的 API:
更改前有效但更改后损坏的示例客户端代码:
Convert an explicit interface implementation into an implicit one.
Kind of Break: Source
Languages Affected: All
The refactoring of an explicit interface implementation into an implicit one is more subtle in how it can break an API. On the surface, it would seem that this should be relatively safe, however, when combined with inheritance it can cause problems.
API Before Change:
API After Change:
Sample Client code that works before change and is broken afterwards:
将字段更改为属性
中断类型:
受影响的 API 语言:Visual Basic 和 C#*
信息:当您将普通字段或变量更改为 Visual Basic 中的属性时,以任何方式引用该成员的任何外部代码都需要重新编译。
更改前的 API:
更改后的 API:
可以正常工作但之后损坏的示例客户端代码:
Changing a field to a property
Kind of Break: API
Languages Affected: Visual Basic and C#*
Info: When you change a normal field or variable into a property in visual basic, any outside code referencing that member in any way will need to be recompiled.
API Before Change:
API After Change:
Sample client code that works but is broken afterwards :
命名空间添加
源级中断/源级安静语义更改
由于命名空间解析在 vb.Net 中的工作方式,向库添加命名空间可能会导致使用以前版本的 Visual Basic 代码进行编译API 不使用新版本进行编译。
示例客户端代码:
如果新版本的 API 添加了命名空间
Api.SomeNamespace.Data
,则上述代码将无法编译。项目级命名空间导入变得更加复杂。如果上面的代码中省略了
Imports System
,但在项目级别导入了System
命名空间,那么该代码仍然可能会导致错误。但是,如果 Api 在其
Api.SomeNamespace.Data
命名空间中包含类DataRow
,则代码将编译,但dr
将是一个实例使用旧版本 API 编译时的System.Data.DataRow
和使用新版本 API 编译时的Api.SomeNamespace.Data.DataRow
。参数重命名
源级中断
更改参数名称是 vb.net 从版本 7(?)(.Net 版本 1?)和 c#.net 从版本 4(.Net 版本)的重大更改4).
更改前的 API:
更改后的 API:
示例客户端代码:
Ref 参数
源级中断
添加具有相同签名的方法重写(除了一个参数通过引用而不是通过值传递)将导致 vb 源引用API无法解析该函数。 Visual Basic 无法(?)在调用点区分这些方法,除非它们具有不同的参数名称,因此这样的更改可能会导致这两个成员在 vb 代码中无法使用。
更改前的 API:
更改后的 API:
示例客户端代码:
字段到属性更改
二进制级别中断/源代码级别中断
除了明显的二进制级别中断之外,如果以下情况,这可能会导致源代码级别中断:成员通过引用传递给方法。
更改前的 API:
更改后的 API:
示例客户端代码:
Namespace Addition
Source-level break / Source-level quiet semantics change
Due to the way namespace resolution works in vb.Net, adding a namespace to a library can cause Visual Basic code that compiled with a previous version of the API to not compile with a new version.
Sample client code:
If a new version of the API adds the namespace
Api.SomeNamespace.Data
, then the above code will not compile.It becomes more complicated with project-level namespace imports. If
Imports System
is omitted from the above code, but theSystem
namespace is imported at the project level, then the code may still result in an error.However, if the Api includes a class
DataRow
in itsApi.SomeNamespace.Data
namespace, then the code will compile butdr
will be an instance ofSystem.Data.DataRow
when compiled with the old version of the API andApi.SomeNamespace.Data.DataRow
when compiled with the new version of the API.Argument Renaming
Source-level break
Changing the names of arguments is a breaking change in vb.net from version 7(?) (.Net version 1?) and c#.net from version 4 (.Net version 4).
API before change:
API after change:
Sample client code:
Ref Parameters
Source-level break
Adding a method override with the same signature except that one parameter is passed by reference instead of by value will cause vb source that references the API to be unable to resolve the function. Visual Basic has no way(?) to differentiate these methods at the call point unless they have different argument names, so such a change could cause both members to be unusable from vb code.
API before change:
API after change:
Sample client code:
Field to Property Change
Binary-level break/Source-level break
Besides the obvious binary-level break, this can cause a source-level break if the member is passed to a method by reference.
API before change:
API after change:
Sample client code:
API 更改:
二进制级别中断:
添加一个新成员(受事件保护),该成员使用另一个程序集 (Class2) 中的类型作为模板参数约束。
当子类 (Class3) 用作此类的模板参数时,将其更改为从另一个程序集中的类型派生。
源级安静语义更改:
(不确定这些内容适合什么位置)
部署更改:
引导/配置更改:
更新:
抱歉,我没有意识到这对我来说是破坏的唯一原因是我在模板约束中使用了它们。
API change:
Binary-level break:
Adding a new member (event protected) that uses a type from another assembly (Class2) as a template argument constraint.
Changing a child class (Class3) to derive from a type in another assembly when the class is used as a template argument for this class.
Source-level quiet semantics change:
(not sure where these fit)
Deployment changes:
Bootstrap/Configuration changes:
Update:
Sorry, I didn't realize that the only reason this was breaking for me was that I used them in template constraints.
添加重载方法以消除默认参数使用
中断类型:源级安静语义更改
由于编译器将缺少默认参数值的方法调用转换为调用方具有默认值的显式调用,因此兼容性给出了现有编译代码;将为所有先前编译的代码找到具有正确签名的方法。
另一方面,不使用可选参数的调用现在被编译为对缺少可选参数的新方法的调用。一切仍然工作正常,但如果被调用的代码驻留在另一个程序集中,则调用它的新编译的代码现在依赖于该程序集的新版本。部署调用重构代码的程序集而不同时部署重构代码所在的程序集会导致“找不到方法”异常。
更改前的 API
更改后的 API
仍可正常工作的示例代码
编译时现在依赖于新版本的示例代码< /强>
Adding overload methods to demise default parameters usage
Kind of break: Source-level quiet semantics change
Because the compiler transforms method calls with missing default parameter values to an explicit call with the default value on the calling side, compatibility for existing compiled code is given; a method with the correct signature will be found for all previously compiled code.
On the other side, calls without usage of optional parameters are now compiled as a call to the new method that is missing the optional parameter. It all is still working fine, but if the called code resides in another assembly, newly compiled code calling it is now dependent to the new version of this assembly. Deploying assemblies calling the refactored code without also deploying the assembly the refactored code resides in is resulting in "method not found" exceptions.
API before change
API after change
Sample code that will still be working
Sample code that is now dependent to the new version when compiling
重命名接口
有点破坏:源代码和二进制
受影响的语言:很可能全部,在 C# 中进行了测试。
更改前的 API:
更改后的 API:
可以正常工作但之后损坏的示例客户端代码:
Renaming an interface
Kinda of Break: Source and Binary
Languages Affected: Most likely all, tested in C#.
API Before Change:
API After Change:
Sample client code that works but is broken afterwards:
升级到扩展方法
种类:源代码级中断
受影响的语言:C# v6 及更高版本(也许是其他语言?)
更改前的 API:
更改后的 API:
更改前工作和更改后损坏的示例客户端代码:
更多信息:https://github.com/dotnet/csharplang/issues/665
Promotion to an Extension Method
Kind: source-level break
Languages affected: C# v6 and higher (maybe others?)
API before change:
API after change:
Sample client code working before change and broken after it:
More Info: https://github.com/dotnet/csharplang/issues/665
具有可为 null 类型的参数的重载方法
种类:源代码级别中断
受影响的语言:C#、VB
更改前的 API:
更改后的 API:
在更改之前工作的示例客户端代码更改并在其后中断:
异常:以下方法或属性之间的调用不明确。
Overloading method with a parameter of nullable type
Kind: Source-level break
Languages affected: C#, VB
API before a change:
API after the change:
Sample client code working before the change and broken after it:
Exception: The call is ambiguous between the following methods or properties.
Visual Studio 扩展 NDepend 在 API 重大更改类别中提供了多个规则来检测二进制级别中断。仅当定义了 NDepend 基线 时,才会执行这些规则。
另外还建议使用 3 个代码查询来让用户浏览新的公共 API 元素:
The Visual Studio Extension NDepend provides several rules in the category API Breaking Changes to detect binary level break. These rules are executed only if the NDepend baseline is defined.
Also 3 code queries are proposed to let the user browse new public API elements:
静态只读转换为 const
类型:二进制级中断
受影响的语言:C#、VB 和 F#
更改前的 API:
更改后的 API:
所有客户端都需要重新编译以适应新的更改,否则会出现
MissingFieldException
被抛出。Static readonly conversion to const
Kind: Binary-level Break
Languages affected: C#, VB, and F#
API before change:
API after change:
All clients need to be recompiled to target the new change, otherwise a
MissingFieldException
is thrown..NET 的文档有一个关于此主题的精彩页面,更改兼容性规则。
我相信它涵盖了几乎所有二进制、源代码甚至行为破坏性更改。
.NET's documentation has a great page on this topic, Change rules for compatibility.
I believe it covers pretty much all binary, source and even behavioral breaking changes.