Xamarin 日本語情報

Xamarin(ザマリン) の代理店だったエクセルソフト田淵のブログです。主に Xamarin に関するエントリーをアップしていきます。(なるべく正しい有益な情報を掲載していきたいと考えていますが、このブログのエントリーは所属組織の公式見解ではありませんのでご注意ください)

Xamarin で使う .NET Standard ライブラリ/PCL(Portable Class Library)/Shared Project について

こんにちは。エクセルソフトの田淵です。

これは Xamarin その1 Advent Calendar 2017 - Qiita の 2日目のエントリーです。今年も 2日目を担当させていただきます。よろしくお願いいたします!

さて、Visual Studio 2017 update 4 がリリースされ、.NET Standard 2.0 に関する情報もだいぶ出てきましたね。次の 15.5 では Xamarin.Forms のプロジェクトを .NET Standard で作成できることが発表されています。

blog.xamarin.com

f:id:ytabuchi:20171116200400p:plain:w600

※写真は Xamarin のブログから

だがちょっと待ってほしい。そのまま .NET Standard で Xamarin のプロジェクトを作って良いのか?今までの PCL とどう違うのか?Shared project とどっちを使うべきか?など気になりませんか?そこらへんを中心に本エントリーで纏めていきたいと思います。

.NET Standard ライブラリ/PCL(Portable Class Library)/Shared Project の違い

まずは 3つの共有化プロジェクトの違いについてです。

.NET Standard と PCL はクラスライブラリなので、成果物として DLL が作成されます。.NET Standard は PCL の上位互換のようなものだと考えてください。以下の記事を読めばイメージがつかめると思います。

一方、Shared Project は、ファイルリンクです。iOSAndroid/UWP のプロジェクトをビルドする際に、それぞれのプロジェクトから参照されます。そのため、iOSAndroid/UWP で使われている Xamarin.iOS/Xamarin.Android/.NET Core Framework で実装されている BCL(Base Class Library) の API を全て使えるのが特長です。ただし、当然 DLL や NuGet 化はできないので、別のソリューションで使用したい場合は、ファイルをコピーする必要があります。

PCL とは

最初の記事の「ポータブル クラス ライブラリとの比較」の章にあるように、PCL は非常に制限が多い仕組みです。対応プラットフォームの組み合わせにより多くのプロファイルが用意されており、プロファイルが対応するプラットフォームの BCL に共通で含まれるクラスしか使えません。

f:id:ytabuchi:20171124114259p:plain:w750

このスクリーンショット .NET Framework v4.5 の PCL C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\v4.5\Profile のうち Xamarin に対応している Profile 75 以外の各プロファイルと、v4.6 の PCL C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\v4.6\Profile のうち Xamarin に対応している Profile 44 と Profile 151 のフォルダ内にある DLL を列挙してその量を比べたものです。そのまま使えるクラスの数の比較にはなりませんが、名前空間がかなり少ないのが分かると思います。

例えば System.Security.Cryptography 名前空間SHA256CryptoServiceProvider クラスは System.Security.Cryptography.Csp.dll がそもそも PCL には存在しないため、SHA256 のハッシュを計算しようと思ったら、PCL に対応した PCLCrypto など、同等の機能を持つライブラリを探してくるか、自力で計算する必要があります。

プロファイルについてですが、この記事 Target Frameworks References for NuGet | Microsoft Docs の「Portable Class Libraries」の章にあるように、PCL のプロファイルはすごくたくさんあります。

ちなみにみんな大好き JSON.NET

f:id:ytabuchi:20171122001951p:plain:w450

こんな感じで、PCL は Profile 259 と Profile 328 を用意していますね。.NET Standard は 1.0 と 1.3 が用意されています。

Xamarin で使おうと思って、PCL のライブラリをインストールしたら、「Profile 259 用の実装がありません」というようなエラーでインストールできなかった。ということがみなさんもあると思いますが、それは提供側のライブラリと利用側のプロジェクトの PCL のプロファイルがマッチしなかったからです。つまり、多くの人に使ってもらえるライブラリを作成するには、ライブラリ製作者は対応させたいプロファイル用に沢山の NuGet パッケージを作成しなければなりません。

Xamarin 用のプロファイルとしては、用意されているクラスが多い順に 7 ≒ 44 > 111 ≒ 151 ≒ 49 ≒ 78 > 259 なので、PCL の場合は Profile 259 でライブラリを作ればどのプロファイルでも読み込めるはずです。

せっかくなので実際に各プロファイルのライブラリを作って調べたところ以下の結果になりました。ネストしている Profile が読み込めるプロファイルです。

  • Profile 7
    • Profile 78
    • Profile 111
    • Profile 259
  • Profile 44
    • Profile 7
    • Profile 78
    • Profile 111
    • Profile 259
  • Profile 111
    • Profile 259
  • Profile 151
    • Profile 111
    • Profile 259
  • Profile 49
    • Profile 78
    • Profile 259
  • Profile 78
    • Profile 259
  • Profile 259

なお、Xamarin.iOS/Xamarin.Android の実装を PCL から利用できるようにする Plugin for Xamarin のライブラリは Bait and Switch という仕組みを使って作られていますが、ここでは割愛します。Plugin の仕組みや作り方を知りたいという方は、以下のリンクをご覧ください。

www.buildinsider.net

ticktack.hatenablog.jp

github.com

PCL プロファイルの変更の仕方

余談ですが、まだ PCL を使用している方が多いと思いますので、プロファイルの変更方法を記載しておきます。

Visual Studio 2017 の場合

プロジェクトの NuGet パッケージをすべて削除して、プロジェクトを右クリックして「プロジェクトのアンロード」を行い、再度プロジェクトを右クリックして「編集 XXX.csproj」をクリックします。csprojXML が表示されるので、TargetFrameworkProfileTargetFrameworkVersion を適切に変更します。

<TargetFrameworkProfile>Profile44</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>

44 と 151 は TargetFrameworkVersionv4.6 を。それ以外は v4.5 を記入します。その後、「プロジェクトの再読み込み」を行います。

以前は以下のようにプロジェクトのプロパティから「ライブラリ>ターゲット>変更」で GUI で選択できましたが、Windows Phone などの選択肢がなくなってしまったので GUI からは適切なプロファイルは選べなくなりました。

f:id:ytabuchi:20171201112459p:plain:w600

Visual Studio for Mac の場合

プロジェクトを右クリックして「オプション」を選択します。

f:id:ytabuchi:20171124115833p:plain:w600

「変更」ボタンをクリックして、以下のダイアログで適切なプロファイルを選択します。

f:id:ytabuchi:20171124115950p:plain:w450

変更後、読み込み済みの NuGet パッケージが指定したプロファイルに合わせて自動的に再ダウンロード、適用されます。(選択したプロファイルにマッチしない場合は、エラーが表示されます。)

PCL まとめ

利用者側は Xamarin.Forms の Forms 部分のプロジェクトや Xamarin ネイティブの共通化の PCL プロジェクトは「Windows Phone 8.1 に対応しなければならない」という辛い条件を除き、Profile 7 または 44 が良い。

PCL ライブラリ製作者側は、可能であれば Profile 259 を用意しておくと、Xamarin でも使ってもらえる可能性が増え、読み込めないという状況を減らすことが出来る。

.NET Standard とは

.NET Standard は規格に合わせて、.NET Framework、.NET Core、Mono での BCL 実装を行おうという試み(多分)で、対象のプラットフォームを選ぶのではなく、.NET Standard のバージョンに合わせて、各 BCL が実装をしている。という理解です(間違ってるかもなので教えてください)。使える API が PCL と比べてかなり多いです。

イメージは MS のエバの方が良く使っていたものです。.NET Framework、.NET Core、Xamarin(Mono)で別々だった BCL が一緒になるイメージです。

f:id:ytabuchi:20171125141043p:plain:w600

f:id:ytabuchi:20171125141225p:plain:w600

.NET Standard ライブラリは OSS として開発されており、以下の GitHub で公開されています。

github.com

使いたい API .NET Standard でサポートされているか?されているとするとどのバージョンか?などは .NET API ブラウザー | Microsoft Docs で確認することができます。先ほどの SHA256CryptoServiceProvider .NET Standard 2.0 からの対応ということがわかります。

f:id:ytabuchi:20171117130726p:plain:w450

.NET Standard 1.6 には未実装の図

f:id:ytabuchi:20171117130746p:plain:w600

.NET Standard 2.0 に実装されている図

PCL の章でもリンクを貼りましたが、.NET Standard | Microsoft Docs のページに .NET Standard のバージョンと対応するプラットフォームの一覧が記載されています。

f:id:ytabuchi:20171116153451p:plain:w600

PCL で鬼門だった Sliverlight、Windows Phone、Windows 8 などの対応をバッサリ切って、.NET Framework、.NET Core、Mono、UWP に絞って使える API をどんどん増やしていく。という感じに見えます。

.NET Standard の各バージョンでどんな API が増えていったのか?も以下に纏められています。

github.com

仕様、実装 、ライブラリなどがすべて .NET Standard という名称になっているため混乱しますが、ここでは .NET Standard ライブラリを中心に書いていきます。また、最初にもリンクした藤原さんの以下の記事も参考にしてください。

yfakariya.blogspot.jp

先日福岡でやった JXUG で発表した少し古い資料ですが、これらのことをざっくり纏めてありますので併せてご覧ください。

従来の .NET Framework では、Target のフレームワークがインストールされていないと、「.NET Framework 4.6 が必要です。ランタイムをインストールしますか?」というようなダイアログとともに、システムの .NET Framework を更新する必要がありました。.NET Core では従来のモノリシックな DLL から細分化された DLL を参照して、アプリケーションの配布に含めることができるように仕様が変わり、特定のバージョンの .NET Core アプリをシステムの .NET Framework に依存せずに利用できるようになりました。Xamarin.Forms で作成することができる UWP は .NET Core で動作するため、Windows 10 の各バージョンに合わせた .NET Standard のライブラリを使用しています。

このあたりの話は Microsoft .NET - .NET とユニバーサル Windows プラットフォーム開発 が参考になるかと思います。

UWP 用の .NET Core の実装は Microsoft.NETCore.UniversalWindowsPlatform で提供されており(多分)、最新の Fall Creators Update に対応した Microsoft.NETCore.UniversalWindowsPlatform のバージョンは 6.0.x です。下位互換があるので、UWP プロジェクトの Microsoft.NETCore.UniversalWindowsPlatform は最新にしても大丈夫なようです。

www.nuget.org

UWP の .NET Standard 対応

blogs.msdn.microsoft.com

2017/10/10 に UWP も .NET Standard 2.0 に対応しました!UWP で .NET Standard 2.0 を使用したい場合は、UWP プロジェクトのターゲットを Fall Creators Update にすれば OK です。(Windows 10 対応!Fall Creators Update 以降です!っていうと、2017年12月時点ではかなり条件が厳しくなりますが、Windows Update を遅らせるのは長くて 1年なはずので来年には OK!(なにかがおかしい…))Windows が Fall Creators Update だけってのは厳しいという場合は、「最少バージョン」を November Update(10586)や Anniversary Update(14393)などの古いバージョンにしておけば良いでしょう。

f:id:ytabuchi:20171124155036p:plain:w450

ただし、その場合、読み込める .NET Standard のライブラリは 1.4 までとなります。Xamarin.Forms のプロジェクトを 2.0 にしてしまうと、「プロジェクト 'XXXX.csproj' は、'.NETStandard,Version=v2.0' を対象としています。'UAP,Version=v10.0.10586' を対象とするプロジェクトは、これを参照できません。」というようなエラーで参照ができません。

.NET Standard 2.0 版の Xamarin.Forms プロジェクトの作り方

blogs.msdn.microsoft.com

こちらのエントリーにあるように、.NET Standard 2.0 正式リリースで大きな変更点は「.NET Framework compatibility mode」だと思います。具体的には .NET Standard 2.0 のプロジェクトでは、何も設定しなくても PCL のプロジェクトを読み込むことができます!

一般的には、PCL のライブラリは .NET Standard 2.0 では概ね使えるはずですが完全に動作が保証されているわけではありません。そのため、次のようなワーニングが表示されます。

パッケージ 'ytPCL259Lib 1.0.0-pre' はプロジェクトのターゲット フレームワーク '.NETStandard,Version=v2.0' ではなく '.NETFramework,Version=v4.6.1' を使用して復元されました。このパッケージは、使用しているプロジェクトとの完全な互換性がない可能性があります。

f:id:ytabuchi:20171122015452p:plain:w750

このようなエラーがビルドの度に表示されます。動作に問題ないと判断した場合、上記エントリーにもありますが、csproj の Package Reference の PCL ライブラリを読み込んでいる箇所で NoWarn="NU1701" を指定することでワーニングを出ないようにできます。

では、実際に .NET Standard 2.0 の Xamarin.Forms プロジェクトを作成してみましょう。2017年12月現在の Visual Studio 2017 Update 4 では、プロジェクトテンプレートが用意されていないため、手動で作成する必要があります。私のおすすめの方法は以下の通りです。

Visual Studio 2017 の場合:

PCL で Xamarin.Forms プロジェクトを作成します。次に Forms 部分のプロジェクト(PCL)の Xamarin.Forms の NuGet パッケージを削除します。更に AssemblyInfo.cs も削除しておきます。

その後、PCL プロジェクトをアンロードし、csproj をテキストとして開きます。

長々と設定が記載されていますが、すべてを以下で置き換えます。Xamarin.Forms のバージョンは最新版で置き換えてください。

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

  <ItemGroup>
    <PackageReference Include="Xamarin.Forms" Version="2.5.0.91635" />
  </ItemGroup>
  
  <ItemGroup>
    <Folder Include="Properties\" />
  </ItemGroup>
</Project>

その後、プロジェクトを再読み込みします。

無事、.NET Standard 2.0 版の Xamarin.Forms のプロジェクトになったはずです。

Xamarin.Forms 2.4 未満を使用する場合は、XAML の自動読み込みが設定されていないため、

<ItemGroup>
  <Compile Update="**\*.xaml.cs" DependentUpon="%(Filename)" />
  <EmbeddedResource Include="**\*.xaml" SubType="Designer" Generator="MSBuild:UpdateDesignTimeXaml" />
</ItemGroup>

が必要です。詳しくは @nuits_jp さんの以下の記事が参考になるかと思います。

www.nuits.jp

Visual Studio for Mac の場合:

@y_chu5 さんの以下の記事そのままで大丈夫です(^^)

qiita.com

Visual Studio for Mac の場合は、NuGet のパッケージがインストールされていても、プロジェクトの設定が変わったらそれに合わせた NuGet パッケージを自動で再インストールしてくれるので、Xamarin.Forms のパッケージは削除しなくても大丈夫です。AssemblyInfo.cs は削除しておきましょう。こちらも Xamarin.Forms 2.4 未満の場合は、上記XAML の設定を追加してください。

.NET Standard 1.X 版の Xamarin.Forms プロジェクトの作り方

基本は上記と同様なのですが、1.6 までの .NET Standard のプロジェクトファイルには「.NET Framework compatibility mode」がないため、PCL しか対応していないライブラリを使いたい場合は手動で PCL 259 などに対応していることを明記する必要があります。

csproj

<PackageTargetFallback>portable-net45+win8+wpa81+wp8</PackageTargetFallback>

を追加してください。上記は PCL 259 を読み込み可能にしています。こんな感じ

.NET Standard ライブラリまとめ

.NET Standard の利用者側の場合、利用者側はいくつかの選択肢があります。

  • UWP 対応が必要な場合:
    .NET Standard 2.0 を使用する場合、UWP は Fall Creators Update のみとなるため現実的ではありません。そのため、.NET Standard 1.4 を使用します。(または PCL か Shared)
  • WPF 対応が必要な場合:
    なるべく新しいバージョンの .NET Framework を使いましょう。.NET Framework 4.6.1 以上で .NET Standard 2.0 に対応できます。4.6 だと .NET Standard 1.3、4.5.1 で 1.2、4.5 で 1.1 までの対応です。
  • 特に制限なく Xamarin.iOS/Xamarin.Android アプリの開発だけをする場合:
    .NET Standard にするなら 2.0 が良いです。その場合 PCL のライブラリは上記にあるように読み込めはしますが注意して使わないといけません。

.NET Standard ライブラリ提供側の場合も同様にいくつかの選択肢があります。

  • UWP でも使ってほしい場合:
    現時点では 1.4 以下で作るのが良いでしょう。
  • Xamarin.iOS/Xamarin.Android だけで良い場合:
    .NET Standard 2.0 でも良いでしょう。
  • .NET Standard 対応と PCL 対応を同時してしておきたい場合:
    調査中…

どうするのが良いのかな?と Twitter で話していたら少し話が盛り上がったので @amay077 さんが以下に纏めてくださいました。

togetter.com

今、Xamarin Plugin を試しに作ってみているので、良い案が出たらまたエントリー書こうと思います。

Shared Project とは

Shared Project であればファイルがビルド時にコピーされるだけなので、PCL や .NET Standard の制限は一切なく、Xamarin.iOS/Xamarin.Android で実装された BCL の API を全て使えます。

個人的には、PCL、.NET Standard どのようなライブラリであってもそのまま使えるため、普段使用しているライブラリが .NET Standard に対応するまでは、一番オススメする共通化の手法です。

Plugin の仕組み

なぜ両方のライブラリが使用できるか?というのは Plugin を作成すると良く分かります。以下、まだ作成途中ですがハッシュ値を計算するサンプルの Plugin です。(UWP 対応で苦戦中)

github.com

Plugin の詳しい作成方法は、@ticktackmobile さんの以下のエントリーを参照してください。Plugin 用のプロジェクトテンプレートは Visual Studio 2015 用で 2017 には対応していない旨のワーニングが出ますが問題なくインストールして使えています。

ticktack.hatenablog.jp

で、Plugin のプロジェクトを作ると

  • Plugin.プロジェクト名 (PCL)
  • Plugin.プロジェクト名.Abstractions (PCL)
  • Plugin.プロジェクト名.プラットフォーム名

のプロジェクトが作成されます。

Abstractions プロジェクトにインターフェースや enum を作成します。

サンプルでは

public interface ICalculateHash
{
    string CalculateHash(string str, HashType type);
}

public enum HashType
{
    md5,
    sha1,
    sha256,
    sha512
}

を用意しました。で、この CalculateHash が動作するように、iOSAndroid に実装を行っています。

public string CalculateHash(string str, HashType type)
{
    var data = Encoding.UTF8.GetBytes(str);

    switch (type)
    {
        case HashType.md5:
            return CalclateMD5(data);
        case HashType.sha1:
            return CalclateSha1(data);
        case HashType.sha256:
            return CalclateSha256(data);
        case HashType.sha512:
            return CalclateSha512(data);
        default:
            return null;
    }
}

private string CalclateMD5(byte[] data)
{
    // MD5のプロバイダーを作成
    var md5 = new MD5CryptoServiceProvider();
    // ハッシュ値を計算
    var hashedBytes = md5.ComputeHash(data);
    // リソース開放
    md5.Clear();
    // ハッシュ値をテキストに戻す
    return GetTextFromHash(hashedBytes);
}

...略

iOSAndroid 共に System.Security.Cryptography が用意されているので、同じコードで実装できます。それが例えば iOSAndroidAPI を使用して別々のコードで実装したとしても、メソッドの引数と戻り値の型は同じです。

で、これを NuGet パッケージにすると、iOSAndroid/UWP 用の DLL と共通プロジェクト用の DLL が出来ます。PCL/.NET Standard のプロジェクトから使う場合は Bait and Switch の仕組みを使い、共通プロジェクト用 DLL のインターフェース経由で同じメソッドを叩く感じですが、Shared Project では経由せずにそのまま iOSAndroid/UWP 用の DLL に実装されている実メソッドを叩いているというだけです。

なので、なんとなく Xamarin.Forms 用というイメージがある Plugin ですが、もちろん Xamarin ネイティブでも使えます。

Shared Project まとめ

個人的には .NET Standard への対応が落ち着くまでは最良の選択肢。迷ったらまずは Shared でやってみると幸せかも。

全体のまとめ

共通化ってやっぱり銀の弾丸ではないんですよね…😭

皆さんの案件に応じて、Xamarin.Forms なのか Xamarin ネイティブなのか?を含め、最適な手法を選択してください👍👏

色々用意したプロジェクト群を GitHub にアップしてあります。興味ある方はこれとこれが参照できるのかーとか試してみてください。

github.com

【余談】package.config と Package Reference

余談ですが、今までは、NuGet パッケージの管理に package.config ファイルを使用していました。Visual Studio 2017 に含まれる NuGet 3.x からは Package Reference という方式が採用され、よりパッケージの依存関係が簡単に解決できるようになっています。

Visual Studio 2017 のオプションで「NuGet パッケージマネージャー>パッケージの管理」が「Package Reference」になっていれば自動的に Package Reference 形式が使用されます。

f:id:ytabuchi:20171124193558p:plain:w600

具体的には、csproj ファイル内に <PackageReference> が追加され、package.config の参照が削除されています。

Package Reference については次の記事が詳しいです。

docs.microsoft.com

docs.microsoft.com

特に 2つ目の「Dependency resolution rules」は目を通しておくと良いでしょう。Package Reference に記載されているバージョンと NuGet に実際に存在するバージョンが違っていた場合、2つのパッケージで同じパッケージが依存関係になっている場合などにどのバージョンが使われるか?のルールが記載されています。

package.config では、依存関係まですべてファイルに記載する必要がありましたので、複数のパッケージをインストールすると、依存関係がバッティングすることがありました。特に Xamarin.Android のサポートライブラリは各種ライブラリから使用されるため、バージョンを揃えるのが難しかったと思います。では、Package Reference でこの問題が解決するか?というと、結論からいうとしません。

ytabuchi.hatenablog.com

このエントリーでも記載しましたが、それぞれの依存関係で別バージョンの別のパッケージを参照していて、それらのコンフリクトなので NuGet から分からないからだと思います。

逆に package.config であれば上記に書いたようにすべてをアップデートする解決策が取れますが、Package Reference では依存関係は隠蔽されてしまっているため、個別に最新のバージョンにアップデートができません。

f:id:ytabuchi:20171124182907p:plain:w300

この問題を解決するには、すべてのサポートライブラリを同じバージョンで個別にインストールする。です。手動で csproj を修正した方が早そうですねw

package.config では以下のような記載でした。

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Xamarin.Android.Arch.Core.Common" version="1.0.0" targetFramework="monoandroid80" />
  <package id="Xamarin.Android.Arch.Lifecycle.Common" version="1.0.1" targetFramework="monoandroid80" />
~略~
  <package id="Xamarin.Android.Support.Vector.Drawable" version="26.1.0.1" targetFramework="monoandroid80" />
  <package id="Xamarin.Forms" version="2.5.0.91635" targetFramework="monoandroid80" />
</packages>

これを Package Reference の書式に合うようにテキストエディタで次のように整形して、プロジェクトをアンロードし csproj を開き、最後の方に <ItemGroup> として追加してあげましょう。

<PackageReference Include="Xamarin.Android.Arch.Core.Common" version="1.0.0" />
<PackageReference Include="Xamarin.Android.Arch.Lifecycle.Common" version="1.0.1" />
~略~
<PackageReference Include="Xamarin.Android.Support.Vector.Drawable" version="26.1.0.1" />
<PackageReference Include="Xamarin.Forms" version="2.5.0.91635" />

更に、別の <ItemGroup><None Include="packages.config" /> と package.config` への参照があるのでこれも削除します。その後、プロジェクトを再読み込みして、ビルドすると、Package Reference で指定したサポートライブラリが読み込まれます。

明日は

@Fumiya_Kume くんです。よろしくお願いいたします。

以上です。

エクセルソフト | ダウンロード | 学習用リソース | JXUG リンクページ | ブログ購読