.NET Core Framework

.NET Framework を使ってクロスプラットフォーム対応にする

Cheryl Simmons

多くの開発者は、ビジネス ロジックのコードを一度作成して、後からそれを再利用したいと考えます。それが可能であれば、複数のプラットフォームをターゲットにする場合、アプリをそれぞれ個別にビルドするよりはるかに簡単になります。.NET Core (コンポーネント化したバージョンの Microsoft .NET Framework) に関する最近の発表と、マイクロソフトと Xamarin の密接なパートナーシップが意味するところは、.NET Core と互換性のあるポータブル クラス ライブラリ (PCL) を作成すれば、これまでにないほど簡単にクロスプラットフォーム対応が可能になるということです。

では、これまで開発してきた .NET Framework の既存のライブラリはどうなるでしょう。このようなライブラリをプラットフォーム間で互換性が保たれるようにし、PCL に変換するにはどれぐらいの作業が必要になるのでしょう。そこで、.NET Portability Analyzer の登場です。.NET Portability Analyzer は 2 つのシンプルなテクニックを使い、一部のプロジェクト ファイルに変更を加え、変換するプロセスが容易になるよう支援します。

.NET Portability Analyzer ツールは Visual Studio の拡張機能で、.NET Framework チームによって作成されました。このツールは、最新バージョンの Visual Studio のうち、拡張機能をサポートする任意のバージョンで使用できます。アセンブリまたはプロジェクト レベルで Portability Analyzer をポイントするだけで、ツールから要約、詳細レポート、および互換性を高めるために使用すべき推奨 API が提示されます。プロジェクトの場合は、ツールによってエラー メッセージが一覧され、変更が必要なコード行が示されます。ツールでは、マイクロソフトの主要プラットフォームに対する結果を提示しますが、構成を変更すれば Mono や Xamarin などの他のプラットフォームに対する結果も提示されます。

.NET Portability Analyzer には、API Portability Analyzer という関連コンソール アプリもあり、Portability Analyzer と同様の結果を生成します (aka.ms/w1vcle からダウンロード可能、英語)。今回は、Visual Studio の拡張機能の使用に注目します。本稿執筆時点から掲載までの間に、.NET Portability Analyzer が更新される可能性があり、本稿に掲載する画像と実際のツールの見た目が異なることがあるため、ご注意ください。

成功に向けての準備

複数のプラットフォームで正しく機能するライブラリにするには、十分なファクタリングを行い、ほぼすべてのビジネス ロジックを含めるようにします。UI コードは別のプロジェクトに分離します。ただし、.NET Core は .NET Framework のサブセットなので、コードを十分ファクタリングしても、ライブラリでは .NET Core でサポートされない API が使用されている可能性があります。

場合によっては、このような API と同じ目的を実現する代替の API があることもあります。このような場合、Portability Analyzer は代替の API を提案します。代替の API が存在しない場合は、プラットフォーム固有のコードを取り除く必要があります。結局のところ、アセンブリがどの程度ファクタリングされているか分からなくても、Portability Analyzer を使用すれば迅速な評価を実行できます。

この拡張機能を使用するには、Visual Studio 2013 または 2014 が必要です。次の手順として、この拡張機能をインストールします。Visual Studio ギャラリーで「.NET Portability Analyzer」を検索するか、aka.ms/lah74y に直接移動します。

[ダウンロード] ボタンをクリックして、[開く] を選択します。次に表示されるダイアログで、拡張機能を適用する Visual Studio のバージョンを選択します。[インストール] をクリックするとインストールが開始されます。インストールが完了したら [閉じる] をクリックしてダイアログを閉じます。次に、ターゲットにするプラットフォームを選択し、アセンブリまたはプロジェクトを分析します。

ターゲットにするプラットフォームを選ぶ

Portability Analyzer は既定で .NET Framework、ASP.NET vNext (別名 .NET Core)、Windows、および Windows Phone についての結果を提示します。Visual Studio の [ツール] メニューの [オプション] を選択して、.NET Portability Analyzer エントリにアクセスし、ターゲットにするプラットフォームのセットを選択することで、追加オプションを指定できます (図 1 参照)。

プロジェクトのターゲット プラットフォームの選択
図 1 プロジェクトのターゲット プラットフォームの選択

Portability Analyzer を実行する

アセンブリとプロジェクトを分析する方法は 2 つあります。

  • 既にビルド済みのアセンブリまたは実行可能なファイルを分析する場合は、Visual Studio の [分析] メニューから Portability Analyzer にアクセスし、分析するアセンブリの場所を参照します。このオプションでは、要約と詳細レポートが生成されます。
  • プロジェクトを分析する場合は、ソリューション エクスプローラーでターゲットにするプロジェクトを右クリックします。[分析] メニューの [Analyze Assembly Portability] を選択します (図 2 参照)。分析は選択したプロジェクト固有に行われます。このオプションでは、要約と詳細レポートが生成され、エラー一覧に問題が発生しているファイル名と行番号を示すメッセージが出力されます。各メッセージをダブル クリックすると、メッセージで指定されているコード行に移動します。

指定したプロジェクトに対して Analyze Assembly Portability を選択
図 2 指定したプロジェクトに対して Analyze Assembly Portability を選択

今回はツールをテストするため、1 年ほど前に取り組んだプロジェクト (Windows Phone Silverlight 8 をターゲットにする言葉遊びのゲーム) を開きました。当初、ポータブル クラス ライブラリ (PCL) のビジネス ロジックは Windows Phone 8 と Windows 8 をターゲットにしていました。このライブラリを再利用して、同じアプリを Windows 向けに実装することを考えていました。しかし、いくつか問題に直面し、時間もなかったため、アプローチを変更し、Windows Phone Silverlight 8 のみをターゲットにするライブラリにしました。

今回の目的は、このライブラリの移植性を分析し、コードに必要な変更を加えた後、プロジェクトを PCL プロジェクトに変換することで、Windows 8.1、Windows Phone 8.1、および Windows Phone Silverlight 8.1 をそれぞれターゲットにすることです。また、.NET Core とプロジェクトの互換性もテストしたいと考えています。Portability Analyzer は、実際にプロジェクトを変換し、ターゲットを変更し、コンパイル エラーの解決を試みることなく、実際に必要な作業のヒントを提供します。今回のライブラリを Android アプリのビルドにも使用できるかどうかも興味があったので、ツールを構成して Xamarin.Android について同様に結果を得られるようにしました。

ツールを実行して得られる結果は心強いものです。図 3 に、要約、詳細レポート、エラー メッセージ、およびレポート URL を示します。要約を見ると、このライブラリでは、選択したすべてのプラットフォームに対してある程度十分な互換性があることが分かります。Windows Phone Silverlight 8.1 に対しては 100% の互換性があることなっています。Windows Phone Silverlight 8 が本来のターゲットなので、それほど驚くことではありません。

互換性のあるプラットフォームを示す詳細互換性レポート
図 3 互換性のあるプラットフォームを示す詳細互換性レポート

見た目がスプレッドシートのような詳細結果は、今回ターゲットにした 1 つ以上のプラットフォームによってサポートされていない API のみを表示しています。詳細に目を通しやすくなっています。その API をサポートしていないプラットフォームには赤い X 印が示され、サポートしているプラットフォームには緑のチェックマークが示されています。すべてのプラットフォームでサポートされる API は、リファクタリングの必要がないため、このレポートに掲載されていないことに注意してください。

詳細レポートには、推奨の変更を示す列もあり、複数のプラットフォームで機能する代替の API が示されています。詳細レポートの最下部に、[Back to Summary] というリンクがあります。このリンクをクリックすると、冒頭にある要約部分に戻ります。今回の結果は非常に短くなっていますが、長めのレポートにはこの最上部に戻る機能が便利です。

プロジェクトの分析が完了しているため、レポートのエラー一覧にはその関数が使用されているファイルと行番号を示すメッセージが表示されています。メッセージをクリックすると、示されたファイルの行番号に移動できます。

Visual Studio 外部の結果にアクセスする場合、その結果は、ターゲットのアセンブリと同じプロジェクト ディレクトリに含まれる HTML ファイル (ApiPortabilityAnalysis.htm) に保存されます。結果の場所はレポート最上部の URL セクションに示されています (図 4 参照)。

Visual Studio 外部へのアクセス用に保存された Portability Analysis の結果
図 4 Visual Studio 外部へのアクセス用に保存された Portability Analysis の結果

まだ作業中

.NET Portability Analyzer、その結果、およびガイダンスは、.NET チームがより多くの情報を収集するにつれて、変更されることが予想されます (図 5 参照)。チームは、このツールと関連コンソール アプリが使用される際の API の用法に関する匿名のデータを収集していて、そのデータを bit.ly/14vciaD (英語) にまとめています。

.NET API の使用法のレベルをまとめたサイト
図 5 .NET API の使用法のレベルをまとめたサイト

このサイトでは、大部分のコードを変更する必要のある API を既定で示しています。(画像にはありませんが) [Show] ドロップダウンの値を変更すると、表示する API を変更できます。また、[R] と [S] の アイコンをクリックすると、Portability Analyzer によって表示されるのと同じ推奨コードを確認できます。

一覧内の各 API の名前はリンクが設定され、その API がサポートされるプラットフォームを示すページに移動できます。チームはこのサイトの更新を継続することを計画していて、顧客からの情報もオープンになる可能性があるため、定期的に確認することをお勧めします。

ライブラリをクロスプラットフォーム対応にする

いよいよライブラリを修正します。ライブラリを Windows Phone 8.1 と Windows 8.1 で機能させる予定ですが、いくつか問題があります。今回は、問題ごとに異なるアプローチを実行します。

プラットフォーム抽象化の使用: リソース ストリーム関連のエントリが 2 つあります。レポートによれば、この 2 つの API は Windows 8.1 でも Windows Phone 8.1 でもサポートされません。ツールからも代替 API が推奨されなかったため、このコードをライブラリから削除する必要があります。レポートでは、この 2 つのメンバーはどちらも、静的辞書ファイルを読み込んでいる数行の同じコードで使われていると示されています。

WordList = new ObservableCollection<string>();
StreamResourceInfo info =
  Application.GetResourceStream(new
  Uri("/WordLibWP;component/US.dic", UriKind.Relative));
StreamReader reader = new StreamReader(info.Stream);
string line;
while ((line = reader.ReadLine()) != null)
{
  WordList.Add(line);
}
reader.close();

Windows 8.1 と Windows Phone 8.1 では Windows.Storage API を使ってリソース ファイルを読み込むことができますが、そうするとライブラリは Windows Phone Silverlight との互換性がなくなります。ターゲットを広げるために、この少量のプラットフォーム固有のコードを抽象化することにします。プラットフォームの抽象化は、動作を定義するライブラリを提供する便利なテクニックですが、ライブラリの異なる動作をプラットフォーム固有のコードに実装します。複数のプラットフォームに互換性のあるコードにする場合、プラットフォームの抽象化は知っておきたいテクニックの 1 つです。

以下は、今回の抽象化のために行った基本手順です。

  • クロスプラットフォーム ライブラリで動作を定義する: このためには、辞書ファイルを読み取るためのメソッドを定義する抽象クラスをライブラリに作成します。DictionaryReader クラスのインスタンスであるプロパティも定義します。以下のようになります。
public abstract class DictionaryReader
{
  public abstract Task<ObservableCollection<string>>
    ReadDictionaryAsync(string path);
  public static DictionaryReader Instance { get; set; }
}
  • プラットフォーム固有のコードに動作を実装する: このためには、Windows Phone Silverlight プロジェクトの DictionaryReader クラスから派生します。アプリのリソースとして辞書ファイルを読み込んで読み取る ReadDictionaryAsync メソッドの実装を用意します。このコードは基本的にライブラリにあるものと同じですが、Windows Phone のコードを機能させるために特定のフォーマットが必要なため、リソース パスに対していくつかのエラー チェックを行っているのがわかります。他のプラットフォームについての実装は、そのプラットフォーム用のアプリローカルなリソースを読み取る手法によって異なります。ただし、既に追加した抽象化によって (図 6 参照)、その点は問題になりません。
  • ライブラリで定義されたインスタンスを、プラットフォーム固有の実装に対して初期化する: このためには、Windows Phone プロジェクトの App クラスにコードを追加します。追加するコードでは、DictionaryReader のインスタンスを初期化し、リソースとしてファイルを読み取る Windows Phone 固有の実装になります。繰り返しになりますが、以下のコードはプラットフォーム固有のプロジェクトに存在し、分析したプロジェクトには存在しません。
public App()
{
  DictionaryReader.Instance = new DictionaryReaderPhone();
...
}

図 6 辞書を読み込んで読み取るための Windows Phone 実装

class DictionaryReaderPhone : DictionaryReader
{
  public override async Task<ObservableCollection<string>>
  ReadDictionaryAsync(string resourcePath)
{
  ObservableCollection<string> wordList =
    new ObservableCollection<string>();
  StreamResourceInfo info =
    Application.GetResourceStream(new Uri(resourcePath,
    UriKind.Relative));
  if (info == null)
    throw new ArgumentException("Could not load resource: " +
      resourcePath +
      ". For Windows Phone this should be in the
      Format:[project];component/[filename]");
  else
  {
    StreamReader reader = new StreamReader(info.Stream);
    string line;
    while ((line = await reader.ReadLineAsync()) != null)
    {
    wordList.Add(line);
    }
    reader.Close();
    return wordList;
    }
  }
}

プラットフォーム固有のコードを抽象化する方法を示すもう 1 つの例として、MSDN ライブラリの記事「汎用性のあるクラス ライブラリを使用したプラットフォームの抽象化」を参照してください。

異なる API を使用する: 次に、現在のカルチャ名を得るために、エントリを評価します。エラー メッセージをダブル クリックすると、以下のメソッドが見つかります。

public string CheckLanguage()
{
  return Thread.CurrentThread.CurrentCulture.Name.Split('-')[0];
}

Portability Analyzer によると、CultureInfo.CurrentCulture を使用できます。CurrentCulture は CultureInfo の静的プロパティで、現在のスレッドから取得している情報と同じ情報を提供するため、問題なく機能します。Thread クラスの取得に利用しているコードを、CultureInfo を使用する以下のコードに置き換えます。

public string CheckLanguage()
{
  return System.Globalization.CultureInfo.CurrentCulture.Name.Split('-')[0];
}

ここまでは順調

変更点をテストすると、すべてが順調に見えます。ここで、Portability Analyzer を再び実行します。ほんのわずかにコードを変更しただけで、レポートがすっきりしたうえ、本来のターゲット プラットフォームに加え、Xamarin.Android もサポートできるようになっています (図 7 参照)。

コード変更後に再び実行した Analysis Report
図 7 コード変更後に再び実行した Analysis Report

クロスプラットフォーム対応に変換する最後の手順は、Windows Phone 固有の現在のライブラリ プロジェクトを、複数のアプリから参照できる PCL に変換する作業です。当初、このための最も簡単で手早い方法は、手動でプロジェクト ファイルを変更する「インプレース」変換だと考えていました。

ある程度調べて見つけた手順をいくつか試したところ、手動でプロジェクト ファイルを変更することに大きなメリットはない、という結論に達しました。.NET チームのメンバーの 1 人にダブルチェックを依頼しましたが、同じ結論でした。手動でプロジェクトを変更するメリットの大部分は、特定の出発点を前提とするものです。

プロジェクトの履歴や、これまでに行ったアップグレードによっては、最終的にプロジェクトが機能しなくなる可能性があります。長い時間をかけて手動でプロジェクト ファイルの変換に挑戦した場合は、新しく PCL プロジェクトを作成する必要がある可能性が高くなります。そのうえ、手動で変換したプロジェクトが最初は機能したとしても、その後に他の変更を加えると、機能しなくなる可能性があります。

結局のところ、今回は新しい PCL プロジェクトを作成したので、読者の皆さんにも同じことをお勧めします。新規のプロジェクトに、コード ファイルをコピーします。コードをコピーすると、新しい PCL にもはや適用されない using ステートメントがあったため、わずかなコンパイル エラーが発生しました。そのステートメントを削除したら、ライブラリの準備は完了です。Windows Phone Silverlight アプリが新しい PCL を参照するように切り替えると、アプリが起動され、再び実行されるようになりました。

使ってみる

Portability Analyzer を使うと、ライブラリをクロスプラットフォーム対応にするために必要な作業を迅速に評価できるだけでなく、コード内にあるプラットフォーム固有の問題を、メソッドの呼び出しやプロパティの使用に至るまで特定できます。また、代替 API を使える部分も提案されます。

今回は、プラットフォーム固有のコードをファクタリングする方法も紹介しました。新しい Portability Analyzer の結果では、Xamarin.Android もだけでなく、追加でターゲットにするプラットフォーム上でもライブラリが動作することが示されています。最終的に、新しいターゲットを指定した PCL を新規に作成し、既存のアプリからこの PCL を参照することができます。新たな .NET Portability Analyzer は、ライブラリを新しいプラットフォームへ進出させる一助となるでしょう。


Cheryl Simmons はマイクロソフトで 10 年間プログラミング ライターを務め、NET 基本クラス ライブラリ、Windows フォーム、Windows Presentation Foundation、Silverlight、 Windows Phone、および関連ツールに関する記事を書いています。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Matt Galbraith、Daniel Plaisted、および Taylor Southwick に心より感謝いたします。