1. ホーム
  2. .net

[解決済み】.NETのAPIブレークスルーのための決定版ガイド

2022-04-04 22:07:14

質問

.NET/CLRのAPIバージョン管理について、特にAPIの変更がクライアントアプリケーションをどのように破壊するか、しないかについて、できるだけ多くの情報を集めたいと考えています。まず、いくつかの用語を定義しましょう。

API変更 - パブリックメンバを含む、ある型の公に見える定義における変更。これには、型名やメンバー名の変更、型の基本型の変更、型の実装インターフェース一覧からのインターフェースの追加/削除、メンバー(オーバーロードを含む)の追加/削除、メンバーの可視性の変更、メソッドや型パラメータの名前の変更、メソッドパラメータのデフォルト値の追加、型やメンバーに対する属性の追加/削除、型やメンバーに対する汎用型パラメータの追加/削除(何か見逃しましたか)が含まれます。これには、メンバー本体の変更や、プライベートメンバーの変更は含まれません(つまり、Reflectionは考慮していません)。

バイナリレベルのブレーク - API の変更により、古いバージョンの API に対してコンパイルされたクライアントアセンブリが、新しいバージョンでロードされない可能性があること。例:メソッドのシグネチャを変更し、以前と同じ方法で呼び出せるようにした場合(例:voidから戻り値の型/パラメータのデフォルト値をオーバーロードする)。

ソースレベルのブレーク - API の変更により、古いバージョンの API に対してコンパイルするように書かれた既存のコードが、新しいバージョンでコンパイルできない可能性があります。ただし、既にコンパイル済みのクライアントアセンブリは従来通り動作します。例:新しいオーバーロードを追加した場合、以前は明確だったメソッド呼び出しが曖昧になる可能性があります。

ソースレベルのクワイエットセマンティクス変更 - API の変更により、古いバージョンの API に対してコンパイルするために書かれた既存のコードが、例えば異なるメソッドを呼び出すことによって、静かにそのセマンティクスを変更することです。しかし、そのコードは警告やエラーなしにコンパイルを続け、以前にコンパイルされたアセンブリは以前と同様に動作するはずです。例: 既存のクラスに新しいインターフェイスを実装すると、オーバーロードの解決時に異なるオーバーロードが選択されるようになります。

最終的な目標は、できるだけ多くのブレーキングとクワイエットセマンティックスのAPI変更をカタログ化し、ブレーキングの正確な効果と、どの言語がその影響を受けるか受けないかを記述することである。後者について説明すると、ある種の変更はすべての言語に普遍的に影響するが(例えば、インターフェースに新しいメンバーを追加すると、どの言語でもそのインターフェースの実装が壊れる)、あるものは壊れるために非常に特定の言語のセマンティクスが入り込むことを必要とする。これは最も典型的な例で、メソッドのオーバーローディングや、一般的には暗黙の型変換に関係するものです。CLS準拠の言語(CLI仕様で定義されたquot;CLS consumerのルールに少なくとも適合している言語)であっても、quot;最小公倍数(least common denominator)を定義する方法はないようです(ここが間違っていると訂正されたらありがたいですが)ので、これは言語ごとに行うしかないでしょう。最も興味があるのは、当然ながら、.NETに最初から付属しているものです。しかし、IronPython、IronRuby、Delphi Prismなど他の言語も関連性があります。また、メソッドのオーバーロード、オプション/デフォルト・パラメータ、ラムダ型推論、変換演算子などの微妙な相互作用は、時に非常に驚くべきものになります。

まず、いくつかの例を挙げてみましょう。

新しいメソッドのオーバーロードを追加する

種類:ソースレベルブレーク

影響を受ける言語 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_Explicitop_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 拡張メソッドは静的メソッドとして呼び出される必要があります。

どのように解決するのですか?

メソッドのシグネチャを変更する

種類 バイナリレベルのブレーク

影響を受ける言語 C# (VBとF#の可能性が高いが未検証)

変更前のAPI

public static class Foo
{
    public static void bar(int i);
}

変更後のAPI

public static class Foo
{
    public static bool bar(int i);
}

変更前に動作していたクライアントコードのサンプル

Foo.bar(13);