クロス プラットフォーム ツールによるライブラリの開発

この記事では、クロスプラットフォーム CLI ツールを使用して .NET 用ライブラリを作成する方法について説明します。 CLI は、サポートされる任意の OS で動作する効率的で低レベルのエクスペリエンスを提供します。 Visual Studio でライブラリを構築することもできます。Visual Studio で構築する場合は、Visual Studio ガイドを参照してください。

必須コンポーネント

.NET Core SDK と CLI がコンピューターにインストールされている必要があります。

このドキュメントで .NET Framework バージョンについて扱うセクションでは、.NET Framework が Windows コンピューターにインストールされている必要があります。

また、古い .NET Framework ターゲットをサポートする場合、.NET ターゲット プラットフォーム ページから古い .NET Framework バージョンの Targeting/Developer Pack をインストールする必要があります。 次の表を参照してください。

.NET Framework のバージョン ダウンロードするもの
4.6.1 .NET Framework 4.6.1 Targeting Pack
4.6 .NET Framework 4.6 Targeting Pack
4.5.2 .NET Framework 4.5.2 Developer Pack
4.5.1 .NET Framework 4.5.1 Developer Pack
4.5 Windows 8 用 Windows ソフトウェア開発キット
4.0 Windows SDK for Windows 7 および .NET Framework 4
2.0、3.0、および 3.5 .NET Framework 3.5 SP1 Runtime (または Windows 8 以降のバージョン)

.NET Standard をターゲット設定する方法

.NET Standard にあまりなじみがない場合、詳細については、「.NET Standard」をご覧ください。

この記事には、.NET Standard バージョンを多様な実装にマップする表が掲載されています。

.NET Standard 1.0 1.1 1.2 1.3 1.4 1.5 1.6 2.0
.NET Core 1.0 1.0 1.0 1.0 1.0 1.0 1.0 2.0
.NET Core 1.x SDK を使用しての .NET Framework 4.5 4.5 4.5.1 4.6 4.6.1 4.6.2
.NET Core 2.0 SDK を使用しての .NET Framework 4.5 4.5 4.5.1 4.6 4.6.1 4.6.1 4.6.1 4.6.1
Mono 4.6 4.6 4.6 4.6 4.6 4.6 4.6 5.4
Xamarin.iOS 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.14
Xamarin.Mac 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.8
Xamarin.Android 7.0 7.0 7.0 7.0 7.0 7.0 7.0 8.0
ユニバーサル Windows プラットフォーム 10.0 10.0 10.0 10.0 10.0 vNext vNext vNext
Windows 8.0 8.0 8.1
Windows Phone 8.1 8.1 8.1
Windows Phone Silverlight 8.0
  • 列は .NET Standard バージョンです。 各見出しセルは、そのバージョンの .NET Standard に追加された API に関するドキュメントのリンクになっています。
  • 行は、各 .NET 実装です。
  • 各セルのバージョン番号は、その .NET Standard バージョンをターゲットにするために必要な最小バージョンの実装です。

ライブラリを作成する目的でこの表の意味について説明します。

選択する .NET Standard のバージョンは、最新の API にアクセスできることと、より多くの .NET 実装と .NET Standard バージョンをターゲット設定できることのトレードオフで決まります。 ターゲット設定可能なプラットフォームとバージョンの範囲は、netstandardX.X のバージョンを選択し (X.X はバージョン番号です)、プロジェクト ファイル (.csproj または .fsproj) に追加することで制御します。

.NET Standard をターゲット設定する場合、ニーズに応じて主に 3 つのオプションがあります。

  1. テンプレートに指定されている既定のバージョンの .NET Standard (netstandard1.4) を使用できます。これで .NET Standard 上のほとんどの API にアクセスできますが、UWP、.NET Framework 4.6.1、近日公開される .NET Standard 2.0 との互換性もあります。

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <TargetFramework>netstandard1.4</TargetFramework>
      </PropertyGroup>
    </Project>
    
  2. プロジェクト ファイルの TargetFramework ノードの値を変更することで、.NET Standard の下位または上位バージョンを使用することができます。

    .NET Standard バージョンは下位互換性があります。 つまり、netstandard1.0 ライブラリは、netstandard1.1 プラットフォーム以降で実行されます。 ただし、上位互換性はないため、古い .NET Standard プラットフォームは新しいプラットフォームを参照できません。 つまり、netstandard1.0 ライブラリは、netstandard1.1 以降をターゲットとするライブラリを参照できません。 API とプラットフォームを適切に組み合わせ、ニーズに対応できる Standard バージョンを選択してください。 現時点では netstandard1.4 が推奨されます。

  3. .NET Framework バージョン 4.0 以前をターゲットにする場合、または .NET Framework では利用できて .NET Standard では利用できない API (System.Drawing など) を使用する場合は、マルチターゲットの方法について次のセクションを参照してください。

.NET Framework をターゲット設定する方法

注意

次の手順では、.NET Framework がコンピューターにインストールされていると想定しています。 依存関係のインストールについては、「前提条件」を参照してください。

ここで使用されている .NET Framework バージョンには、現在サポートされていないものもあります。 サポートされないバージョンについては、「.NET Framework Support Lifecycle Policy FAQ」(.NET Framework のサポート ライフサイクル ポリシーについてよく寄せられる質問) を参照してください。

最大数の開発者とプロジェクトを対象にしたい場合、基準となるターゲットは .NET Framework 4.0 です。 .NET Framework をターゲット設定するには、まず、サポートしたい .NET Framework バージョンに対応する正しいターゲット フレームワーク モニカー (TFM) を使用します。

.NET Framework 2.0   --> net20
.NET Framework 3.0   --> net30
.NET Framework 3.5   --> net35
.NET Framework 4.0   --> net40
.NET Framework 4.5   --> net45
.NET Framework 4.5.1 --> net451
.NET Framework 4.5.2 --> net452
.NET Framework 4.6   --> net46
.NET Framework 4.6.1 --> net461
.NET Framework 4.6.2 --> net462
.NET Framework 4.7   --> net47

この TFM をプロジェクト ファイルの TargetFramework セクションに挿入します。 .NET Framework 4.0 をターゲットとするライブラリを作成する例を次に示します。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net40</TargetFramework>
  </PropertyGroup>
</Project>

以上です。 これは .NET Framework 4 向けにのみコンパイルされていますが、新しいバージョンの .NET Framework のライブラリを使用できます。

マルチターゲットを設定する方法

注意

次の手順では、.NET Framework がコンピューターにインストールされている想定です。 インストールする必要がある依存関係と、そのダウンロードできる場所については、「前提条件」セクションを参照してください。

プロジェクトが .NET Framework と .NET Core の両方をサポートしている場合、状況によっては古いバージョンの .NET Framework をターゲットにする必要があります。 このシナリオで、新しい API と新しいターゲット向けの言語構成を使用する場合、コードで #if ディレクティブを使用します。 また、必要に応じて、ターゲットにする各プラットフォームに応じて異なるパッケージと依存関係追加して、それぞれに異なる必要な API を含めます。

たとえば、HTTP 上でネットワークキング操作を行うライブラリがあるとします。 .NET Standard と .NET Framework バージョン 4.5 以降の場合、System.Net.Http 名前空間の HttpClient クラスを使用できます。 ただし、それより前のバージョンの .NET Framework に HttpClient クラスはないので、代わりに System.Net 名前空間の WebClient クラスを使用できます。

プロジェクト ファイルは次のようになります。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netstandard1.4;net40;net45</TargetFrameworks>
  </PropertyGroup>

  <!-- Need to conditionally bring in references for the .NET Framework 4.0 target -->
  <ItemGroup Condition="'$(TargetFramework)' == 'net40'">
    <Reference Include="System.Net" />
  </ItemGroup>

  <!-- Need to conditionally bring in references for the .NET Framework 4.5 target -->
  <ItemGroup Condition="'$(TargetFramework)' == 'net45'">
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Threading.Tasks" />
  </ItemGroup>
</Project>

主な変更点が 3 つあります。

  1. TargetFramework ノードは TargetFrameworks で置き換えられ、3 つの TFM が内部に表現されています。
  2. 1 つの .NET Framework 参照を取り込む net40 ターゲットの <ItemGroup> ノードがあります。
  3. .NET Framework の参照 2 に取り込む net45 ターゲットの <ItemGroup> ノードがあります。

ビルド システムは #if ディレクティブで使用される次のプリプロセッサ シンボルを認識します。

ターゲット フレームワーク シンボル
.NET Framework NET20, NET35, NET40, NET45, NET451, NET452, NET46, NET461, NET462, NET47, NET471
.NET Standard NETSTANDARD1_0, NETSTANDARD1_1, NETSTANDARD1_2, NETSTANDARD1_3, NETSTANDARD1_4, NETSTANDARD1_5, NETSTANDARD1_6, NETSTANDARD2_0
.NET Core NETCOREAPP1_0NETCOREAPP1_1NETCOREAPP2_0

次に、ターゲットごとに条件付きコンパイルを利用する例を示します。

using System;
using System.Text.RegularExpressions;
#if NET40
// This only compiles for the .NET Framework 4 targets
using System.Net;
#else
 // This compiles for all other targets
using System.Net.Http;
using System.Threading.Tasks;
#endif

namespace MultitargetLib
{
    public class Library
    {
#if NET40
        private readonly WebClient _client = new WebClient();
        private readonly object _locker = new object();
#else
        private readonly HttpClient _client = new HttpClient();
#endif

#if NET40
        // .NET Framework 4.0 does not have async/await
        public string GetDotNetCount()
        {
            string url = "http://www.dotnetfoundation.org/";

            var uri = new Uri(url);

            string result = "";

            // Lock here to provide thread-safety.
            lock(_locker)
            {
                result = _client.DownloadString(uri);
            }

            int dotNetCount = Regex.Matches(result, ".NET").Count;

            return $"Dotnet Foundation mentions .NET {dotNetCount} times!";
        }
#else
        // .NET 4.5+ can use async/await!
        public async Task<string> GetDotNetCountAsync()
        {
            string url = "http://www.dotnetfoundation.org/";

            // HttpClient is thread-safe, so no need to explicitly lock here
            var result = await _client.GetStringAsync(url);

            int dotNetCount = Regex.Matches(result, ".NET").Count;

            return $"dotnetfoundation.org mentions .NET {dotNetCount} times in its HTML!";
        }
#endif
    }
}

dotnet build を使用してこのプロジェクトをビルドすると、bin/ フォルダー以下に 3 つのディレクトリがあることがわかります。

net40/
net45/
netstandard1.4/

各ディレクトリには、各ターゲットの .dll ファイルがあります。

.NET Core でライブラリをテストする方法

プラットフォーム全体でテストできることが重要です。 xUnit または MSTest はそのまま利用できます。 どちらも、.NET Core 上のライブラリの単体テストに最適です。 テスト プロジェクトでソリューションをセットアップする方法は、ソリューションの構造によって異なります。 次の例は、テスト ディレクトリとソース ディレクトリが同じ最上位ディレクトリにある場合です。

注意

この例ではいくつかの .NET CLI コマンドを使用しています。 詳細については、「dotnet new」と「dotnet sln」を参照してください。

  1. ソリューションを設定します。 次のコマンドで実行することができます。

    mkdir SolutionWithSrcAndTest
    cd SolutionWithSrcAndTest
    dotnet new sln
    dotnet new classlib -o MyProject
    dotnet new xunit -o MyProject.Test
    dotnet sln add MyProject/MyProject.csproj
    dotnet sln add MyProject.Test/MyProject.Test.csproj
    

    これでプロジェクトが作成され、ソリューション内のプロジェクトがリンクされます。 SolutionWithSrcAndTest のディレクトリは次のようになります。

    /SolutionWithSrcAndTest
    |__SolutionWithSrcAndTest.sln
    |__MyProject/
    |__MyProject.Test/
    
  2. テスト プロジェクトのディレクトリに移動し、MyProject から MyProject.Test への参照を追加します。

    cd MyProject.Test
    dotnet add reference ../MyProject/MyProject.csproj
    
  3. パッケージを復元し、プロジェクトをビルドします。

    dotnet restore
    dotnet build
    
  4. dotnet test コマンドを実行して、xUnit が実行されることを確認します。 MSTest を使用する場合は、MSTest コンソール実行ツールが実行されることを確認します。

以上です。 コマンド ライン ツールを使用して、すべてのプラットフォームでライブラリをテストできるようになりました。 すべてをセットアップしてテストに進む場合、ライブラリのテストはとても単純です。

  1. ライブラリに変更を加えます。
  2. コマンド ラインから、dotnet test コマンドを使用してテスト ディレクトリでテストを実行します。

dotnet test コマンドを呼び出すと、コードは自動的にリビルドされます。

複数のプロジェクトを使用する方法

大規模なライブラリで一般的なニーズは、複数のプロジェクトに機能を配置することです。

たとえば、慣用的な C# や F# で使用できるライブラリをビルドするとします。 これは、ライブラリのユーザーは、C# または F# に対して自然な方法で使用することを意味します。 たとえば、C# の場合、次のようにライブラリを使用できます。

using AwesomeLibrary.CSharp;

public Task DoThings(Data data)
{
    var convertResult = await AwesomeLibrary.ConvertAsync(data);
    var result = AwesomeLibrary.Process(convertResult);
    // do something with result
}

F# の場合は次のようになります。

open AwesomeLibrary.FSharp

let doWork data = async {
    let! result = AwesomeLibrary.AsyncConvert data // Uses an F# async function rather than C# async method
    // do something with result
}

このような使用シナリオは、アクセスされる API が C# と F# 用に異なる構造を持つ必要があることを示します。 これを実現する一般的なアプローチとして、ライブラリのすべてのロジックをコア プロジェクトに取り入れ、C# および F# プロジェクトでコア プロジェクトに呼び出す API レイヤーを定義する方法があります。 以降のセクションでは、次の名前を使用します。

  • AwesomeLibrary.Core - ライブラリのすべてのロジックを含むコア プロジェクト
  • AwesomeLibrary.CSharp - C# で使用するためのパブリック API を含むプロジェクト
  • AwesomeLibrary.FSharp - F# で使用するためのパブリック API を含むプロジェクト

自分の端末で次のコマンドを実行し、このガイドと同じ構造を作成することができます。

mkdir AwesomeLibrary && cd AwesomeLibrary
dotnet new sln
mkdir AwesomeLibrary.Core && cd AwesomeLibrary.Core && dotnet new classlib
cd ..
mkdir AwesomeLibrary.CSharp && cd AwesomeLibrary.CSharp && dotnet new classlib
cd ..
mkdir AwesomeLibrary.FSharp && cd AwesomeLibrary.FSharp && dotnet new classlib -lang F#
cd ..
dotnet sln add AwesomeLibrary.Core/AwesomeLibrary.Core.csproj
dotnet sln add AwesomeLibrary.CSharp/AwesomeLibrary.CSharp.csproj
dotnet sln add AwesomeLibrary.FSharp/AwesomeLibrary.FSharp.fsproj

これで上記の 3 つのプロジェクトと、プロジェクトをリンクするソリューション ファイルが追加されます。 ソリューション ファイルとリンクするプロジェクトを作成すると、最上位レベルからプロジェクトを復元し、ビルドできるようになります。

プロジェクト間参照

プロジェクトを参照するには、.NET Core CLI を使用してプロジェクト参照を追加することをお勧めします。 AwesomeLibrary.CSharpAwesomeLibrary.FSharp のプロジェクト ディレクトリから、次のコマンドを実行できます。

$ dotnet add reference ../AwesomeLibrary.Core/AwesomeLibrary.Core.csproj

AwesomeLibrary.CSharpAwesomeLibrary.FSharp の両方のプロジェクト ファイルは、ProjectReference ターゲットとして AwesomeLibrary.Core を参照するようになります。 この参照を確認するには、プロジェクト ファイルに以下の行があることを確認します。

<ItemGroup>
  <ProjectReference Include="..\AwesomeLibrary.Core\AwesomeLibrary.Core.csproj" />
</ItemGroup>

この .NET Core CLI を使用しない場合は、このセクションを各プロジェクト ファイルに手動で追加します。

ソリューションの構築

マルチプロジェクト ソリューションのもう 1 つの重要な側面は、全体のプロジェクト構造を適切に構築することです。 ただし、dotnet sln add でソリューション ファイルに各プロジェクト ファイルがリンクされ、ソリューション レベルで dotnet restoredotnet build を実行できる限り、好みに応じてコードを整理することができます。