.NET 中 API 突破性更改的权威指南
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1456785/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
A definitive guide to API-breaking changes in .NET
提问by Pavel Minaev
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:
我想收集尽可能多的关于 .NET/CLR 中 API 版本控制的信息,特别是 API 更改如何破坏或不破坏客户端应用程序。首先,让我们定义一些术语:
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).
API 更改- 类型的公开可见定义的更改,包括其任何公共成员。这包括更改类型和成员名称、更改类型的基类型、从类型的已实现接口列表中添加/删除接口、添加/删除成员(包括重载)、更改成员可见性、重命名方法和类型参数、添加默认值对于方法参数,添加/删除类型和成员的属性,以及添加/删除类型和成员的泛型类型参数(我错过了什么?)。这不包括成员团体的任何变化,或私人成员的任何变化(即我们不考虑 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).
二进制级中断- 一种 API 更改,导致针对旧版本 API 编译的客户端程序集可能不会随新版本一起加载。示例:更改方法签名,即使它允许以与以前相同的方式调用(即:void 返回类型/参数默认值重载)。
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.
源级中断- 一种 API 更改,导致编写的现有代码针对旧版本的 API 进行编译,可能无法使用新版本进行编译。然而,已经编译的客户端程序集像以前一样工作。示例:添加新的重载可能会导致之前明确的方法调用出现歧义。
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.
源级安静语义更改- API 更改导致现有代码编写以针对旧版本的 API 进行编译,例如通过调用不同的方法悄悄更改其语义。然而,代码应该继续编译而不会出现警告/错误,并且以前编译的程序集应该像以前一样工作。示例:在现有类上实现新接口,导致在重载解析期间选择不同的重载。
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.
最终目标是对尽可能多的破坏性和安静语义 API 更改进行分类,并描述破坏的确切影响,以及哪些语言受到和不受影响。扩展后者:虽然一些更改普遍影响所有语言(例如,向接口添加新成员将破坏该接口在任何语言中的实现),但有些更改需要非常特定的语言语义才能发挥作用才能中断。这通常涉及方法重载,并且通常涉及与隐式类型转换有关的任何事情。即使对于符合 CLS 的语言(即那些至少符合 CLI 规范中定义的“CLS 消费者”规则的语言),似乎也没有任何方法可以在这里定义“最小公分母”——尽管我 如果有人在这里纠正我是错误的,我会很感激 - 所以这将不得不逐个语言地进行。最感兴趣的自然是 .NET 自带的那些:C#、VB 和 F#;但其他的,如 IronPython、IronRuby、Delphi Prism 等也与此相关。极端情况越多,它就越有趣——像删除成员这样的事情是不言而喻的,但是方法重载、可选/默认参数、lambda 类型推断和转换运算符之间的微妙交互可能会非常令人惊讶有时。
A few examples to kickstart this:
启动此操作的几个示例:
Adding new method overloads
添加新的方法重载
Kind: source-level break
种类:源级中断
Languages affected: C#, VB, F#
受影响的语言:C#、VB、F#
API before change:
更改前的API:
public class Foo
{
public void Bar(IEnumerable x);
}
API after change:
更改后的API:
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
受影响的语言:C#、VB
Languages not affected: F#
不受影响的语言:F#
API before change:
更改前的API:
public class Foo
{
public static implicit operator int ();
}
API after change:
更改后的API:
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_Explicitand op_Implicitmethods.
注意:F# 没有被破坏,因为它对重载运算符没有任何语言级别的支持,既不显式也不隐式 - 两者都必须直接调用 asop_Explicit和op_Implicitmethods。
Adding new instance methods
添加新的实例方法
Kind: source-level quiet semantics change.
Kind:源级安静语义变化。
Languages affected: C#, VB
受影响的语言:C#、VB
Languages not affected: F#
不受影响的语言:F#
API before change:
更改前的API:
public class Foo
{
}
API after change:
更改后的API:
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.
注意:F# 没有损坏,因为它没有对 的语言级别支持ExtensionMethodAttribute,并且需要将 CLS 扩展方法作为静态方法调用。
采纳答案by Justin Drury
Changing a method signature
更改方法签名
Kind: Binary-level Break
种类:二进制中断
Languages affected: C# (VB and F# most likely, but untested)
受影响的语言:C#(最有可能是 VB 和 F#,但未经测试)
API before change
更改前的 API
public static class Foo
{
public static void bar(int i);
}
API after change
更改后的 API
public static class Foo
{
public static bool bar(int i);
}
Sample client code working before change
更改前工作的示例客户端代码
Foo.bar(13);
回答by Eldritch Conundrum
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.
这是因为 C# 将参数的默认值直接编译到调用程序集中。这意味着如果你不重新编译,你会得到一个 MissingMethodException,因为旧的程序集试图调用一个参数较少的方法。
API Before Change
变更前的API
public void Foo(int a) { }
API After Change
更改后的 API
public void Foo(int a, string b = null) { }
Sample client code that is broken afterwards
之后被破坏的示例客户端代码
Foo(5);
The client code needs to be recompiled into Foo(5, null)at the bytecode level. The called assembly will only contain Foo(int, string), not Foo(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#).
客户端代码需要Foo(5, null)在字节码级别重新编译。被调用的程序集将只包含Foo(int, string),不包含Foo(int)。这是因为默认参数值纯粹是一种语言特性,.Net 运行时对它们一无所知。(这也解释了为什么默认值必须是 C# 中的编译时常量)。
回答by Pavel Minaev
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:
class Foo
{
public virtual void Bar() {}
public virtual void Baz() {}
}
API after change:
更改后的API:
class FooBase
{
public virtual void Bar() {}
}
class Foo : FooBase
{
public virtual void Baz() {}
}
Sample code that keeps working throughout the change (even though I expected it to break):
在整个更改过程中保持工作的示例代码(即使我预计它会中断):
// C++/CLI
ref class Derived : Foo
{
public virtual void Baz() {{
// Explicit override
public virtual void BarOverride() = Foo::Bar {}
};
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 BarOverrideoverrides Foo::Barrather than FooBase::Bar, assembly loader is smart enough to substitute one for another correctly without any complaints - apparently, the fact that Foois a class is what makes the difference. Go figure...
C++/CLI 是唯一具有类似于虚拟基类成员的显式接口实现的构造的 .NET 语言 - “显式覆盖”。我完全期望这会导致与将接口成员移动到基本接口时相同类型的破坏(因为为显式覆盖生成的 IL 与显式实现相同)。令我惊讶的是,事实并非如此——即使生成的 IL 仍然指定BarOverride覆盖Foo::Bar而不是FooBase::Bar,程序集加载器足够聪明,可以正确地替换另一个而没有任何抱怨——显然,Foo是一个类的事实是造成差异的原因。去搞清楚...
回答by Pavel Minaev
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)
受影响的语言:C#、VB、C++/CLI、F#(用于源代码中断;二进制文件自然会影响任何语言)
API before change:
更改前的API:
interface IFoo
{
void Bar();
void Baz();
}
API after change:
更改后的API:
interface IFooBase
{
void Bar();
}
interface IFoo : IFooBase
{
void Baz();
}
Sample client code that is broken by change at source level:
在源代码级别被更改破坏的示例客户端代码:
class Foo : IFoo
{
void IFoo.Bar() { ... }
void IFoo.Baz() { ... }
}
Sample client code that is broken by change at binary level;
在二进制级别被更改破坏的示例客户端代码;
(new Foo()).Bar();
Notes:
笔记:
For source level break, the problem is that C#, VB and C++/CLI all require exactinterface name in the declaration of interface member implementation; thus, if the member gets moved to a base interface, the code will no longer compile.
对于源码级别的破解,问题在于C#、VB和C++/CLI在接口成员实现的声明中都需要准确的接口名称;因此,如果成员被移动到基接口,代码将不再编译。
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.
二进制中断是因为接口方法在生成的 IL 中是完全限定的,用于显式实现,并且接口名称也必须准确。
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.
可用的隐式实现(即 C# 和 C++/CLI,但不是 VB)在源代码和二进制级别都可以正常工作。方法调用也不会中断。
回答by glopes
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 版本重新编译,则会引入静默二进制级别中断。枚举值是编译时常量,因此对它们的任何使用都包含在客户端程序集的 IL 中。这种情况有时特别难以发现。
API Before Change
变更前的API
public enum Foo
{
Bar,
Baz
}
API After Change
更改后的 API
public enum Foo
{
Baz,
Bar
}
Sample client code that works but is broken afterwards:
示例客户端代码有效但之后被破坏:
Foo.Bar < Foo.Baz
回答by Pavel Minaev
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
受影响的语言:C#、VB
Languages not affected: F#, C++/CLI
不受影响的语言:F#、C++/CLI
API before change:
更改前的API:
public class Foo
{
}
API after change:
更改后的API:
public class Foo
{
public void Frob() {}
}
Sample client code that is broken by change:
被更改破坏的示例客户端代码:
class Bar
{
public void Frob() {}
}
class Program
{
static void Qux(Action<Foo> a)
{
}
static void Qux(Action<Bar> a)
{
}
static void Main()
{
Qux(x => x.Frob());
}
}
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.
这里的问题是由 C# 和 VB 中存在重载解析的 lambda 类型推断引起的。通过检查 lambda 的主体是否对给定类型有意义,这里使用了一种有限形式的鸭子类型来打破联系,如果只有一种类型导致可编译的主体,则选择该类型。
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.
这里的危险是客户端代码可能有一个重载的方法组,其中一些方法接受他自己类型的参数,而其他方法接受你的库公开的类型的参数。如果他的任何代码依赖类型推断算法来仅根据成员的存在或不存在来确定正确的方法,那么将新成员添加到您的一种类型中,其名称与客户端类型中的一种相同,可能会引发推理off,导致在重载解析过程中产生歧义。
Note that types Fooand Barin 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.
请注意,在此示例中,类型Foo和Bar不以任何方式相关,不通过继承或其他方式相关。仅在单个方法组中使用它们就足以触发这一点,如果这发生在客户端代码中,您将无法控制它。
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.
上面的示例代码演示了一种更简单的情况,即源级中断(即编译器错误结果)。但是,如果通过推理选择的重载具有其他参数,否则这将导致其排名低于其他参数(例如,具有默认值的可选参数,或声明和实际参数之间的类型不匹配需要隐式转换)。在这种情况下,重载决议将不再失败,但编译器会悄悄地选择不同的重载。然而,在实践中,如果不仔细构造方法签名来故意引起这种情况,就很难遇到这种情况。
回答by LBushkin
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:
public class Foo : IEnumerable
{
public IEnumerator GetEnumerator();
}
API After Change:
变更后的API:
public class Foo : IEnumerable
{
IEnumerator IEnumerable.GetEnumerator();
}
Sample Client code that works before change and is broken afterwards:
在更改前工作并在更改后中断的示例客户端代码:
new Foo().GetEnumerator(); // fails because GetEnumerator() is no longer public
回答by LBushkin
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 方面更为微妙。从表面上看,这应该是相对安全的,但是,当与继承结合使用时,它可能会导致问题。
API Before Change:
变更前的API:
public class Foo : IEnumerable
{
IEnumerator IEnumerable.GetEnumerator() { yield return "Foo"; }
}
API After Change:
变更后的API:
public class Foo : IEnumerable
{
public IEnumerator GetEnumerator() { yield return "Foo"; }
}
Sample Client code that works before change and is broken afterwards:
在更改前工作并在更改后中断的示例客户端代码:
class Bar : Foo, IEnumerable
{
IEnumerator IEnumerable.GetEnumerator() // silently hides base instance
{ yield return "Bar"; }
}
foreach( var x in new Bar() )
Console.WriteLine(x); // originally output "Bar", now outputs "Foo"
回答by Hagelt18
Changing a field to a property
将字段更改为属性
Kind of Break: API
中断类型:API
Languages Affected: Visual Basic and C#*
受影响的语言:Visual Basic 和 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.
信息:当您在 Visual Basic 中将普通字段或变量更改为属性时,任何以任何方式引用该成员的外部代码都需要重新编译。
API Before Change:
变更前的API:
Public Class Foo
Public Shared Bar As String = ""
End Class
API After Change:
变更后的API:
Public Class Foo
Private Shared _Bar As String = ""
Public Shared Property Bar As String
Get
Return _Bar
End Get
Set(value As String)
_Bar = value
End Set
End Property
End Class
Sample client code that works but is broken afterwards :
示例客户端代码有效但之后被破坏:
Foo.Bar = "foobar"
回答by jswolf19
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.
由于命名空间解析在 vb.Net 中的工作方式,将命名空间添加到库会导致使用旧版本 API 编译的 Visual Basic 代码无法使用新版本编译。
Sample client code:
示例客户端代码:
Imports System
Imports Api.SomeNamespace
Public Class Foo
Public Sub Bar()
Dim dr As Data.DataRow
End Sub
End Class
If a new version of the API adds the namespace Api.SomeNamespace.Data, then the above code will not compile.
如果新版本的 API 添加了 namespace Api.SomeNamespace.Data,则上述代码将无法编译。
It becomes more complicated with project-level namespace imports. If Imports Systemis omitted from the above code, but the Systemnamespace is imported at the project level, then the code may still result in an error.
项目级命名空间导入变得更加复杂。如果Imports System在上面的代码中省略了,但是System在项目级别导入了命名空间,那么代码仍然可能会导致错误。
However, if the Api includes a class DataRowin its Api.SomeNamespace.Datanamespace, then the code will compile but drwill be an instance of System.Data.DataRowwhen compiled with the old version of the API and Api.SomeNamespace.Data.DataRowwhen compiled with the new version of the API.
但是,如果 ApiDataRow在其Api.SomeNamespace.Data命名空间中包含一个类,则代码将编译但dr将是System.Data.DataRow使用旧版本 APIApi.SomeNamespace.Data.DataRow编译时和使用新版本 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).
更改参数名称是 vb.net 版本 7(?)(.Net 版本 1?)和 c#.net 版本 4(.Net 版本 4)的重大更改。
API before change:
更改前的API:
namespace SomeNamespace {
public class Foo {
public static void Bar(string x) {
...
}
}
}
API after change:
更改后的API:
namespace SomeNamespace {
public class Foo {
public static void Bar(string y) {
...
}
}
}
Sample client code:
示例客户端代码:
Api.SomeNamespace.Foo.Bar(x:"hi"); //C#
Api.SomeNamespace.Foo.Bar(x:="hi") 'VB
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 的 vb 源无法解析该函数。Visual Basic 没有办法(?)在调用点区分这些方法,除非它们具有不同的参数名称,因此这样的更改可能会导致两个成员在 vb 代码中都无法使用。
API before change:
更改前的API:
namespace SomeNamespace {
public class Foo {
public static void Bar(string x) {
...
}
}
}
API after change:
更改后的API:
namespace SomeNamespace {
public class Foo {
public static void Bar(string x) {
...
}
public static void Bar(ref string x) {
...
}
}
}
Sample client code:
示例客户端代码:
Api.SomeNamespace.Foo.Bar(str)
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:
namespace SomeNamespace {
public class Foo {
public int Bar;
}
}
API after change:
更改后的API:
namespace SomeNamespace {
public class Foo {
public int Bar { get; set; }
}
}
Sample client code:
示例客户端代码:
FooBar(ref Api.SomeNamespace.Foo.Bar);

