従来のPCLと.NET Standard
ここで、従来からあるPCLと新しく登場した.NET Standardの違いについても解説しておきましょう。
従来のPCLも、.NET Standardも、.NET Framework APIのサブセット(部分集合)を定めた規格(仕様)です。そのサブセットの決め方に、次のような違いがあるのです。
端的にいうと、実装が先か/規格が先か、という違いです。
・ 従来からあるPCL規格:各プラットフォームの実装 ⇒ 共通するAPIを見つけ出して規格化
・ .NET Standard規格:共通に実装すべきAPIを規定 ⇒ それに従って各プラットフォームで実装
従来からあるPCLは、.NET Standardの登場にともなって「プロファイルベースのPCL」と呼ばれるようになりました。.NET Standard規格に従って作られたクラスライブラリは「.NET StandardベースのPCL」です(「.NET Standard~ポータブル クラス ライブラリとの比較」参照)。どちらも、作成されたバイナリファイルはPCL(Portable Class Library‐移植可能なライブラリ)だということなのですね。
プロファイルベースのPCL(従来のPCL)
プロファイルベースのPCLには、その種類が多いことと、一定の包含関係がないという問題点があります。例えばNuGetのドキュメント「Target frameworks」のPortable Class Librariesセクションには、「Profile2(portable-net40+win8+sl4+wp7)」から始まって40種類以上ものプロファイルが載っています。しかもプロファイル間に包含関係があるとは限らないのですから、どのプロファイルを選ぶべきか悩ましいことになります。
プロファイルの包含関係について、3つ(バージョン違いを含めると5つ)のプラットフォームだけに絞っていくつか例を考えてみます(次の図)。
- .NET Framework 4.5/4.5.1:「本家」の.NET Framework実装です。Windows FormsやWPFなどで使っているもの。以降は「.NET4.5/4.5.1」と略記します
- Windows Runtime for Windows 8.0/8.1:Windows 8.0/8.1用のストアアプリから利用できる.NET Framework実装です。以降は「WinRT8/8.1」と略記します
- Silverlight for Windows Phone 8.0:Windows Phone 8.0用のSilverlightアプリから利用できる.NET Framework実装です。以降は「SL8」と略記します
- Profile78:.NET4.5とWinRT8とSL8のすべてに共通して存在するAPIのセットです(次の図)。ここで考えている5つのプラットフォームすべてで利用できるPCLになりますが、その代わり使えるAPIの数は少なくなります。
- Profile49:.NET4.5とSL8に共通して存在するAPIのセットです(次の図)。WinRTに存在するAPIも入っていますが(薄い赤色)、WinRTからは利用できないAPIを含んでいるため(濃い赤色)、WinRTからは利用できないPCLになります。このProfile49は、上のProfile78を包含しています。
- Profile44:.NET4.5.1とWinRT8.1に共通して存在するAPIのセットです(次の図)。 .NET4.5/WinRT8.0/SL8からは利用できないPCLになります。このProfile44は、先のProfile78を包含していますが、上のProfile49とは(共通部分はあるものの)包含関係にはなっていません。
.NET StandardベースのPCL
.NET Standardも、.NET Frameworkのサブセットを定義する規格ですが、そのバージョンによって含まれるAPIはきれいな包含関係になっています(次の図)。本稿執筆時点では、その最大バージョンは2.0です。なお、.NET Standard 1.xは、実際には1.0から1.6までの7バージョンありますが、ここではいくつか省略しています。図中に付記した数字は、そのバージョンの.NET Standardに含まれるAPIの数です(「.NET Standard FAQ」による)。例えば.NET Standard 1.1には10,239個のAPIが規定されていますが、その中には.NET Standard 1.0の7,949個も含まれています。
「規格先行」(とはいうものの、実態としては1.4までは全プラットフォームの実装が先にありました)の.NET Standardは、「プラットフォームのバージョンX.Xで、.NET StandardのバージョンY.Yを実装しました」という形になります(次の表)。例えば、.NET Framework「本家」はバージョン4.6で.NET Standard 1.3のAPIを実装し、4.6.1になって.NET Standard 2.0を実装したという形です。また例えば、Windows Phone用のSilverlightは、8.0で.NET Standard 1.0を実装した形になっていますが、.NET Standard 1.1以降を実装しているSilverlightは出ていません。あるいは逆に、例えば.NET Standard 2.0規格のクラスライブラリを利用したければ、.NET「本家」は4.6.1以降、UWPは10.0.16299以降、Xamarin.iOSは10.14以降、そしてXamarin.Androidは8.0以降が必要になります(Windows PhoneとWindows Runtimeからは利用不可)。
このように.NET Standardのバージョンは、プロファイルベースのPCLよりずっと分かりやすいものになりました。なお、この表は抜粋です。完全なものは「.NET Standard」の.NET 実装のサポートセクションをご覧ください。
| 1.0 | 1.1 | 1.3 | 2.0 | |
|---|---|---|---|---|
| (.NET StandardのAPI数) | (7,949) | (10,239) | (13,122) | (32,638) |
| .NET Framework 「本家」 (+.NET Core 2 SDK) |
4.5 | 4.5 | 4.6 | 4.6.1 |
| Silverlight for Windows Phone | 8.0 | N/A | N/A | N/A |
| Windows Runtime for Windows 8.x | 8.0 | 8.0 | N/A | N/A |
| UWP | 10.0.12040 | 10.0.12040 | 10.0.12040 | 10.0.16299 |
| Xamarin.iOS | 10.0 | 10.0 | 10.0 | 10.14 |
| Xamarin.Android | 7.0 | 7.0 | 7.0 | 8.0 |
| .NET Core | 1.0 | 1.0 | 1.0 | 2.0 |
.NET Standardと.NET Core
.NET Standardと似た言葉として「.NET Core」というものがありますが、別のものです。
.NET Standardは規格ですが、.NET Coreは実装です。.NET Coreは、.NET Frameworkの「本家」とは独立した、クロスプラットフォーム対応(Windows/Mac OS/Linux)の.NET Framework実装です。現在のところ(.NET Core 2.0)、Windows FormsやWPFなどは含まれておらず、Webアプリ(ASP.NET Core)やUWPアプリ/コンソールアプリを作るのに使われます。
また、.NET StandardのAPIと.NET CoreのAPIは異なります。上の表に載せたように.NET Coreは.NET Standard規格のAPIを実装していますが、それだけでなく、.NET Coreには.NET StandardにないAPIも実装されているのです。例えば、次項で取り上げるSystem.Data.SqlClient名前空間は、.NET Core 2.0には実装されていますが、.NET Standard 2.0には定義されていません。あるいは、.NET Core 2.0のKeyValuePair<TKey,TValue>構造体にはDeconstructメソッドがありますが、.NET Standard 2.0のKeyValuePair<TKey,TValue>構造体にはありません。
.NET Core 2.0のKeyValuePair<TKey,TValue>.Deconstructメソッド
これがあると、Dictionary<TKey,TValue>コレクションをforeachループで回すときにタプルの形で受けられるので、とても便利です(下)。これを知ってしまうと、いったんKeyValuePairで受けてからループ内でキーと値を取り出すコードを書くのがどうにも無駄に思えてしかたがなくなります(上)。
// 従来の書き方しかできない
foreach (var kvp in dic)
{
var id = kvp.Key;
var name = kvp.Value;
// id(キー)とname(値)を使って何かする
}
// .NET Core 2.0では簡潔に書ける
foreach (var (id, name) in dic)
{
// id(キー)とname(値)を使って何かする
}
