September 2017

Volume 32 Number 9

.NET Standard - .NET Core と .NET Standard の分かりやすい解説

Immo Landwerth | September 2017

.NET ファミリの最新メンバーである .NET Core および .NET Standard と .NET Framework との違いについて多くの混乱が生じています。今回は、それぞれの内容を正確に説明し、どのような場合にどれを選択すべきかを見ていきます。

詳しく見ていく前に、.NET の大要が分かる図を参考に、.NET Core と .NET Standard の位置付けを確認しましょう。.NET Framework は 15 年前に初めてリリースされました。そのときは、Windows デスクトップや Web アプリケーションのビルドに使用できる単一の .NET スタックを備えていました。その後、iOS や Android 向けモバイル アプリや macOS のデスクトップ アプリケーションのビルドに使用できる Xamarin など、他の .NET 実装が登場します (図 1 参照)。

.NET の状況

図 1 .NET の状況

.NET Core と .NET Standard の位置付けは、以下のとおりです。

  • .NET Core: 最新の .NET 実装です。オープン ソースで、複数の OS 向けに利用できます。.NET Core により、クロスプラットフォーム コンソール アプリケーション、ASP.NET Core Web アプリケーション、およびクラウド サービスをビルドできます。
  • .NET Standard: すべての .NET 実装が実装する必要のある基本 API のセットです。この API を基本クラス ライブラリ (BCL) と呼びます。.NET Standard をターゲットにすることで、どの .NET 実装やどの OS で実行されても、すべての .NET アプリケーションが共有できるライブラリをビルドできます。

.NET Core の概要

.NET Core は、クロスプラットフォーム対応の完全なオープン ソースの新しい .NET 実装です。この実装は、.NET Framework と Silverlight から分岐したものです。自己完結型の XCOPY 配置を有効にすることで、モバイル ワークロードやサーバー ワークロード向けに最適化されています。

.NET Core をより身近に感じられるように、.NET Core の開発について詳しく見てみましょう。併せて、新しいコマンドライン ベースのツールについても調べます。.NET Core 開発には Visual Studio 2017 を使用することもできますが、マガジンの読者なら Visual Studio には精通しているはずなので、ここでは新しいエクスペリエンスに注目します。

.NET が作成された当初は、Windows での迅速なアプリケーション開発に重点を置いて、最適化されていました。つまり、.NET 開発と Visual Studio は事実上切り離せない関係でした。そして、確実に言えるのは、Visual Studio を使用した開発は洗練されているということです。生産性が非常に高く、デバッガーはこれまで使用した中でも秀逸です。

ただし、Visual Studio の使用が、常に最適な選択というわけではありません。C# を習得するために少し .NET に触れてみたいだけなら、大容量の IDE をダウンロードしてインストールする必要はありません。また、SSH 経由で Linux コンピューターにアクセスしている場合は、単純に IDE は選択肢になりません。コマンドライン インターフェイス (CLI) を使いたいだけという場合もあります。

このような理由から、.NET Core CLI という優れた CLI が生まれました。.NET Core CLI のメイン ドライバーは「dotnet」と呼ばれます。 事実上、プロジェクトの作成、ビルド、テスト、パッケージ化という開発のすべての側面にこの dotnet を使用できます。次の例を見てみましょう。

手始めに、Hello World コンソール アプリケーションを作成して実行します (ここでは、Windows で PowerShell を使用していますが、macOS や Linux で Bash を使用しても同じように機能します)。

$ dotnet new console -o hello
$ cd hello
$ dotnet run
Hello World!

「dotnet new」コマンドは、Visual Studio で [ファイル] メニューの [新しいプロジェクト] を選択した場合と同じ動作をする CLI です。さまざまな種類のプロジェクトを作成できます。「dotnet new」と入力して、インストール済みのさまざまなテンプレートを確認します

ここでは、ロジックの一部をクラス ライブラリに取り出します。そのためには、hello プロジェクトと一緒にクラス ライブラリ プロジェクトを作成します。

$ cd ..
$ dotnet new library -o logic
$ cd logic

ライブラリにカプセル化するロジックは、Hello World メッセージを作成する部分です。そのため、Class1.cs のコンテンツを以下のコードに変更します。

namespace logic
{
  public static class HelloWorld
  {
      public static string GetMessage(string name) => $"Hello {name}!";
  }
}

ここで、Class1.cs の名前を HelloWorld.cs に変更します。

$ mv Class1.cs HelloWorld.cs

この変更のために、プロジェクト ファイルを更新する必要はありません。.NET Core で使用される新しいプロジェクト ファイルには、単純にプロジェクトのディレクトリのソース ファイルがすべてインクルードされます。これにより、ファイルの追加、削除、および名前変更によって、プロジェクトを変更する必要はなくなります。その結果、コマンド ラインからのファイル操作がより円滑になります。

HelloWorld クラスを使用するには、今回のロジック ライブラリを参照するように hello アプリを更新する必要があります。これを行うには、プロジェクト ファイルを編集するか、「dotnet add reference」コマンドを使用します。

$ cd ../hello
$ dotnet add reference ../logic/logic.csproj

ここで HelloWorld クラスを使用するように Program.cs ファイルを更新します (図 2 参照)。

図 2 HelloWorld クラスを使用するように Program.cs ファイルを更新

using System;
using logic;
namespace hello
{
class Program
{
static void Main(string[] args)
{
Console.Write("What's your name: ");
var name = Console.ReadLine();
var message = HelloWorld.GetMessage(name);
Console.WriteLine(message);
}
}
}

アプリをビルドして実行するには、「dotnet run」と入力するだけです。

$ dotnet run
What's your name: Immo
Hello Immo!

また、コマンド ラインからテストを作成することもできます。CLI は、MSTest と人気の xUnit フレームワークをサポートします。ここでは xUnit を使用します。

$ cd ..
$ dotnet new xunit -o tests
$ cd tests
$ dotnet add reference ../logic/logic.csproj

UnitTest1.cs を変更して、テストを追加します (図 3 参照)。

図 3 UnitTest1.cs のコンテンツを変更してテストを追加

using System;
using Xunit;
using logic;
namespace tests
{
public class UnitTest1
{
[Fact]
public void Test1()
{
var expectedMessage = "Hello Immo!";
var actualMessage = HelloWorld.GetMessage("Immo");
Assert.Equal(expectedMessage, actualMessage);
}
}
}

これで、「dotnet test」を呼び出して、テストを実行できるようになります。

$ dotnet test
Total tests: 1. Passed: 1. Failed: 0. Skipped: 0.
Test Run Successful.

少し面白くするために、簡単な ASP.NET Core Web サイトを作成してみましょう。

$ cd ..
$ dotnet new web -o web
$ cd web
$ dotnet add reference ../logic/logic.csproj

次のように Startup.cs ファイルを編集して、HelloWorld クラスを使用するように app.Run の呼び出しを変更します。

app.Run(async (context) =>
{
  var name = Environment.UserName;
  var message = logic.HelloWorld.GetMessage(name);
  await context.Response.WriteAsync(message);
});

開発 Web サーバーを起動するには、再度「dotnet run」を使用するだけです。

$ dotnet run
Hosting environment: Production
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.

表示された http://localhost:5000 という URL を参照します。

この時点で、プロジェクトの構造は図 4 のようになります。

図 4 作成したプロジェクトの構造

$ tree /f
│
├───hello
│ hello.csproj
│ Program.cs
│
├───logic
│ HelloWorld.cs
│ logic.csproj
│
├───tests
│ tests.csproj
│ UnitTest1.cs
│
└───web
Program.cs
Startup.cs
web.csproj

Visual Studio を使用してより簡単にファイルを編集するため、次のようにソリューション ファイルも作成して、このソリューションにすべてのプロジェクトを追加します。

$ cd ..
$ dotnet new sln -n HelloWorld
$ ls -fi *.csproj -rec | % { dotnet sln add $_.FullName }

ご覧のように、.NET Core CLI は強力で、他の分野の開発者にも分かりやすい簡単なエクスペリエンスになっています。また、Windows で PowerShell を使って dotnet を使用していても、Linux や macOS で作業するのとエクスペリエンスが非常によく似ているのがわかります。

.NET Core のもう 1 つの大きなメリットは、自己完結型の配置をサポートすることです。.NET Core ランタイムの独自のコピーを用意するような方法で、Docker を使用してアプリケーションをコンテナー化することができます。これにより、異なる .NET Core バージョンを使用している同じのコンピューターで、相互に干渉しないで異なるアプリケーションを実行できるようになります。.NET Core はオープン ソースなので、夜間ビルドや自身で変更またはビルドしたバージョンをインクルードして、自身が加えた変更を含めることができます (この点については、今回は扱いません)。

.NET Standard の概要

最新のエクスペリエンスをビルドしている場合、アプリケーションが複数のフォーム ファクターに及ぶことがよくあるため、必然的に .NET 実装も複数になります。最近の利用者の多くは、スマートフォンから Web アプリを使用し、クラウドベースのバックエンドでデータを共有できることを望んでいます。ノート PC を使用しているときは、Web サイト経由でアクセスしたいとも考えます。独自のインフラストラクチャでは、社内スタッフがシステムを管理できるように、コマンドライン ツールや、できればデスクトップ アプリも使用したいと考えるでしょう。このような場合に、各種 .NET 実装がどのように機能するかを確認します (図 5 を参照)。

図 5 .NET 実装の説明

  OS オープン ソース 用途
.NET Framework Windows いいえ Windows デスクトップ アプリケーションや、IIS で実行する ASP.NET Web アプリケーションのビルドに使用
.NET Core Windows、Linux、macOS はい クロスプラットフォーム コンソール アプリケーション、ASP.NET Core Web アプリケーション、およびクラウド サービスのビルドに使用
Xamarin iOS、Android、macOS はい iOS と Android 向けモバイル アプリケーションおよび macOS 向けデスクトップ アプリケーションのビルドに使用
.NET Standard N/A はい .NET Framework、.NET Core、Xamarin など、すべての .NET 実装から参照できるライブラリのビルドに使用

このような環境では、コードの共有が大きな課題になります。API が使用される場所を把握し、共有コンポーネントでは、使用しているすべての .NET 実装で利用可能な API のみを使うようにします。

このような場合は .NET Standard を使用します。.NET Standard は仕様です。各 .NET Standard のバージョンは、そのバージョンに準拠するようにどの .NET 実装でも必ず提供している API セットを定義します。この API セットは単なるライブラリで、アプリケーションはビルドできませんが、その点を除けば、もう 1 つの .NET スタックと考えられます。これは、あらゆる場所から参照するライブラリとして使用できる .NET 実装です。

おそらく、.NET Standard でサポートされる API を知りたいでしょう。.NET Framework に詳しい方は、前述の BCL にも詳しいはずです。BCL は、UI フレームワークとアプリケーション モデルから独立した基本の API セットです。これには、基本型、ファイル入出力、ネットワーク、リフレクション、シリアル化などが含まれています。

すべての .NET スタックは、.NET Standard のいずれかのバージョンを実装します。基本的なルールとして、通常は、新しいバージョンの .NET 実装を作成するときは、その時点で利用可能な最新バージョンの .NET Standard を実装します。

HTML やブラウザーの関係によく似ています。HTML 仕様 が .NET Standard で、各種のブラウザーが .NET Framework、.NET Core 、Xamarin などの .NET 実装と考えます。

ここからは .NET Standard の使い方について見ていきます。実際のところ、既にご存知です。ロジック クラス ライブラリの作成を思い出してください。 プロジェクト ファイルを詳しく見ていきます。

$ cd logic
$ cat logic.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

</Project>

上記のスクリプトと "hello" コンソール アプリケーションのプロジェクト ファイルを比べてみましょう。

$ cd ..\hello
$ cat hello.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\logic\logic.csproj" />
  </ItemGroup>

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

</Project>

ご覧のように、ロジック ライブラリでは TargetFramework の値が netstandard2.0、コンソール アプリケーションでは値が netcoreapp2.0 になっています。TargetFramework プロパティは、ターゲットにする .NET 実装をターゲットを示します。つまり、コンソール アプリケーションは .NET Core 2.0 をターゲットにし、ライブラリは .NET Standard 2.0 をターゲットにしています。このロジック ライブラリは、.NET Core アプリケーションからだけでなく、.NET Framework や Xamarin 向けにビルドされたアプリケーションからも参照できます。

残念ながら、現在利用可能なほとんどのライブラリでは、まだ .NET Standard をターゲットにしていません。ほとんどのライブラリは、.NET Framework をターゲットにしています。もちろん、すべてのライブラリが .NET Standard をターゲットにすることはありません (また、そうある必要もありません)。たとえば、Windows Presentation Foundation (WPF) コントロールを含むライブラリでは、UI が .Net Standard に含まれていないため、.NET Framework をターゲットにする必要があります。ただし、多くの汎用ライブラリでは、単純に作成時点で .NET Standard がまだ存在しなかったために .NET Framework をターゲットにしているだけです。

.NET Standard 2.0 では API のセットが十分大きいため、ほぼすべての汎用ライブラリは .NET Standard をターゲットにすることができます。結果的に、今日 NuGet に存在する全ライブラリの 70 パーセントは、.NET Standard に含まれる API だけを使用するようになっています。.NET Standard と互換性があることを明示しているライブラリはまだ一部にすぎません。

開発者が利用できない状況を防ぐため、互換性モードが追加されています。ターゲット フレームワークのライブラリも、.NET Standard のライブラリも提供していない NuGet パッケージをインストールすると、NuGet は .NET Framework への切り替えを試します。つまり、一見すると .NET Standard をターゲットにしているかのように、.NET Framework ライブラリを参照します。

次に例を示します。この例では、2007 年に作成され、よく使われているコレクション ライブラリ (PowerCollections) を使用します。しばらく更新していなかったため、まだ .NET Framework 2.0 をターゲットにしています。NuGet から hello アプリケーションにこのライブラリをインストールします。

$ dotnet add package Huitian.PowerCollections

このライブラリでは、順序を保証しないバッグなど、BCL では提供していない追加のコレクション タイプを提供しています。これを活用するように hello アプリケーションを変更します (図 6 参照)。

図 6 PowerCollections を使用するサンプル アプリケーション

using System;
using Wintellect.PowerCollections;
namespace hello
{
class Program
{
static void Main(string[] args)
{
var data = new Bag<int>() { 1, 2, 3 };
foreach (var element in data)
Console.WriteLine(element);
}
}
}

プログラムを実行すると、次のように表示されます。

$ dotnet run
hello.csproj : warning NU1701: Package 'Huitian.PowerCollections 1.0.0' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETCoreApp,Version=v2.0'. This may cause compatibility problems.
1
3
2

何が起きているのでしょう。 hello アプリケーションは .NET Core 2.0 をターゲットにします。.NET Core 2.0 は .NET Standard 2.0 を実装しているので、.NET Framework ライブラリを参照するための互換性モードも備えています。ただし、すべての .NET Framework ライブラリがすべての .NET 実装で機能するわけではありません。たとえば、Windows フォームや WPF API を使用する場合もあります。NuGet がこれを認識する方法はありません。そのため、開発者がこの状況に気付いて、これに起因する問題のトラブルシューティングに余計な時間がかからないように、NuGet は警告メッセージを表示します。

この警告は、ビルドのたびに表示されます。これにより、パッケージ インストール時の警告を単に見逃したり、警告があったことを忘れてしまった場合に生じる問題を回避しています。

もちろん、ビルドするたびに対応策がない警告を無視しなければならないのは、とても面倒です。そこで、アプリケーションの検証後にそのパッケージの警告を無効にすることを考えます。アプリケーションが正常に実行されている (作成したバッグのコンテンツを正しく出力した) ので、この警告が表示されないようにします。これを行うには、hello.csproj ファイルを編集して、NoWarn 属性をパッケージ参照に追加します。

<PackageReference Include="Huitian.PowerCollections" Version="1.0.0" 
  NoWarn="NU1701" />

これでアプリケーションを再実行しても、警告が表示されなくなります。互換性モードを使用する別のパッケージをインストールする必要が生じた場合、そのパッケージに対しても警告が表示されますが、やはり同じように対処できます。

また、新しいツールにより、クラス ライブラリ プロジェクトでは NuGet パッケージをビルドの一部として作成できます。これにより、お使いのライブラリを無制限 (nuget.org にプッシュ) または組織内限定 (Visual Studio Team Services または MyGet に独自のパッケージ フィードをプッシュ) で、さらに簡単に共有できるようになります。また、新しいプロジェクトでは、マルチターゲット機能をサポートしており、複数の .NET 実装に対応する単一のプロジェクトをビルドできます。つまり、条件付きコンパイル (#if) を使用して、特定の .NET 実装にライブラリを対応付けされます。また、プラットフォーム固有の API 向けの .NET Standard ラッパーもビルドできます。ただし、それらの機能については、今回は触れません。

まとめ

.NET Standard は、すべての .NET 実装が提供しなければならない API の仕様です。.NET ファミリに一貫性をもたらし、任意の .NET 実装から使用できるライブラリのビルドを可能にします。つまり、共有コンポーネントをビルドする場合の PCL の代わりになります。

.NET Core は、ASP.NET Core を使用してコンソール アプリケーション、Web アプリケーション、およびクラウド サービスをビルドするために最適化された .NET Standard の実装です。.NET Core の SDK は、Visual Studio 開発に加えて、完全なコマンドライン ベースの開発ワークフローをサポートする強力なツールを備えています。詳細については、aka.ms/netstandardfaq および aka.ms/netcore をご覧ください。


Immo Landwerth は、マイクロソフトのプログラム マネージャーとして .NET に携わっています。.NET Standard、BCL、およびAPI の設計に力を注いでいます。


この記事について MSDN マガジン フォーラムで議論する