Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
C#による.NET Core入門(5)

C#による.NET Core入門(5)

.NET Standardなライブラリプロジェクトを作成して参照する

2017年12月7日

クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載。5回目は.NET Standardなクラスライブラリなプロジェクトを作成し、別のコンソールプロジェクトから参照する方法を説明する。

レッドハット株式会社 田中 孝佳
  • このエントリーをはてなブックマークに追加

クラス・ライブラリ・プロジェクトの作成

プロジェクトとソリューションの作成

 今回はクラスライブラリプロジェクトを作成し、そのプロジェクトを参照するコンソールプロジェクトを作成する。また2つのプロジェクトをまとめるソリューションも作成する。まずリスト1の一連のコマンドを実行し、ソリューションの作成、プロジェクトの作成、プロジェクトのソリューションへの追加を行ってほしい。

shell
$ mkdir Example4
$ cd Example4
$ dotnet new sln
$ dotnet new console -o Example4.ConsoleApp
$ dotnet sln add Example4.ConsoleApp/Example4.ConsoleApp.csproj
$ dotnet new classlib -o Example4.Lib
$ dotnet sln add Example4.Lib/Example4.Lib.csproj
リスト1 ソリューションとクラスライブラリとコンソールプロジェクトを作成し、プロジェクトをソリューションに追加するコマンド

 なお、これ以降のシェル上でのコマンド実行は、リスト1実行後のカレントディレクトリである、ソリューションファイルが配置されているディレクトリを基準に全て実行してある。カレントディレクトリの移動が必要な場合は、最初にcdコマンドで移動している。

 dotnet new classlibで指定しているclasslibというのがデフォルトで用意されているクラスライブラリプロジェクトのテンプレートだ。作られたクラスライブラリプロジェクトにはClass1.cs<プロジェクト名>.csprojの2つのファイルが存在しているはずである。Class1.csは適宜リネームや削除して必要なクラスファイルを追加すればよいが、まず<プロジェクト名>.csprojファイルの中身を見てみよう。

Example4.Lib.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
</Project>
リスト2 デフォルトのテンプレートで生成されたクラスライブラリプロジェクトの.csprojファイル

 XMLの<Project>要素は、前回説明したコンソールプロジェクトと同じである。前回のリスト2と見比べると、<PropertyGroup>の子要素の<OutputType>要素がなくなり、<TargetFramework>要素がnetstandard2.0となっているところが違う要素である。

 もしこのクラスライブラリプロジェクトを.NET Core 2.0以降のみから参照できるライブラリとして開発するのであれば、この<TargetFramework>要素をnetcoreapp2.0に変更することもできる。しかし、netstandard2.0とすることにより、既存の.NET Frameworkをはじめ、Xamarinの各プラットフォームやUWPといったプロジェクトからも参照できるクラスライブラリになり汎用性が増す。特に.NET Core 2.0向けのライブラリとしてまず開発する場合、ほとんどのケースでnetstandard2.0として問題がないため、クラスライブラリプロジェクトの<TargetFramework>netstandard2.0を指定して開発を進めていくことを強くお勧めする。

 また、可能であればより低い.NET Standardのバージョンを指定することをお勧めする。この理由については、次の「.NET Standard 2.0とは」の項で説明する。

.NET Standard 2.0とは

 もともと.NETは、Windows上の.NET Frameworkという単一実装のみで動いていた。そのため、あるバージョンの.NET FrameworkのクラスライブラリのAPI群は、そのまま同じバージョンの.NET Frameworkの実装で動くことが確実だった。

 その後、SilverlightWindows Phoneアプリなど、複数の.NETプラットフォームが出てきて、対象プラットフォーム(Windows上のフル.NET Frameworkなのか、Silverlightなのか、Windows Phoneなのか)によってサポートするAPIに違いが生じてきた。確かに違いはあるものの、共通部分も多くあるため、「プラットフォームの差異による影響を受けない特殊なクラスライブラリを利用することで、共通部分は同じソースコードで動かしたい」という要望から、プロファイルベースのポータブル・クラス・ライブリPCL)という仕組みが生まれた。

 ただしPCLにも課題が生じてきている。具体的には、PCLはお互いのプラットフォームがサポートするAPIの共通部分という形を採っていたため、組み合わせが増えるとより複雑なサポートAPIの管理が必要になってきたのだ。

 さらに.NET Coreが出てきた現在、.NETが正式にサポートするプラットフォームは、

  • .NET Core
  • .NET Framework
  • mono
  • Xamarin (iOS/macOS/Android)
  • UWP

など広範囲に及ぶようになった。そのため、プロファイルベースの管理ではなく、.NET Standardという仕組みを新たに導入するに至った。

 .NET Standardは、「それぞれの.NET実装が実装すべきAPI群をあらかじめ決めておき、各プラットフォームでの動作をサポートする.NET実装が実際にそのAPIを実装してサポートする」という仕組みになっている。ターゲットとする.NET Standardのバージョンを決めておくことで、クラスライブラリを開発するときは、サポートされるAPIのみを参照して開発できる。また、完成したクラスライブラリは、そのバージョンの.NET Standardに対応している全ての.NET実装で参照できる。

 .NET Standardのバージョンごとにサポートする.NET実装は、公式ドキュメントの表を参照してほしい(参考:表1に執筆時点のものを引用転載している)。

.NET Standard 1 1.1 1.2 1.3 1.4 1.5 1.6  2 
.NET Core 1 1 1 1 1 1 1  2 
.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 10 10 10 10 10 10 10.14
Xamarin.Mac 3 3 3 3 3 3 3 3.8
Xamarin.Android 7 7 7 7 7 7 7 8
UWP 10 10 10 10 10 vNext vNext vNext
Windows 8 8 8.1
Windows Phone 8.1 8.1 8.1
Windows Phone Silverlight 8
表1 .NET Standardの各バージョンに対応する各.NET実装のバージョン

 まず、.NET実装の各バージョンから、対応する.NET Standardのバージョンを確認してみよう。

 .NET Core 2.0に関して、表1の黄色の背景色で示した部分を見てほしい。.NET Core 2.0netcoreapp2.0)をターゲットとするプロジェクトは、.NET Standard 2.0netstandard2.0)をターゲットとするクラスライブラリを参照できることが分かる。

 また.NET Core 1.0に関して、黄色の背景色の左側にある列を見ほしい。.NET Core 1.0netcoreapp1.0)をターゲットとするプロジェクトは、.NET Standard 1.6およびそれより小さいバージョン(1.5/1.4/1.3/1.2/1.1/1)をターゲットとするクラスライブラリを参照できることが分かる。

 次に、.NET Standardのバージョンから、それを実装している各.NET実装のバージョンを確認してみよう(先ほどとは逆の視点になる)。

 表1の緑色の背景色で示した部分を見てほしい。もし.NET Standard 2.0を指定すると、サポートされる最小のバージョンは、.NET Frameworkだと4.6.1と比較的新しいバージョンにしないといけない。他の.NET実装も同様に最新バージョンが必要になる。

 もちろん、.NET Standard 2.0の方が1.6やそれ以前より利用可能なAPIが増えているので、.NET Standard 2.0でのみ利用できるAPIを利用する必要がある場合は.NET Standard 2.0と指定するしかないだろう。しかし、より小さなバージョンの.NET Standardで開発可能な場合は、小さいバージョンを指定しておいた方が、そのクラスライブラリを利用できるプラットフォームが多くなるわけである。

 例えば、.NET Standard 2.0の1つ前の.NET Standard 1.6それより小さいバージョン(1.5/1.4/1.3/1.2/1.1/1)を指定してクラスライブラリを開発した場合、.NET Core 1.0以降のバージョンをターゲットとしているプロジェクトで利用できる、つまり.NET Core 2.0をターゲットとしているプロジェクトからも参照できるので、.NET Standard 2.0を指定するよりも幅広いバージョンとプラットフォームで利用できるようになる。これが「可能であればより低い.NET Standardのバージョンを指定することをお勧めする」理由だ。

 また、クラスライブラリで複数の.NET Standardのバージョンをターゲットとすることもできる。.NET Standard 2.0でのみ利用可能なAPIを利用する部分をプリプロセッサディレクティブで切り分けることで、同じソースコードファイルから複数のバージョン向けの.NET Standard向けのバイナリを出力できる。この方法については最後の方の節で紹介する。

クラスライブラリプロジェクトの作成

 それでは実際にクラスライブラリプロジェクトを作成してみよう。

 クラスライブラリプロジェクトは、コンソールプロジェクトと違い、エントリポイントとなるMainメソッドを持たない。他のプロジェクトがクラスライブラリを参照して、そのライブラリ内のクラスを利用できるようにするために、publicなクラスとメソッドを定義する。今回は、Class1.csファイルをいったん削除し、MyStringLib.csというファイルを作成し、リスト3のようにコードを定義する。

MyClassLib.cs
using System;

namespace Example4.Lib
{
  public class MyStringLib
  {
    public static string Copy(string str)
    {
      return string.Copy(str);
    }
  }
}
リスト3 publicなクラスとメソッドを定義

 編集後、リスト4の1つ目のdotnetコマンドでクラスライブラリプロジェクトをビルドすると、bin/Debug/netcoreapp2.0フォルダーにExample4.ConsoleApp.dllというバイナリファイルが生成される。

 配置用に発行したい場合は、2つ目のdotnetコマンドを実行するとbin/Debug/netcoreapp2.0/publishフォルダー以下に生成されることが確認できる。

shell
$ cd Example4.ConsoleApp
$ dotnet build
Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Example4.ConsoleApp -> /home/tatanaka/Example4/Example4.ConsoleApp/bin/Debug/netcoreapp2.0/Example4.ConsoleApp.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:09.40

$ dotnet publish
Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Example4.ConsoleApp -> /home/tatanaka/Example4/Example4.ConsoleApp/bin/Debug/netcoreapp2.0/Example4.ConsoleApp.dll
  Example4.ConsoleApp -> /home/tatanaka/Example4/Example4.ConsoleApp/bin/Debug/netcoreapp2.0/publish/
リスト4 ビルドと配置コマンドの実行と結果例

プロジェクトの参照

コンソールプロジェクトからの参照

 次に、このクラスライブラリプロジェクトをコンソールプロジェクトから参照してみよう。リスト5のコマンドを実行すると、コンソールプロジェクトからクラスライブラリプロジェクトへの参照が追加される。

shell
$ cd ..
$ dotnet add Example4.ConsoleApp/Example4.ConsoleApp.csproj reference Example4.Lib/Example4.Lib.csproj
Reference `..\Example4.Lib\Example4.Lib.csproj` added to the project.
リスト5 プロジェクト参照を追加するコマンド

 このコマンドは、リスト6のような<ItemGroup>要素を、.csprojファイルに追加している。実際にリスト5のコマンドを実行した後に.csprojファイルを見ると確認できる。

 また、.csprojファイルを直接編集してこの要素を追加することでも、参照を追加できる。

 コンソールプロジェクト側のソースコードからクラスライブラリ側のメソッドを呼び出すコードを、リスト7のように追加した。

Program.cs
using System;
using Example4.Lib;

namespace Example4.ConsoleApp
{
  class Program
  {
    static void Main(string[] args)
    {
      var testString = "test";
      var copiedString = MyStringLib.Copy(testString);
      Console.WriteLine($"copied string is {copiedString}");
    }
  }
}
リスト7 コンソール側からクラスライブラリ側のメソッドを呼び出す

 リスト8のようにコマンドを実行すると、コンソールプロジェクトを実行できる。

 コマンド出力を見ると分かるように、クラスライブラリプロジェクトをビルドした後、コンソールプロジェクトをビルドしている。あるプロジェクトをビルドするためには、プロジェクト参照している全てのプロジェクトを先にビルドしないといけないため、プロジェクトを相互参照することはできない。

shell
$ cd Example4.ConsoleApp
$ dotnet build
Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Example4.Lib -> /home/tatanaka/Example4/Example4.Lib/bin/Debug/netstandard2.0/Example4.Lib.dll
  Example4.ConsoleApp -> /home/tatanaka/Example4/Example4.ConsoleApp/bin/Debug/netcoreapp2.0/Example4.ConsoleApp.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:09.40
リスト8 コンソールプロジェクトのビルド

 またリスト9のようにコンソールプロジェクトを発行すると、参照しているクラスライブラリプロジェクトの.dllバイナリファイルがbin/Debug/netcoreapp2.0/publishフォルダーの下に一緒に生成されていることも分かる。これは参照しているライブラリを全て一緒の場所に配置するためである。

shell
$ dotnet publish
Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Example4.Lib -> /home/tatanaka/Example4/Example4.Lib/bin/Debug/netstandard2.0/Example4.Lib.dll
  Example4.ConsoleApp -> /home/tatanaka/Example4/Example4.ConsoleApp/bin/Debug/netcoreapp2.0/Example4.ConsoleApp.dll
  Example4.ConsoleApp -> /home/tatanaka/Example4/Example4.ConsoleApp/bin/Debug/netcoreapp2.0/publish/
リスト9 コンソールプロジェクトの発行

複数フレームワークをターゲットとするクラスライブラリプロジェクト

 例えば、.NET Core 2.0と.NET Core 1.0および.NET Framework 4.5から参照される予定であるクラスライブラリを作る場合、.NET Standard 1.0~1.6の間のいずれかを単一のターゲットとするクラスライブラリを作成すればよい。しかし、先に.NET Standard 2.0で作成している場合や、将来的に.NET Standard 2.0のみの対応を予定している場合など、「.NET Standard 2.0を主なターゲットとしつつ、より小さいバージョンの.NET Standardもターゲットとして追加したい」というケースもあるだろう。

 本章では、その実現方法を解説する。その題材として、今作成した.NET Standard 2.0ターゲットのクラスライブラリを、.NET Core 1.0のコンソールプロジェクトから参照しようとするところから始めてみよう(前述したバージョンの組み合わせを考えると、これは当然エラーになるが、実際にエラーになる点を確認してみる)。

.NET Core 1.0 コンソールプロジェクトの作成

 まず.NET Core 1.0のコンソールプロジェクトを作成する。プロジェクトの作成自体は.NET Core SDK 2.0で実行できるが、実行するためには.NET Core SDK 1.0が必要なので、実際に実行したい場合は連載第1回の記事などを参考にインストールしてほしい。.NET Core SDK 1.0と2.0(および1.1や今後リリースが予定されるそれ以外のバージョンも含む)はインストール先のパスを変えておけば共存が可能である。

 今回は、~/dotnet-10以下に.NET Core 1.0 SDKを配置しており、PATH環境変数には含めず、表示しているコマンドでは~/dotnet-10/dotnetが.NET Core SDK 1.0の実行コマンドである。

 リスト10のコマンドでコンソールプロジェクトを作成した後、Example4.ConsoleApp.NetCoreApp10/Example4.ConsoleApp.NetCoreApp10.csprojファイルを直接編集して<TargetFramework>要素をnetcoreapp1.0に変更してほしい。

shell
$ cd ..
$ ~/dotnet-10/dotnet new console -o Example4.ConsoleApp.NetCoreApp10
リスト10 .NET Core 1.0をターゲットとするためのプロジェクトの作成

 変更後、.NET Core 2.0のときと同様にリスト11のコマンドでクラスライブラリをプロジェクト参照として追加してみると、エラーが発生した。dotnetコマンドによるプロジェクト参照の追加は、ターゲットフレームワークに互換性がない場合、エラーが出て追加できないようになっている。また、.csprojファイルを直接編集して追加した場合は、ビルド時にエラーが発生する。

shell
$ ~/dotnet-10/dotnet add Example4.ConsoleApp.NetCoreApp10/Example4.ConsoleApp.NetCoreApp10.csproj reference Example4.Lib/Example4.Lib.csproj
Project `/home/tatanaka/Example4/Example4.Lib/Example4.Lib.csproj` cannot be added due to incompatible targeted frameworks between the two projects. Please review the project you are trying to add and verify that is compatible with the following targets:
    - netcoreapp1.0
リスト11 .NET Core 1.0をターゲットとしたプロジェクトに.NET Standard 2.0をターゲットとするクラスライブラリプロジェクトの参照を追加しようとした結果

ターゲットを複数指定したクラスライブラリプロジェクト

 そこで、.NET Core 1.0もサポートしている.NET Standard 1.0をクラスライブラリプロジェクトのターゲットに追加してみよう。

 .NET Standard 2.0から「変更」するのではなく「追加」としたのは、.NET Standard 2.0に対応したコードを基本にしつつ、必要な部分だけ.NET Standard 1.0で実行できるコードに切り替えるためである。ターゲットを複数指定する方法は連載第2回でやったように.csprojファイルをリスト12のように編集して、<TargetFramework>要素から<TargetFrameworks>要素に変更すればよい。

Example4.Lib.csproj
  <PropertyGroup>
    <TargetFrameworks>netstandard2.0;netstandard1.0</TargetFrameworks>
  </PropertyGroup>
リスト12 ターゲットを追加したクラスライブラリの.csprojファイル(抜粋)

 さて、netstandard2.0netstandard1.0を指定したので、.NET Core SDK 2.0のdotnetコマンドを使って、このコードをビルドしてみよう。リスト13のようにエラーが発生するはずだ。

shell
$ dotnet build
Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Example4.Lib -> /home/tatanaka/Example4/Example4.Lib/bin/Debug/netstandard2.0/Example4.Lib.dll
MyStringLib.cs(10,27): error CS0117: 'string' does not contain a definition for 'Copy' [/home/tatanaka/Example4/Example4.Lib/Example4.Lib.csproj]

Build FAILED.

MyStringLib.cs(10,27): error CS0117: 'string' does not contain a definition for 'Copy' [/home/tatanaka/Example4/Example4.Lib/Example4.Lib.csproj]
    0 Warning(s)
    1 Error(s)

Time Elapsed 00:00:05.61
リスト13 クラスライブラリプロジェクトの.NET Core 1.0でのビルド結果

 フレームワークを指定しない場合、指定してある全てのターゲットフレームワークに対してビルドを行うが、まず.NET Standard 2.0向けのビルドは成功している。次に.NET Standard 1.0向けのビルドで失敗しているが、これは、string.Copyメソッドが.NET Standard 2.0で追加されたAPIであるからだ。

 そこでこの部分だけifディレクティブを使って、.NET Standard 1.0をターゲットフレームワークとしてコンパイルするときは(より正確には.NET Standard 2.0以外をターゲットフレームワークとしてビルドしたときは)同等の処理を行う別のコードに切り替えるようにしてみよう(リスト14)。各ターゲットにおけるコンパイラーに渡されるプリプロセッサシンボルの値は.NET Coreのドキュメントに記載がある。

MyStringLib.cs
#if NETSTANDARD2_0
return string.Copy(str);
#else
return new string(str.ToCharArray());
#endif
リスト14 ifディレクティブによる、ターゲットフレームワークごとの処理の切り替え(抜粋)

 再度ビルドすると、今度は両方のターゲットフレームワークでビルドが成功するはずだ。

shell
$ dotnet build
Microsoft (R) Build Engine version 15.3.409.57025 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Example4.Lib -> /home/tatanaka/Example4/Example4.Lib/bin/Debug/netstandard1.0/Example4.Lib.dll
  Example4.Lib -> /home/tatanaka/Example4/Example4.Lib/bin/Debug/netstandard2.0/Example4.Lib.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:05.14
リスト15 クラスライブラリプロジェクトのビルド結果

 そこで、再度リスト11のコマンドを実行すると、.NET Core 1.0のコンソールプロジェクトにこのクラスライブラリをプロジェクト参照として追加できるはずだ。するとリスト7同様に、.NET Core 1.0のコンソールプロジェクトにクラスライブラリのメソッドを呼び出す処理を追加できるようになる。

 今回は、クラスライブラリプロジェクトを.NET Standard 2.0をターゲットとして作成し、プロジェクト参照としてコンソールプロジェクトに追加する方法を説明した。さらに複数ターゲットをクラスライブラリに指定する方法を紹介した。

 しかし、プロジェクト参照は参照される側のソースコードが必要であり、全てをビルドし直さないといけないなど制約も多い。そこで次回は、今回作成したクラスライブラリプロジェクトをパッケージングし、nuget.orgにアップロードし、コンソールプロジェクトの参照をNuGet経由に変更してみる予定である。

※以下では、本稿の前後を合わせて5回分(第3回~第7回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

C#による.NET Core入門(5)
3. .NET Coreでプロジェクトを作成して開発してみよう

クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載。3回目は実際にプロジェクトを新規に作成して、Visual Studio Codeを使って開発するフローを説明する。

C#による.NET Core入門(5)
4. .NET Coreでコンソールアプリを配置する

クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載。4回目は作成したコンソールアプリのプロジェクトをビルドして配置する手順を説明する。

C#による.NET Core入門(5)
5. 【現在、表示中】≫ .NET Standardなライブラリプロジェクトを作成して参照する

クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載。5回目は.NET Standardなクラスライブラリなプロジェクトを作成し、別のコンソールプロジェクトから参照する方法を説明する。

C#による.NET Core入門(5)
6. .NET CoreライブラリプロジェクトをパッケージングしてNuGetサーバーに発行する

クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載。6回目はクラスライブラリプロジェクトをNuGetパッケージとして参照できるように、作成と発行を行う。

C#による.NET Core入門(5)
7. .NET Coreで単体テストを行う

クロスプラットフォームで開発できる.NET Coreの基礎から開発実践までが学べる入門連載。最終回は単体テスト用のプロジェクトを作成して、テストを行う方法を説明する。

サイトからのお知らせ

Twitterでつぶやこう!