C# 開発者向けのバージョンと更新に関する考慮事項

互換性は、新しい機能を C# 言語に追加するときの重要な目標です。 ほとんどの場合、既存のコードは、新しいコンパイラのバージョンで問題なく再コンパイルできます。 .NET ランタイム チームにはさらに、更新されたライブラリの互換性を確保するという目標もあります。 ほとんどの場合、更新されたライブラリを使用して更新されたランタイムからアプリを起動すると、その動作は前のバージョンとまったく同じです。

通常、アプリのコンパイルに使用される言語バージョンは、プロジェクトで参照されるランタイムのターゲット フレームワーク モニカー (TFM) と一致します。 既定の言語バージョンの変更について詳しくは、「言語バージョンの構成」というタイトルの記事を参照してください。 この既定の動作により、最大限の互換性が確保されます。

"破壊的変更" を導入する場合、それらは次のように分類されます。

  • "バイナリの破壊的変更": バイナリの破壊的変更によって、新しいランタイムを使用して起動したときに、アプリケーションまたはライブラリで、クラッシュの可能性を含め、異なる動作が発生します。 これらの変更を組み込むには、アプリを再コンパイルする必要があります。 既存のバイナリは正しく機能しません。
  • "ソースの破壊的変更": ソースの破壊的変更では、ソース コードの意味が変更されます。 最新の言語バージョンでアプリケーションをコンパイルする前に、ソース コードを編集する必要があります。 既存のバイナリは、新しいホストとランタイムで正しく実行されます。 言語構文の場合、"ソースの破壊的変更" は、ランタイムの破壊的変更で定義されている "動作の変更" でもあることに注意してください。

バイナリの破壊的変更がアプリに影響を与える場合、アプリを再コンパイルする必要がありますが、ソース コードを編集する必要はありません。 ソースの破壊的変更がアプリに影響を与える場合でも、既存のバイナリは、更新されたランタイムとライブラリを使用する環境で引き続き正しく実行されます。 ただし、新しい言語バージョンとランタイムで再コンパイルするには、ソースを変更する必要があります。 変更がソースとバイナリの両方の破壊的変更である場合、最新バージョンでアプリケーションを再コンパイルし、ソースを更新する必要があります。

C# 言語チームとランタイム チームによる破壊的変更を回避するという目標があるため、アプリケーションの更新は通常、TFM の更新とアプリの再構築になります。 ただし、ただし、一般に配布されるライブラリの場合は、サポートされる TFM とサポートされる言語バージョンに関するポリシーを慎重に評価する必要があります。 最新バージョンで見つかった機能を使用して新しいライブラリを作成したが、コンパイラの以前のバージョンを使用してビルドされたアプリがそれを使用できることを保証しなければならない場合があります。 または、既存のライブラリをアップグレードしたが、多くのユーザーがバージョンをまだアップグレードしていない場合があります。

ライブラリへの破壊的変更の導入

ライブラリのパブリック API に新しい言語機能を採用する場合、その機能の採用によってライブラリのユーザーにバイナリまたはソースのいずれかの破壊的変更が導入されるかどうかを評価する必要があります。 public または protected インターフェイスに表示されない内部実装の変更は互換性があります。

Note

System.Runtime.CompilerServices.InternalsVisibleToAttribute を使用して型で内部メンバーを表示できるようにすると、内部メンバーによって破壊的変更が導入される可能性があります。

"バイナリの破壊的変更" の場合、ユーザーは新しいバージョンを使用するためにコードを再コンパイルする必要があります。 たとえば、次のパブリック メソッドについて考えてみましょう。

public double CalculateSquare(double value) => value * value;

このメソッドに in 修飾子を追加すると、それはバイナリの破壊的変更になります。

public double CalculateSquare(in double value) => value * value;

新しいライブラリが正しく動作するには、ユーザーは、CalculateSquare を使用するアプリケーションを再コンパイルする必要があります。

"ソースの破壊的変更" の場合、ユーザーは、再コンパイルする前にコードを変更する必要があります。 たとえば、次の型について考えてみましょう

public class Person
{
    public string FirstName { get; }
    public string LastName { get; }

    public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName);

    // other details omitted
}

新しいバージョンでは、record 型用に生成された合成メンバーを利用したいとしましょう。 次のような変更を行います。

public record class Person(string FirstName, string LastName);

前の変更では、Person から派生するすべての型を変更する必要があります。 これらのすべての宣言で、record 修飾子を宣言に追加する必要があります。

破壊的変更の影響

"バイナリの破壊的変更" をライブラリに追加すると、そのライブラリを使用するすべてのプロジェクトが強制的に再コンパイルされます。 ただし、これらのプロジェクト内のソース コードを変更する必要はありません。 その結果、各プロジェクトに対する破壊的変更の影響はかなり小さくなります。

ライブラリに対して "ソースの破壊的変更" を行う場合、新しいライブラリで使用するために、すべてのプロジェクトでソースを変更する必要があります。 必要な変更に新しい言語機能が必要な場合、それらのプロジェクトは、現在使用しているものと同じ言語バージョンと TFM に強制的にアップグレードされます。 ユーザーにさらに多くの作業を要求し、ユーザーが強制的にアップグレードされた可能性もあります。

作成する破壊的変更の影響は、ライブラリに依存するプロジェクトの数によって異なります。 ライブラリがいくつかのアプリケーションによって内部的に使用される場合、影響を受けるすべてのプロジェクトの破壊的変更に対応できます。 ただし、ライブラリが一般にダウンロードされる場合、潜在的な影響を評価し、次の代替方法を検討する必要があります。

  • 既存の API を並列化する新しい API を追加する。
  • さまざまな TFM の並列ビルドを検討する。
  • マルチターゲットを検討する。