.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方法。添加新的實例方法種類:源級靜默語義變化。受影響的語言: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擴展方法作為靜態方法調用。
3 回答

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