C# でのバージョン管理

このチュートリアルでは、.NET でのバージョン管理のしくみについて学習します。 また、ライブラリのバージョン管理を行う際や、ライブラリを新しいバージョンにアップグレードする際の考慮事項についても学習します。

ライブラリの作成

一般用途向けの .NET ライブラリを作成したことがある開発者であれば、新しい更新プログラムをロールアウトする必要に迫られた経験もあることでしょう。 このプロセスのあり方によって、既存のコードを新バージョンのライブラリへとシームレスに移行できるかどうかは大きく左右されます。 ここでは、新しいリリースを作成する際に考慮すべき点について、いくつか説明します。

セマンティック バージョン管理

セマンティック バージョン管理 (略して SemVer) は、特定のマイルス トーン イベントを示すためにライブラリの各バージョンに適用される命名規則です。 うまく管理すれば、ライブラリに適用されたバージョン情報によって、同じライブラリの旧バージョンを使用したプロジェクトとの互換性を開発者が確認できるようになります。

SemVer に対する最も基本的なアプローチは、3 コンポーネント形式 MAJOR.MINOR.PATCH です。

  • MAJOR は、互換性のない API 変更を加えたときにインクリメントされます
  • MINOR は、下位互換性のある方法で機能を追加したときにインクリメントされます
  • PATCH は、下位互換性のあるバグ修正を行ったときにインクリメントされます

.NET ライブラリにバージョン情報を適用する際には、他のシナリオ (プレリリース バージョンなど) を指定することもできます。

下位互換性

ライブラリの新バージョンをリリースする際、特に大きな懸念事項となるのが、旧バージョンとの互換性です。 旧バージョンに依存するコードが再コンパイル後に新バージョンで機能する場合、ライブラリの新バージョンは旧バージョンに対してソース互換性があるということになります。 旧バージョンに依存するアプリケーションが再コンパイルを経ずに新バージョンで機能する場合、ライブラリの新バージョンはバイナリ互換性があるということになります。

次に示すのは、旧バージョンのライブラリとの下位互換性を維持するうえでの考慮事項です。

  • 仮想メソッド: 新バージョンで仮想メソッド非仮想にした場合は、そのメソッドをオーバーライドするプロジェクトを更新する必要があります。 これはきわめて重大な変更であり、極力回避することをお勧めします。
  • メソッド シグネチャ: メソッドの動作を更新するためにそのシグネチャも変更する必要がある場合は、そのメソッドに対するコード呼び出しが引き続き機能するように、代わりにオーバーロードを作成する必要があります。 旧メソッドのシグネチャは、新しいメソッド シグネチャを呼び出すようにいつでも操作して、実装の整合性を維持できます。
  • Obsolete 属性: この属性をコード内で使用すると、現在非推奨に指定されていて、今後のバージョンで削除される可能性が高いクラスやクラス メンバーを指定することができます。 これにより、ライブラリを使用している開発者が、今後の重大な変更に余裕を持って準備できるようになります。
  • 省略可能なメソッド引数: これまで省略可能であったメソッド引数を必須にしたり、それらの既定値を変更する場合は、それらの引数が指定されていないすべてのコードを更新する必要があります。

Note

必須の引数を省略可能にしても、メソッドの動作が変更されない限り、影響はほとんどありません。

新バージョンのライブラリへの更新を行いやすくすれば、その分、ユーザーがアップグレードを早く完了できるようになります。

アプリケーション構成ファイル

.NET 開発者の皆さんは、ほとんどのタイプのプロジェクトで app.config ファイルを使用しているのではないでしょうか。 このシンプルな構成ファイルは、新しい更新プログラムのロールアウトをスムーズにするうえで大いに役立ちます。 通常、ライブラリを設計する際には、定期的に変更される可能性が高い情報を app.config ファイルに保存します。そうすれば、それらの情報が更新された際にも、ライブラリの再コンパイルを行うことなく、旧バージョンの構成ファイルを新しいバージョンに置き換えるだけで済みます。

ライブラリの使用

他の開発者によって作成された .NET ライブラリを使用する場合には、新バージョンのライブラリが自分のプロジェクトに対して完全互換ではない場合が多く、それらの変更にうまく対応するために、コードを更新しなければならないことも少なくありません。

幸いなことに、C# と .NET エコシステムでは、重大な変更をもたらす可能性がある新バージョンのライブラリと正常に連携できるよう、アプリを簡単に更新するための機能や技術が提供されています。

アセンブリ バインド リダイレクト

app.config ファイルを使用して、アプリで使用するライブラリのバージョンを更新できます。 バインド リダイレクトというものを追加することで、アプリを再コンパイルしなくても、新しいライブラリ バージョンを使用することができます。 次の例は、アプリの app.config ファイルを更新して、当初のコンパイルに使用された 1.0.0 バージョンではなく、1.0.1 パッチ バージョンの ReferencedLibrary が使用されるようにする方法を示しています。

<dependentAssembly>
    <assemblyIdentity name="ReferencedLibrary" publicKeyToken="32ab4ba45e0a69a1" culture="en-us" />
    <bindingRedirect oldVersion="1.0.0" newVersion="1.0.1" />
</dependentAssembly>

Note

このアプローチは、新バージョンの ReferencedLibrary がアプリに対してバイナリ互換性を持っている場合にのみ有効です。 互換性を判断するときに注意すべき変更点については、上記の「下位互換性」セクションをご覧ください。

new

new 修飾子を使用して、基底クラスの継承メンバーを非表示にできます。 これは、派生クラスが基底クラスの更新に対応できるようにするための 1 つの手段です。

次に例を示します。

public class BaseClass
{
    public void MyMethod()
    {
        Console.WriteLine("A base method");
    }
}

public class DerivedClass : BaseClass
{
    public new void MyMethod()
    {
        Console.WriteLine("A derived method");
    }
}

public static void Main()
{
    BaseClass b = new BaseClass();
    DerivedClass d = new DerivedClass();

    b.MyMethod();
    d.MyMethod();
}

出力

A base method
A derived method

上記の例では、DerivedClass によって BaseClass 内の MyMethod メソッドを非表示にしています。 つまり、派生クラス内に既に存在しているメンバーが新バージョンのライブラリ内の基底クラスによって追加された場合には、派生クラスのメンバーに new 修飾子を使用するだけで、基底クラスのメンバーを非表示にすることができます。

new 修飾子が指定されなかった場合、派生クラスは既定で基底クラスの競合メンバーを非表示にします。コンパイラの警告が生成されますが、コードはコンパイルされます。 つまり、既存のクラスに新しいメンバーを追加するだけで、新バージョンのライブラリは依存先のコードに対して、ソースとバイナリの両方の互換性を持つことになります。

override

override 修飾子を使用した場合、派生実装は基底クラス メンバーの実装を非表示にはせず、そのメンバーを拡張します。 基底クラスのメンバーには、virtual 修飾子が適用されている必要があります。

public class MyBaseClass
{
    public virtual string MethodOne()
    {
        return "Method One";
    }
}

public class MyDerivedClass : MyBaseClass
{
    public override string MethodOne()
    {
        return "Derived Method One";
    }
}

public static void Main()
{
    MyBaseClass b = new MyBaseClass();
    MyDerivedClass d = new MyDerivedClass();

    Console.WriteLine("Base Method One: {0}", b.MethodOne());
    Console.WriteLine("Derived Method One: {0}", d.MethodOne());
}

出力

Base Method One: Method One
Derived Method One: Derived Method One

override 修飾子はコンパイル時に評価され、オーバーライドする仮想メンバーが見つからない場合には、コンパイラがエラーをスローします。

説明されている手法の知識を持ち、それらを使用する状況を理解しておくと、ライブラリのバージョン間の移行を容易にするために今後も役立ちます。