.NET / CLRでのAPIのバージョン管理、特にAPIの変更がクライアントアプリケーションをどのように破壊するか、または破壊しないかについて、できるだけ多くの情報を収集したいと思います。まず、いくつかの用語を定義しましょう。
APIの変更-パブリックメンバーのいずれかを含む、タイプのパブリックに表示される定義の変更。これには、タイプとメンバーの名前の変更、タイプの基本タイプの変更、タイプの実装されたインターフェイスのリストからのインターフェイスの追加/削除、メンバーの追加/削除(オーバーロードを含む)、メンバーの可視性の変更、メソッドとタイプのパラメーターの名前変更、デフォルト値の追加が含まれますメソッドパラメーター、型とメンバーの属性の追加/削除、および型とメンバーの汎用型パラメーターの追加/削除(何か見落としましたか?)。これには、メンバー本体の変更やプライベートメンバーの変更は含まれません(つまり、リフレクションは考慮されません)。
バイナリレベルの中断-APIの変更により、古いバージョンのAPIに対してコンパイルされたクライアントアセンブリが新しいバージョンで読み込まれない可能性があります。例:以前と同じ方法で呼び出すことができる場合でも、メソッドのシグネチャを変更します(つまり、voidでタイプ/パラメーターのデフォルト値のオーバーロードを返します)。
ソースレベルの中断-APIの変更により、古いバージョンのAPIに対してコンパイルするように記述された既存のコードが、新しいバージョンでコンパイルされない可能性があります。ただし、コンパイル済みのクライアントアセンブリは以前と同じように機能します。例:以前は明確であったメソッド呼び出しにあいまいさをもたらす可能性のある新しいオーバーロードを追加します。
ソースレベルの静かなセマンティクスの変更-古いバージョンのAPIに対してコンパイルするように記述された既存のコードが、別のメソッドを呼び出すなどして、そのセマンティクスを静かに変更するAPIの変更。ただし、コードは警告/エラーなしでコンパイルを継続する必要があり、以前にコンパイルされたアセンブリは以前と同じように機能するはずです。例:既存のクラスに新しいインターフェースを実装すると、過負荷の解決中に別の過負荷が選択されます。
最終的な目標は、可能な限り多くの破壊的で静かなセマンティクスAPIの変更をカタログ化し、破壊の正確な影響と、それによって影響を受ける言語と影響を受けない言語を説明することです。後者を拡張するには:一部の変更はすべての言語に普遍的に影響しますが(たとえば、インターフェイスに新しいメンバーを追加すると、任意の言語でのそのインターフェイスの実装が機能しなくなります)、一部の変更では、中断するために非常に特殊な言語セマンティクスが必要になります。これには通常、メソッドのオーバーロードが含まれ、一般に、暗黙的な型変換に関係するものはすべて含まれます。ここでは、CLS準拠の言語(つまり、CLI仕様で定義されている「CLSコンシューマー」のルールに少なくとも準拠している言語)についても、「最小公分母」を定義する方法はないようです。誰かが私をここで間違っていると訂正してくれれば幸いです-ですから、これは言語ごとに行わなければなりません。最も興味深いのは、当然、.NETに付属しているC#、VB、F#です。ただし、IronPython、IronRuby、DelphiPrismなどの他のものも関連しています。コーナーケースが多いほど、興味深いものになります。メンバーの削除などはかなり自明ですが、メソッドのオーバーロード、オプション/デフォルトパラメーター、ラムダ型推論、変換演算子などの間の微妙な相互作用は非常に驚くべきものです。時には。
これをキックスタートするためのいくつかの例:
新しいメソッドのオーバーロードを追加する
種類:ソースレベルのブレーク
影響を受ける言語: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();
ExtensionMethodAttribute
注:F#は、の言語レベルのサポートがなく、静的メソッドとしてCLS拡張メソッドを呼び出す必要があるため、壊れていません。