.NET中API破壞性更改的權(quán)威指南我想盡可能多地收集有關(guān).NET / CLR中API版本控制的信息,特別是API更改如何破壞客戶端應(yīng)用程序。首先,讓我們定義一些術(shù)語:API更改 - 類型的公開可見定義的更改,包括其任何公共成員。這包括更改類型和成員名稱,更改類型的基本類型,從類型的已實(shí)現(xiàn)接口列表添加/刪除接口,添加/刪除成員(包括重載),更改成員可見性,重命名方法和類型參數(shù),添加默認(rèn)值對(duì)于方法參數(shù),在類型和成員上添加/刪除屬性,以及在類型和成員上添加/刪除泛型類型參數(shù)(我錯(cuò)過了什么嗎?)。這不包括成員團(tuán)體的任何變化,或私人成員的任何變化(即我們不考慮反射)。二進(jìn)制級(jí)別中斷 - 一種API更改,導(dǎo)致針對(duì)舊版本API編譯的客戶端程序集可能無法加載新版本。示例:更改方法簽名,即使它允許以與之前相同的方式調(diào)用(即:void返回類型/參數(shù)默認(rèn)值重載)。源級(jí)別中斷 - 一種API更改,導(dǎo)致編寫現(xiàn)有代碼以針對(duì)舊版本的API進(jìn)行編譯,可能無法使用新版本進(jìn)行編譯。然而,已經(jīng)編譯的客戶端程序集像以前一樣工作。示例:添加一個(gè)新的重載,這可能導(dǎo)致前一個(gè)明確的方法調(diào)用不明確。源級(jí)安靜語義更改 - 一種API更改導(dǎo)致編寫的現(xiàn)有代碼針對(duì)舊版API進(jìn)行編譯,從而悄然改變其語義,例如通過調(diào)用不同的方法。但是,代碼應(yīng)該繼續(xù)編譯而沒有警告/錯(cuò)誤,以前編譯的程序集應(yīng)該像以前一樣工作。示例:在現(xiàn)有類上實(shí)現(xiàn)新接口,導(dǎo)致在重載解析期間選擇不同的重載。最終目標(biāo)是盡可能地對(duì)盡可能多的破壞和靜默語義API更改進(jìn)行編目,并描述破壞的確切影響,以及哪些語言不受其影響。擴(kuò)展后者:雖然一些變化普遍影響所有語言(例如,向接口添加新成員將破壞任何語言中該接口的實(shí)現(xiàn)),但有些需要非常特定的語言語義才能進(jìn)入游戲以獲得休息。這通常涉及方法重載,并且通常涉及與隱式類型轉(zhuǎn)換有關(guān)的任何事情。似乎沒有任何方法可以在這里定義“最小公分母”,即使對(duì)于符合CLS的語言(即那些至少符合CLI規(guī)范中定義的“CLS使用者”規(guī)則的語言) - 盡管我 如果有人在這里糾正我是錯(cuò)的,我會(huì)很感激 - 所以這必須按語言去語言。那些最感興趣的東西自然就是開箱即用的.NET:C#,VB和F#; 但其他人,如IronPython,IronRuby,Delphi Prism等也是相關(guān)的。它的角落越多,它就越有趣 - 刪除成員之類的東西是不言而喻的,但是例如方法重載,可選/默認(rèn)參數(shù),lambda類型推斷和轉(zhuǎn)換運(yùn)算符之間的微妙交互可能會(huì)非常令人驚訝有時(shí)。舉幾個(gè)例子來啟動(dòng)這個(gè):添加新方法重載種類:源級(jí)休息受影響的語言:C#,VB,F(xiàn)#更改前的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]);添加新的隱式轉(zhuǎn)換運(yùn)算符重載種類:源級(jí)休息。受影響的語言: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#沒有被破壞,因?yàn)樗鼪]有任何語言級(jí)別的支持重載運(yùn)算符,既不顯式也不隱式 - 都必須直接調(diào)用op_Explicit和op_Implicit方法。添加新的實(shí)例方法種類:源級(jí)靜默語義變化。受影響的語言: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#沒有被破壞,因?yàn)樗鼪]有語言級(jí)支持ExtensionMethodAttribute,并且需要將CLS擴(kuò)展方法作為靜態(tài)方法調(diào)用。
3 回答

慕桂英546537
TA貢獻(xiàn)1848條經(jīng)驗(yàn) 獲得超10個(gè)贊
當(dāng)我發(fā)現(xiàn)它時(shí),這個(gè)非常不明顯,特別是考慮到界面相同情況的不同。這根本不是休息,但令人驚訝的是我決定將它包括在內(nèi):
將類成員重構(gòu)為基類
善良:不休息!
受影響的語言:無(即沒有破壞)
更改前的API:
class Foo{ public virtual void Bar() {} public virtual void Baz() {}}
更改后的API:
class FooBase{ public virtual void Bar() {}}class Foo : FooBase{ public virtual void Baz() {}}
在整個(gè)更改過程中保持工作的示例代碼(即使我預(yù)計(jì)它會(huì)中斷):
// C++/CLIref class Derived : Foo{ public virtual void Baz() {{ // Explicit override public virtual void BarOverride() = Foo::Bar {}};
筆記:
C ++ / CLI是唯一具有類似于虛擬基類成員的顯式接口實(shí)現(xiàn)的構(gòu)造的.NET語言 - “顯式覆蓋”。我完全期望導(dǎo)致與將接口成員移動(dòng)到基接口時(shí)相同的破壞(因?yàn)闉轱@式覆蓋生成的IL與顯式實(shí)現(xiàn)相同)。令我驚訝的是,事實(shí)并非如此 - 即使生成的IL仍然指定BarOverride
覆蓋Foo::Bar
而不是FooBase::Bar
,匯編加載器足夠智能,可以正確地替換另一個(gè)而沒有任何抱怨 - 顯然,這Foo
是一個(gè)類的事實(shí)是產(chǎn)生差異的原因。去搞清楚...
- 3 回答
- 0 關(guān)注
- 687 瀏覽
添加回答
舉報(bào)
0/150
提交
取消