その歴史を通じて、.NETはバージョンからバージョン、および.NETの実装全体にわたって高いレベルの互換性を維持しようとしました。 .NET 5 (および .NET Core) 以降のバージョンは、.NET Framework と比較して新しいテクノロジと見なすことができますが、.NET Framework からのこの.NETの実装の能力が制限される主な要因は 2 つあります。
- 多数の開発者が、当初は .NET Framework アプリケーションを開発または開発し続けます。 .NET実装全体で一貫した動作が期待されます。
- .NET標準ライブラリ プロジェクトを使用すると、開発者は、.NET Framework および .NET 5 (および .NET Core) 以降のバージョンで共有される一般的な API を対象とするライブラリを作成できます。 開発者は、.NET アプリケーションで使用されるライブラリは、.NET Framework アプリケーションで使用されるのと同じライブラリと同じように動作することを期待しています。
開発者は、.NET実装間の互換性と共に、.NETの特定の実装のバージョン間で高レベルの互換性を期待します。 特に、.NET Core の以前のバージョン用に記述されたコードは、.NET 5 以降のバージョンでシームレスに実行する必要があります。 実際、多くの開発者は、新しくリリースされたバージョンの.NETで見つかった新しい API も、それらの API が導入されたプレリリース バージョンと互換性があることを期待しています。
この記事では、互換性に影響する変更と、.NET チームが各種類の変更を評価する方法について説明します。 .NET チームが破壊的変更の可能性にどのようにアプローチするかを理解することは、既存の.NET API の動作を変更する pull request を開く開発者にとって特に役立ちます。
次のセクションでは、.NET API に加えられた変更のカテゴリと、アプリケーションの互換性への影響について説明します。 変更は許可されている(✔️)、許可されていない(❌)、または前の動作がどの程度予測可能で明白で一貫していたかどうかの判断と評価が必要です(❓)。
注
- ライブラリ開発者は、.NET ライブラリの変更を評価する方法のガイドとして機能するだけでなく、これらの基準を使用して、複数の.NETの実装とバージョンを対象とするライブラリへの変更を評価することもできます。
- 互換性カテゴリ (前方互換性や下位互換性など) については、「 コードの変更が互換性に与える影響」を参照してください。
パブリック コントラクトの変更
このカテゴリの変更により、タイプのパブリック サーフェス領域が変更されます。 このカテゴリの変更のほとんどは 、下位互換性 に違反するため禁止されています (以前のバージョンの API で開発されたアプリケーションは、新しいバージョンで再コンパイルせずに実行できます)。
種類
✔️ 許可: インターフェイスが既に基本型によって実装されている場合に、型からインターフェイス実装を削除する
❓ 判断が必要: 型に新しいインターフェイス実装を追加する
これは、既存のクライアントに悪影響を与えないので、許容される変更です。 新しい実装を受け入れ可能な状態に保つには、型に対する変更が、ここで定義されている許容可能な変更の境界内で機能する必要があります。 デザイナーまたはシリアライザーが下位レベルでは使用できないコードやデータを生成する機能に直接影響するインターフェイスを追加する場合は、細心の注意が必要です。 たとえば、 ISerializable インターフェイスです。
❓ 判断が必要:新しい基底クラスの導入
新しい 抽象 メンバーを導入したり、既存の型のセマンティクスや動作を変更したりしない場合は、2 つの既存の型間の階層に型を導入できます。 たとえば、.NET Framework 2.0 では、DbConnection クラスは SqlConnection の新しい基底クラスとなり、以前は Component から直接派生していました。
✔️ 許可: あるアセンブリから別のアセンブリに型を移動する
古いアセンブリは、新しいアセンブリを指すTypeForwardedToAttributeでマークする必要があります。
✔️ 許可: 構造体型を
readonly struct型に変更するreadonly struct型をstruct型に変更することはできません。✔️ 許可: アクセス可能な (パブリックまたは保護された) コンストラクターがない場合に、sealed キーワードまたは abstract キーワードを型に追加する
✔️ 型の可視性を拡張することを許可する
❌ 未許可: 型の名前空間または名前を変更する
❌ 許可されていません: 「public type」の名前変更または削除
これにより、名前が変更または削除された型を使用するすべてのコードが中断されます。
注
まれに、.NETがパブリック API を削除することがあります。 詳細については、.NET での
API の削除を参照してください。 .NETのサポート ポリシーの詳細については、「.NET サポート ポリシー」を参照してください。 ❌ 未許可: 列挙型の基になる型を変更する
これは、コンパイル時と動作の破壊的変更だけでなく、属性引数を解析不可能にする可能性があるバイナリ破壊的変更です。
❌ 未許可: 以前はアンシールドだった型をシールする
❌ 許可されていません: インターフェイスの基本型のセットにインターフェイスを追加する
インターフェイスが以前に実装していなかったインターフェイスを実装している場合、元のバージョンのインターフェイスを実装したすべての型が壊れます。
❓ 判断が必要: 実装されている一連のインターフェイスから基底クラスまたはインターフェイスのセットからクラスを削除する
インターフェイスの削除規則には例外が 1 つあります。削除されたインターフェイスから派生するインターフェイスの実装を追加できます。 たとえば、型またはインターフェイスがIDisposableを実装するIComponentを実装している場合は、IDisposableを削除できます。
❌ 未許可:
readonly struct型を struct 型に変更するただし、
struct型をreadonly struct型に変更することはできます。❌ 禁止: 構造体 型を
ref struct型に変更すること、およびその逆も許されない❌ 未許可: 型の可視性を下げる
ただし、型の可視性を高めることは許可されます。
メンバー
✔️ 許可: 仮想ではないメンバーの可視性を拡張する
✔️ 許可: アクセス可能な (パブリックまたは保護された) コンストラクターがないパブリック型に抽象メンバーを追加する、または型がシールされている
ただし、アクセス可能な (パブリックまたは保護された) コンストラクターがあり、
sealedされていない型に抽象メンバーを追加することはできません。✔️ 許可: 型にアクセス可能な (パブリックまたは保護された) コンストラクターがない場合、または型がシールされている場合に、保護されたメンバーの可視性を制限する
✔️ 許可: メンバーを削除された型よりも階層内の上位クラスに移動する
✔️ 許可: オーバーライドの追加または削除
オーバーライドを導入すると、以前のコンシューマーが ベースを呼び出すときにオーバーライドをスキップする可能性があります。
✔️ ALLOWED: 以前にクラスにコンストラクターがない場合は、パラメーターなしのコンストラクターと共に、クラスにコンストラクターを追加する
ただし、パラメーターなしのコンストラクターを追加 せずに 、以前にコンストラクターが存在しなかったクラスにコンストラクターを追加することは許可されません。
✔️ 許可:
ref readonlyからref戻り値への変更 (仮想メソッドまたはインターフェイスを除く)✔️ 許可:フィールドの静的な型が可変値型ではない場合に、フィールドから readonly を削除する
✔️ 許可: 以前に定義されていない新しいイベントを呼び出す
❓ 判断が必要: 新しいインスタンス フィールドを型に追加する
この変更はシリアル化に影響します。
❌ 未許可: パブリック メンバーまたはパラメーターの名前変更または削除
これにより、名前が変更または削除されたメンバーまたはパラメーターを使用するすべてのコードが中断されます。
これには、プロパティからのゲッターまたはセッターの削除または名前変更、および列挙メンバーの名前変更または削除が含まれます。
❓ 判断が必要: インターフェイスにメンバーを追加する
最小.NET バージョンを .NET Core 3.0 (C# 8.0) に引き上げるという意味での破壊的変更ですが、これは、既定のインターフェイス メンバー (DIM) が導入されたときであり、静的で非抽象の非仮想メンバーをインターフェイスに追加することが許可されています。
実装を指定した場合、既存のインターフェイスに新しいメンバーを追加しても、ダウンストリーム アセンブリでコンパイルエラーが発生するとは限りません。 ただし、すべての言語で DIM がサポートされているわけではありません。 また、一部のシナリオでは、ランタイムは呼び出す既定のインターフェイス メンバーを決定できません。 C# 13 以降では、
ref struct型はインターフェイスを実装できますが、ボックス化したり、インターフェイス型に変換したりすることはできません。 そのため、ref struct型は、すべてのインスタンス インターフェイス メンバーに明示的な実装を提供する必要があります。インターフェイスによって提供される既定の実装を使用することはできません。ref structが実装するインターフェイスに既定のインスタンス メンバーを追加するには、対応する実装を追加するref structが必要です。これはソースの破壊的変更です。 このような理由から、既存のインターフェイスにメンバーを追加するときに判断を使用します。注
インターフェイスが
ref struct型によって実装されている場合 (C# 13 以降で可能)、既定のインスタンス メンバーをインターフェイスに追加することは、これらの呼び出し元のソース破壊的変更です。ref structは、新しいメンバーの明示的な実装を提供する必要があります。既定の実装にフォールバックすることはできません。❌ 禁止: パブリック定数または列挙メンバーの値の変更
❌ DISALLOWED: プロパティ、フィールド、パラメーター、または戻り値の型を変更する
❌ 許可されていません: パラメーターの追加、削除、または順序の変更
✔️ 許可:
refパラメーターをref readonlyに変更するパラメーターを
refからref readonlyに変更することは、ref修飾子を使用して引数を渡す既存の呼び出しサイトではソース互換であり、これらの呼び出しは変更されずにコンパイルを続けます。refをinに変更するのとは異なり、ref readonlyパラメーターでは、呼び出し元が右辺値 (変数以外) を渡すことを自動的に許可しません。引数が変数でない場合、コンパイラは警告を発行します。 既存のref通話サイトは有効なままです。❌ 禁止されている:
inパラメーターをref readonlyに変更することパラメーターが
inに変更されると、in修飾子 (コンパイラがinパラメーターを許可する) を指定せずにref readonly引数を渡す呼び出しサイトは警告を受け取ります。ref readonlyは、引数を参照渡しする必要があるためです。 警告をエラーとして扱う呼び出し元では、ソースコードに破壊的な変更が発生します。❌ DISALLOWED: パラメーターの名前変更 (大文字と小文字の変更を含む)
これは、次の 2 つの理由で中断と見なされます。
❌ 禁止:
refの戻り値からref readonlyの戻り値への変更❌️ 許可されていない: 仮想メソッドまたはインターフェイスの戻り値を
ref readonlyからrefに変更すること❌ 未許可: メンバーから abstract を追加または削除する
❌ 禁止: メンバーから 仮想 キーワードを削除すること
❌ 未許可: メンバーに virtual キーワードを追加する
C# コンパイラが非仮想メソッドを呼び出すために callvirt Intermediate Language (IL) 命令を出力する傾向があるので、これは多くの場合、重大な変更ではありません (
callvirtは null チェックを実行しますが、通常の呼び出しでは実行されません)。ただし、いくつかの理由からこの動作は不変ではありません。- C# は、.NET がターゲットとする唯一の言語ではありません。
- C# コンパイラは、ターゲット メソッドが非仮想であり、おそらく null でない場合 (
callvirtを介してアクセスされるメソッドなど) に対して、通常の呼び出しに対するの最適化をますます試みます。
メソッドを仮想にすると、多くの場合、コンシューマー コードは非仮想的に呼び出されます。
❌ 未許可: virtual メンバーを abstract にする
仮想メンバーは、派生クラスによってオーバーライドできるメソッド実装を提供します。 抽象メンバーには実装がないのでオーバーライドする "必要があります"。
❌ 未許可: sealed キーワードをインターフェイス メンバーに追加する
既定のインターフェイス メンバーに
sealedを追加すると、そのメンバーの派生型の実装が呼び出されないように、非仮想になります。❌ DISALLOWED: アクセス可能な (パブリックまたはプロテクト) コンストラクターを持ち、シールドされていないパブリック型に抽象メンバーを追加することは許可されていません
❌ 許可されていません: メンバーに対する 静的 キーワードを追加したり削除したりすること
❌ DISALLOWED: 既存のオーバーロードを排除し、別の動作を定義するオーバーロードを追加する
これにより、前のオーバーロードにバインドされた既存のクライアントが中断されます。 たとえば、クラスに、 UInt32を受け入れるメソッドの単一バージョンがある場合、既存のコンシューマーは、 Int32 値を渡すときに、そのオーバーロードに正常にバインドされます。 ただし、 Int32を受け入れるオーバーロードを追加すると、再コンパイル時または遅延バインディングの使用時に、コンパイラが新しいオーバーロードにバインドされるようになりました。 動作が異なる場合は、重大な変更になる可能性があります。
❓ 判断が必要: 既存のオーバーロードにOverloadResolutionPriorityAttributeを追加するか、優先度値を変更する
OverloadResolutionPriorityAttributeはソースレベルでのオーバーロード解決に影響を与えます。再コンパイルを行う呼び出し元は、以前のオーバーロードとは異なる解決に至る可能性があります。 目的の用途は、コンパイラが既存のオーバーロードよりもそれを優先するように、新しいより優れたオーバーロードに属性を追加することです。 それを既存のオーバーロードに追加したり、既に属性付きオーバーロードの優先度値を変更したりすると、再コンパイルする呼び出し元の動作が変わる可能性があるため、ソースの破壊的変更になる可能性があります。
✔️ 許可: ジェネリック型パラメーターに
allows ref structの反制約を追加するallows ref structを追加すると、ref struct型を許可することで、型引数として使用できる型が拡張されます。ref struct以外の型引数を使用する既存の呼び出し元は影響を受けません。 ジェネリック メソッドまたは型は、その型パラメーターのすべてのインスタンスの ref 安全規則に従う必要があります。❌ DISALLOWED: ジェネリック型パラメーターから
allows ref structの反制約を削除するallows ref structを削除すると、呼び出し元が型引数として使用できる型が制限されます。ref structを型引数として渡す呼び出し元はコンパイルされなくなります。❌ DISALLOWED: パラメーターなしのコンストラクターを追加せずに、以前にコンストラクターが存在しなかったクラスにコンストラクターを追加する
❌️ 未許可: フィールドに readonly を追加する
❌ 許可されていません: メンバーの可視性を低下させる
これには、アクセス可能な (または
public) コンストラクターが存在し、型がprotected場合に、保護されたメンバーの可視性を低下させることが含まれます。 そうでない場合は、保護されたメンバーの可視性を下げることが許可されます。メンバーの可視性を高めることは許可されます。
❌ 許可されていません: メンバーの型を変更する
メソッドの戻り値、またはプロパティまたはフィールドの型は変更できません。 たとえば、 Object を返すメソッドのシグネチャは、 Stringを返すように変更することはできません。その逆も同様です。
❌ DISALLOWED: 非パブリック フィールドを持たない構造体にインスタンス フィールドを追加する
構造体にパブリック フィールドしかない場合、またはフィールドがまったくない場合、呼び出し元は、最初に使用する前にすべてのパブリック フィールドが構造体に設定されている限り、構造体のコンストラクターを呼び出したり、ローカルを
default(T)に初期化したりせずに、その構造体型のローカルを宣言できます。 このような構造体に新しいフィールド (パブリックまたは非パブリック) を追加することは、これらの呼び出し元のソース破壊的変更です。コンパイラでは、追加のフィールドを初期化する必要が出るようになったためです。さらに、新しいフィールド (パブリックまたは非パブリック) をフィールドのない構造体に追加するか、パブリック フィールドのみを持つ構造体に追加することは、コードに
[SkipLocalsInit]を適用した呼び出し元に対するバイナリ破壊的変更です。 コンパイラはコンパイル時にこれらのフィールドを認識していなかったため、構造体を完全に初期化しない IL を出力し、初期化されていないスタック データから構造体が作成される可能性があります。構造体にパブリックでないフィールドがある場合、コンパイラは既にコンストラクターまたは
default(T)を使用して初期化を強制しており、新しいインスタンス フィールドの追加は破壊的変更ではありません。❌ 未許可: 以前に発生したことのない既存のイベントを発生させる
動作の変更
アセンブリ
✔️ 許可: 同じプラットフォームがまだサポートされている場合にアセンブリを移植可能にする
❌ 許可されていません: アセンブリの名前を変更する
❌ 許可されていません: アセンブリの公開キーの変更
プロパティ、フィールド、パラメーター、および戻り値
✔️ 許可: プロパティ、フィールド、戻り値、または out パラメーターの値をより派生型に変更する
たとえば、 Object の型を返すメソッドは、 String インスタンスを返すことができます。 (ただし、メソッドシグネチャは変更できません)。
✔️ 許可: メンバーが仮想でない場合に、プロパティまたはパラメーターに対して受け入れられる値の範囲を増やす
メソッドに渡すことができる値またはメンバーから返される値の範囲は拡張できますが、パラメーターまたはメンバー型は展開できません。 たとえば、メソッドに渡される値は 0 から 124 から 0 - 255 に拡張できますが、パラメーターの型を Byte から Int32 に変更することはできません。
❌ DISALLOWED: メンバーが仮想である場合に、プロパティまたはパラメーターで受け入れられる値の範囲を増やす
この変更により、既存のオーバーライドされたメンバーが壊れ、拡張された値の範囲で正しく機能しなくなります。
❌ 許可されていません: プロパティまたはパラメーターに対して受け入れられる値の範囲を減らす
❌ 禁止事項: プロパティ、フィールド、戻り値、または out パラメーターで返される値の範囲を広げること
❌ DISALLOWED: プロパティ、フィールド、メソッドの戻り値、または out パラメーターの戻り値を変更する
❌ DISALLOWED: プロパティ、フィールド、またはパラメーターの既定値を変更する
パラメーターの既定値を変更または削除することは、バイナリ区切りではありません。 パラメーターの既定値を削除するとソースの中断が発生し、パラメーターの既定値を変更すると、再コンパイル後に動作が中断される可能性があります。
このため、パラメーターの既定値を削除することは、あいまいさを排除するために、これらの既定値を新しいメソッド オーバーロードに "移動" する場合に許容されます。 たとえば、既存のメソッド
MyMethod(int a = 1)を考えてみましょう。MyMethodとaの 2 つの省略可能なパラメーターを持つbのオーバーロードを導入する場合は、aの既定値を新しいオーバーロードに移動することで互換性を維持できます。 2 つのオーバーロードはMyMethod(int a)とMyMethod(int a = 1, int b = 2)です。 このパターンを使用すると、MyMethod()をコンパイルできます。❌ 未許可: 数値の戻り値の有効桁数を変更する
❓ 判断が必要:入力の解析と新しい例外のスローを変更する (解析の動作がドキュメントで指定されていない場合でも)
❌ 禁止される:
union宣言におけるケース型の追加や削除union型のケース型の追加または削除は、バイナリ区切りとソース区切りの両方です。ケースの種類を追加した後、パターン マッチング テストは完全ではなくなりました。 コンパイラは、パターン マッチング式を非網羅的として警告します。 実行時に予期しない値が発生すると、ランタイム例外が発生します。 ケース型を削除すると、そのケース型のコンストラクター宣言が削除されます。
例外
✔️ 許可:既存の例外ではなく派生した例外をスローする
新しい例外は既存の例外のサブクラスであるため、以前の例外処理コードは引き続き例外を処理します。 たとえば、.NET Framework 4 では、カルチャの作成と取得のメソッドは、カルチャが見つからない場合にCultureNotFoundExceptionではなく、ArgumentExceptionをスローし始めました。 CultureNotFoundExceptionはArgumentExceptionから派生しているため、これは許容される変更です。
✔️ 許可: NotSupportedException、NotImplementedException、NullReferenceExceptionよりも具体的な例外を投げる
✔️ 許可:回復不能と見なされる例外をスローする
回復不能な例外をキャッチするのではなく、高レベルの catch-all ハンドラーで処理する必要があります。 したがって、ユーザーには、これらの明示的な例外をキャッチするコードは必要ありません。 回復不可能な例外は次のとおりです。
✔️ 許可:新しいコード パスで新しい例外をスローする
例外は、新しいパラメーター値または状態で実行され、以前のバージョンを対象とする既存のコードでは実行できない新しいコード パスにのみ適用する必要があります。
✔️ 許可: より堅牢な動作または新しいシナリオを有効にするために例外を削除する
たとえば、以前は正の値のみを処理し、それ以外の場合は
DivideをスローしたArgumentOutOfRangeExceptionメソッドは、例外をスローせずに負の値と正の値の両方をサポートするように変更できます。✔️ 許可: エラー メッセージのテキストを変更する
開発者は、ユーザーのカルチャに基づいて変更されるエラー メッセージのテキストに依存しないでください。
❌ 未許可: 上記以外の場合に例外をスローする
❌ 許可されていません: 上記以外の場合の例外の削除
属性
✔️ 許可されること: 観測対象ではない属性の値を変更する
❌ 禁止: 観察可能な属性の値を変更すること
❓ 判断が必要: 属性の削除
ほとんどの場合、属性 ( NonSerializedAttribute など) の削除は重大な変更です。
プラットフォームのサポート
✔️ 許可: 以前はサポートされなかったプラットフォームでの操作のサポート
❌ 禁止されています: 以前はプラットフォームでサポートされていた操作が、現在はサポートされていないか、特定のサービスパックが必要になりました
内部実装の変更
❓ 判断が必要:内部タイプの表面積を変更する
このような変更は一般に許可されていますが、非公開のリフレクションは互換性がなくなります。 一般的なサード パーティ製ライブラリや多数の開発者が内部 API に依存している場合、このような変更が許可されない場合があります。
❓ 判断が必要: メンバーの内部実装を変更する
これらの変更は一般に許可されますが、プライベートなリフレクションには支障をきたす可能性があります。 顧客コードがプライベートリフレクションに頻繁に依存している場合や、変更によって意図しない副作用が生じる場合、これらの変更が許可されない場合があります。
✔️ 許可:操作のパフォーマンスを向上させる
操作のパフォーマンスを変更する機能は不可欠ですが、このような変更により、操作の現在の速度に依存するコードが中断される可能性があります。 これは、非同期操作のタイミングに依存するコードに特に当てはまります。 パフォーマンスの変更は、問題の API の他の動作には影響しません。それ以外の場合、変更は中断されます。
✔️ 許可: 操作の性能を間接的に変更すること (多くの場合、それが悪影響を及ぼすことがあります)
問題の変更が何らかの理由で破壊的変更として分類されていない場合、これは許容されます。 多くの場合、追加の操作を含めたり、新しい機能を追加したりするアクションを実行する必要があります。 これはほとんどの場合、パフォーマンスに影響しますが、問題の API を期待どおりに機能させるために不可欠な場合があります。
❌ DISALLOWED: 同期 API を非同期 (およびその逆) に変更する
コード変更
✔️ 許可: パラメーターにparamsを追加する
❌ 禁止: コード ブロックにcheckedステートメントを追加すること
この変更により、以前に実行されたコードから OverflowException がスローされることがあるため、許容されません。
❌ DISALLOWED: パラメーターからparamsを削除することは許可されていません
❌ DISALLOWED:
paramsパラメーターのコレクション型の変更C# 13 以降では、
paramsパラメーターは、Span<T>、ReadOnlySpan<T>、アクセス可能なパラメーターなしのコンストラクターとインスタンス IEnumerable<T> メソッドを使用してAddを実装する構造体またはクラス型、およびIList<T>などの特定のインターフェイス型など、配列以外のコレクション型をサポートします。 既存のparamsパラメーターのコレクション型 (たとえば、params T[]からparams ReadOnlySpan<T>) を変更すると、メソッドの IL シグネチャが変更され、バイナリ破壊的変更になります。 以前のバージョンに対してコンパイルされた呼び出し元は再コンパイルする必要があります。✔️ 許可: 拡張メソッドを拡張ブロック メンバー構文に変換する
C# 14 以降では、以前の
extensionパラメーター構文に加えて、thisブロックを使用して拡張メンバーを宣言できます。 どちらの形式でも同じ IL が生成されるため、呼び出し元はそれらを区別できません。 既存の拡張メソッドを新しい拡張ブロック構文に変換することは、バイナリとソースの互換性があります。❌ 禁止: イベントの発生順序を変更すること
開発者は、イベントが同じ順序で発生することを合理的に期待できます。開発者コードは、イベントが発生する順序によって頻繁に異なります。
❌ 許可されていません: 特定のアクションに対するイベントの発生を削除する
❌ DISALLOWED: 指定されたイベントが呼び出される回数を変更する
❌ 許可されていません: 列挙型にFlagsAttributeを追加することは
こちらも参照ください
.NET