連載
» 2015年06月17日 05時00分 公開

WinRT/Metro TIPS:テキストファイルを簡単に読み書きするには?[ユニバーサルWindowsアプリ開発]

Windowsランタイムアプリではテキストファイルの扱い方が独特だ。そこで、本稿ではテキストファイルの読み取り/書き込みを行う方法を解説する。

[山本康彦,BluewaterSoft/Microsoft MVP for Windows Platform Development]
WinRT/Metro TIPS
業務アプリInsider/Insider.NET

powered by Insider.NET

「WinRT/Metro TIPS」のインデックス

連載目次

 テキストファイルを読み書きする。基本的なことではあるが、デスクトップアプリ開発やASP.NETのWeb開発からWindowsランタイムアプリ開発に移ってきた人にとっては、どうすればよいのか分かりにくいかもしれない。そこで本稿では、Windows 8.1とWindows Phone 8.1用のユニバーサルWindowsアプリ、およびWindows 10用のユニバーサルWindowsアプリで共通に使える方法を解説する。なお、本稿のサンプルは「Windows Store app samples:MetroTips #107」からダウンロードできる。

事前準備

 Windows 8.1とWindows Phone 8.1用のユニバーサルWindowsアプリを開発するには、以下の開発環境が必要である。本稿では、無償のVisual Studio Community 2013 with Update 4を使っている。

  • SLAT対応のPC*1
  • 2014年4月のアップデート*2適用済みの64bit版Windows 8.1 Pro版以上*3
  • Visual Studio 2013 Update 2(またはそれ以降)*4を適用済みのVisual Studio 2013(以降、VS 2013)*5

*1 SLAT対応ハードウエアは、Windows Phone 8.1エミュレーターの実行に必要だ。ただし未対応でも、ソースコードのビルドと実機でのデバッグは可能だ。SLAT対応のチェック方法はMSDNブログの「Windows Phone SDK 8.0 ダウンロードポイント と Second Level Address Translation (SLAT) 対応PCかどうかを判定する方法」を参照。なお、SLAT対応ハードウエアであっても、VM上ではエミュレーターが動作しないことがあるのでご注意願いたい。

*2 事前には「Windows 8.1 Update 1」と呼ばれていたアップデート。スタート画面の右上に検索ボタンが(環境によっては電源ボタンも)表示されるようになるので、適用済みかどうかは簡単に見分けられる。ちなみに公式呼称は「the Windows RT 8.1, Windows 8.1, and Windows Server 2012 R2 update that is dated April, 2014」というようである。

*3 Windows Phone 8.1エミュレーターを使用しないのであれば、32bit版のWindows 8.1でもよい。

*4 マイクロソフトのダウンロードページから誰でも入手できる(このURLはUpdate 4のもの)。

*5 本稿に掲載したコードを試すだけなら、無償のExpressエディションやCommunityエディションで構わない。Visual Studio Express 2013 with Update 4 for Windows(製品版)はマイクロソフトのページから無償で入手できる。Expressエディションはターゲットプラットフォームごとに製品が分かれていて紛らわしいが、Windowsランタイムアプリの開発には「for Windows」を使う(「for Windows Desktop」はデスクトップで動作するアプリ用)。また、2014年11月12日(米国時間)に新しくリリースされたVisual Studio Community 2013 with Update 4(製品版)もマイクロソフトのページから無償で入手できる。Communityエディションは本稿執筆時点では英語版だけなので、同じ場所にあるVisual Studio 2013 Language Packの日本語版を追加インストールし、[オプション]ダイアログで言語を切り替える必要がある。


Windows.Storage名前空間のFileIOクラス

 .NET Frameworkで開発してきた人は、ファイルの読み書きというとSystem.IO名前空間のFileクラスやStreamReader/StreamWriterクラスなどを思い浮かべるだろう*6。しかし、WindowsランタイムではWindows.Storage名前空間のFileIOクラスをよく利用する。System.IO名前空間も使えるが、かえって面倒になる。

 Windows.Storage名前空間のFileIOクラスは、それほど大きくないファイルを読み書きしやすい作りになっている。とても簡単にファイルの読み書きができるのだ(それを本稿で解説する)。その半面、メモリに載りきらない大きなファイルや、Unicode以外のエンコーディングは扱えない。また、例えば、XmlSerializerクラスの出力先にもできない。そのような高度な処理が必要なときは、面倒だが.NET Frameworkの世界に移ってSystem.IO名前空間を使うことになる*7

*6 .NET FrameworkのSystem.IO名前空間のFileクラスはWindowsランタイムアプリでは使えない。StreamReader/StreamWriterクラスはWindowsランタイムアプリで使える。StreamReader/StreamWriterクラスを使ってテキストファイルを読み書きする方法は、次を参照してほしい。

*7  Unicode以外のエンコーディングのテキストファイルを読み込む方法や、XmlSerializerクラスを使う例は、次を参照してほしい。


処理対象のファイルオブジェクトを得るには?

 読み書きの対象とするファイルは、StorageFileオブジェクト(Windows.Storage名前空間)として扱う。ところが、Windowsランタイムアプリでは、ファイルのフルパスを使ってファイルのオブジェクトを得ることはできないのだ。本稿では、StorageFileオブジェクトは取得できているものとして話を進めるが、簡単に紹介しておこう。StorageFileオブジェクトを取得するには、次のような方法を用いる。

  • URIスキーム:アプリのパッケージやローカルデータ記憶域にあるファイルは、URIスキームで指定が可能である。例: StorageFile.GetFileFromApplicationUriAsync("ms-appdata:///local/foo.txt");
  • ApplicationDataクラス/KnownFoldersクラス:アプリのローカルデータ記憶域にあるファイルは、ApplicationDataクラスを利用して取得できる。マニフェストで「機能」を追加した場合は、KnownFoldersクラスも使える。例: ApplicationData.Current.LocalFolder.GetFileAsync("foo.txt")
  • ファイルピッカー:任意の場所のファイルを扱うには、ファイルピッカー(Windows.Storage.Pickers名前空間のFileOpenPicker/FileSavePickerクラス)を出してエンドユーザーに選択してもらう必要がある

テキストファイルを読み取るには?

 FileIOクラスのReadTextAsyncメソッドかReadLinesAsyncメソッドを使えばよい。

 ReadTextAsyncメソッドは、テキストファイルの内容全体を一つの文字列として読み取ってくれる(次のコード)。

// 読み取るテキストファイル(何らかの手段で取得したStorageFileオブジェクト)
StorageFile sampleFile = ……省略……

// ファイルの全体を読み取る
string readText = await FileIO.ReadTextAsync(sampleFile);

' 読み取るテキストファイル(何らかの手段で取得したStorageFileオブジェクト)
Dim sampleFile As StorageFile = ……省略……

' ファイルの全体を読み取る
Dim readText As String = Await FileIO.ReadTextAsync(sampleFile)

テキストファイルの全体を読み込むコード例(上:C#/下:VB)
FileIOクラス(Windows.Storage名前空間)のReadTextAsyncメソッドは、ファイルの全体を読み込んで一つの文字列として返す。
読み取れるのはUnicodeのファイルだけである。Unicodeの種類(UTF-8/UTF-16BE/UTF-16LE)は自動判別されるが、第2引数として指定することもできる。
メモリに載りきらない大きなファイルを読もうとすると、例外が発生する。その他、他のアプリがファイルを開いていたりしても、例外が発生する。サンプルコードということで省略しているが、実用ではReadTextAsyncメソッドを呼び出している行をtry〜catchしていただきたい。

 ReadLinesAsyncメソッドは、読み込んだテキストファイルを行ごとに分解し、文字列のコレクションとして返してくれる(次のコード)。

// 読み取るテキストファイル(何らかの手段で取得したStorageFileオブジェクト)
StorageFile sampleFile = ……省略……

// ファイルを行ごとに分解して読み込む
IList<string> lines = await FileIO.ReadLinesAsync(sampleFile);

foreach (string line in lines)

  // line変数に1行分の文字列が入っているので、ここで1行に対する処理を行う
  System.Diagnostics.Debug.WriteLine(line);

  // ファイルの読み込みは完了しているので、ここでファイルを書き換えてもOK!
}

' 読み取るテキストファイル(何らかの手段で取得したStorageFileオブジェクト)
Dim sampleFile As StorageFile = ……省略……

' ファイルを行ごとに分解して読み込む
Dim lines As IList(Of String) = Await FileIO.ReadLinesAsync(sampleFile)

For Each line As String In lines
  ' line変数に1行分の文字列が入っているので、ここで1行に対する処理を行う
  System.Diagnostics.Debug.WriteLine(line)

  ' ファイルの読み込みは完了しているので、ここでファイルを書き換えてもOK!
Next

テキストファイルを行ごとに分解して読み込むコード例(上:C#/下:VB)
FileIOクラス(Windows.Storage名前空間)のReadLinesAsyncメソッドは、ファイルの全体を読み込んで行ごとに分解し、文字列のコレクションとして返す。
読み取れるのはUnicodeのファイルだけである。Unicodeの種類(UTF-8/UTF-16BE/UTF-16LE)は自動判別されるが、第2引数として指定することもできる。
ReadLinesAsyncメソッドから返された文字列のコレクションはforeach/For Eachループで処理できる。ループ変数には1行分の文字列が入っているので、行ごとの処理ができる。.NET FrameworkのFileクラス(System.IO名前空間)のReadLinesメソッドでは列挙が終わるまで(=ループを抜けるまで)ファイルをオープンしたままになった。それに対して、このReadLinesAsyncメソッドではファイルの全体を読み込み、ファイルをクローズしてから結果を返しているので、ループの中でファイルを書き換えることも可能だ。
メモリに載りきらない大きなファイルを読もうとすると、例外が発生する。その他、他のアプリがファイルを開いていたりしても、例外が発生する。サンプルコードということで省略しているが、実用ではReadLinesAsyncメソッドを呼び出している行をtry〜catchしていただきたい。
なお、Windowsランタイムアプリでも、デバッグ実行時にはDebugクラス(System.Diagnostics名前空間)のWriteLineメソッドでVisual Studioの出力ペインに文字列を表示できる。

ファイルの末尾にテキストを追加するには?

 FileIOクラスのAppendTextAsyncメソッドかAppendLinesAsyncメソッドを使えばよい。

 空のファイル(=サイズが0のファイル)に追加した場合は、次項で説明する上書きと同じ結果になる。

 AppendTextAsyncメソッドには、StorageFileオブジェクトと一つの文字列を与える。すると、そのファイルの末尾にその文字列が追加される(次のコード)。

// 書き込むテキストファイル(何らかの手段で取得したStorageFileオブジェクト)
StorageFile sampleFile = ……省略……

// 追加したいテキスト
string text =
@"1行目
2行目
";

// ファイルの末尾にテキストを追加する
await FileIO.AppendTextAsync(sampleFile, text);

' 書き込むテキストファイル(何らかの手段で取得したStorageFileオブジェクト)
Dim sampleFile As StorageFile = ……省略……

' 追加したいテキスト
Dim text As String = "1行目" + vbCrLf _
                     + "2行目" + vbCrLf

' ファイルの末尾にテキストを追加する
Await FileIO.AppendTextAsync(sampleFile, text)

テキストファイルの末尾に一つの文字列を追加するコード例(上:C#/下:VB)
FileIOクラス(Windows.Storage名前空間)のAppendTextAsyncメソッドは、ファイルの末尾に文字列を追加する。
書き込めるのはUnicodeのファイルだけである。Unicodeの種類(UTF-8/UTF-16BE/UTF-16LE)を第3引数として指定することもできる。
この例では2行分の文字列を作ってから書き込んでいる。これを1行ずつ2回に分けてAppendTextAsyncメソッドを呼び出すと、パフォーマンスが落ちるので注意してほしい。AppendTextAsyncメソッドの呼び出しごとにファイルのオープン/クローズの処理が行われる。これはデータを書き出す処理よりはるかに時間がかかるのである。
他のアプリがファイルを開いていたり、ファイルに読み取り禁止属性が付いていたりすると、例外が発生する。サンプルコードということで省略しているが、実用ではAppendTextAsyncメソッドを呼び出している行をtry〜catchしていただきたい。

 AppendLinesAsyncメソッドには、StorageFileオブジェクトと文字列のコレクションを与える。コレクション内のそれぞれの文字列を1行として、そのファイルの末尾に順に追加される(次のコード)。

// 書き込むテキストファイル(何らかの手段で取得したStorageFileオブジェクト)
StorageFile sampleFile = ……省略……

// 追加したいテキストのコレクション
IEnumerable<string> texts = new List<string>()
                                {
                                  "1行目",
                                  "2行目",
                                };

// ファイルの末尾にテキストを追加する
await FileIO.AppendLinesAsync(sampleFile, texts);

' 書き込むテキストファイル(何らかの手段で取得したStorageFileオブジェクト)
Dim sampleFile As StorageFile = ……省略……

' 追加したいテキストのコレクション
Dim texts As IEnumerable(Of String) = New List(Of String)() _
                                            From {
                                              "1行目",
                                              "2行目"
                                            }

' ファイルの末尾にテキストを追加する
Await FileIO.AppendLinesAsync(sampleFile, texts)

テキストファイルの末尾に文字列のコレクションを追加するコード例(上:C#/下:VB)
FileIOクラス(Windows.Storage名前空間)のAppendLinesAsyncメソッドは、コレクションのそれぞれの文字列を1行として、ファイルの末尾に順に追加する。この例では、2行追加される。改行コードとしてはEnvironmentクラス(System名前空間)のNewLineプロパティの値が使われるようだ(Windows/Windows Phoneでは「\r\n」/vbCrLF)。
書き込めるのはUnicodeのファイルだけである。Unicodeの種類(UTF-8/UTF-16BE/UTF-16LE)を第3引数として指定することもできる。
他のアプリがファイルを開いていたり、ファイルに読み取り禁止属性が付いていたりすると、例外が発生する。サンプルコードということで省略しているが、実用ではAppendLinesAsyncメソッドを呼び出している行をtry〜catchしていただきたい。
なお、このVBのコードでは、Visual Basic 2010から利用できるようになったコレクション初期化子を使用している。

ファイルにテキストを上書きするには?

 FileIOクラスのWriteTextAsyncメソッドかWriteLinesAsyncメソッドを使えばよい。

 WriteTextAsyncメソッドには、StorageFileオブジェクトと一つの文字列を与える。すると、そのファイルの内容がその文字列で置き換えられる(次のコード)。

// 書き込むテキストファイル(何らかの手段で取得したStorageFileオブジェクト)
StorageFile sampleFile = ……省略……

// 上書きしたいテキスト
string text =
@"1行目
2行目
";

// ファイルの内容を上書きする
await FileIO.WriteTextAsync(sampleFile, text);

' 書き込むテキストファイル(何らかの手段で取得したStorageFileオブジェクト)
Dim sampleFile As StorageFile = ……省略……

' 上書きしたいテキスト
Dim text As String = "1行目" + vbCrLf _
                     + "2行目" + vbCrLf

' ファイルの内容を上書きする
Await FileIO.WriteTextAsync(sampleFile, text)

テキストファイルの内容を一つの文字列で置き換えるコード例(上:C#/下:VB)
FileIOクラス(Windows.Storage名前空間)のWriteTextAsyncメソッドは、与えた文字列でファイルの内容を上書きする。
書き込めるのはUnicodeのファイルだけである。Unicodeの種類(UTF-8/UTF-16BE/UTF-16LE)を第3引数として指定することもできる。
他のアプリがファイルを開いていたり、ファイルに読み取り禁止属性が付いていたりすると、例外が発生する。サンプルコードということで省略しているが、実用ではWriteTextAsyncメソッドを呼び出している行をtry〜catchしていただきたい。

 WriteLinesAsyncメソッドには、StorageFileオブジェクトと文字列のコレクションを与える。コレクション内のそれぞれの文字列を1行として、そのファイルの内容を置き換える(次のコード)。

// 書き込むテキストファイル(何らかの手段で取得したStorageFileオブジェクト)
StorageFile sampleFile = ……省略……

// 上書きしたいテキストのコレクション
IEnumerable<string> texts = new List<string>()
                                {
                                  "1行目",
                                  "2行目",
                                };

// ファイルを上書きする
await FileIO.WriteLinesAsync(sampleFile, texts);

' 書き込むテキストファイル(何らかの手段で取得したStorageFileオブジェクト)
Dim sampleFile As StorageFile = ……省略……

' 上書きしたいテキストのコレクション
Dim texts As IEnumerable(Of String) = New List(Of String)() _
                                            From {
                                              "1行目",
                                              "2行目"
                                            }

' ファイルを上書きする
Await FileIO.WriteLinesAsync(sampleFile, texts)

テキストファイルの内容を文字列のコレクションで置き換えるコード例(上:C#/下:VB)
FileIOクラス(Windows.Storage名前空間)のWriteLinesAsyncメソッドは、コレクションのそれぞれの文字列を1行として、ファイルの内容を置き換える。この例では、書込み後のファイルの内容は2行になる。改行コードとしてはEnvironmentクラス(System名前空間)のNewLineプロパティの値が使われるようだ(Windows/Windows Phoneでは「\r\n」/vbCrLF)。
書き込めるのはUnicodeのファイルだけである。Unicodeの種類(UTF-8/UTF-16BE/UTF-16LE)を第3引数として指定することもできる。
他のアプリがファイルを開いていたり、ファイルに読み取り禁止属性が付いていたりすると、例外が発生する。サンプルコードということで省略しているが、実用ではWriteLinesAsyncメソッドを呼び出している行をtry〜catchしていただきたい。
なお、このVBのコードでは、Visual Basic 2010から利用できるようになったコレクション初期化子を使用している。

追加書き込みのパフォーマンスの違い

 以上で、テキストファイルを読み書きする方法の説明は、一通り終了である。ところで、AppendTextAsyncメソッドを使うコードの説明に、複数回に分けて呼び出すとパフォーマンスが落ちると書いた。AppendTextAsyncメソッドの呼び出しごとにファイルのオープン/クローズ処理が走るので、時間がかかるのだ。「そうはいっても大したことじゃないだろう?」と思われるかもしれないので、最後にその検証を行っておこう。数行程度では確かに大したことはないのだが、数百行のオーダーになってくるととんでもないことになるのである。次のコードでは、1000行の書き込みにかかる時間を比較している。

// 冒頭に「using System.Linq;」が必要

// 書き込むテキスト(1000行)
IEnumerable<string> texts = Enumerable.Range(1, 1000).Select(n => n.ToString());

// 何らかの手段で取得したStorageFileオブジェクト
StorageFile sampleFile = ……省略……

// 計測用のタイマーをスタート
var stopWatch = new System.Diagnostics.Stopwatch();
stopWatch.Start();

// 1行ずつ追加書き込み
foreach(var line in texts)
  await FileIO.AppendTextAsync(sampleFile, line);

// タイマーを止めて経過時刻表示
stopWatch.Stop();
long time1 = stopWatch.ElapsedMilliseconds;
System.Diagnostics.Debug.WriteLine("AppendTextAsync: {0}ミリ秒", time1);

// 何らかの手段で取得したStorageFileオブジェクト(新しいファイルを用意する)
sampleFile = ……省略……

// タイマーをスタート
stopWatch.Restart();

// まとめて追加書き込み
await FileIO.AppendLinesAsync(sampleFile, texts);

// タイマーを止めて経過時刻表示
stopWatch.Stop();
long time2 = stopWatch.ElapsedMilliseconds;
System.Diagnostics.Debug.WriteLine("AppendLinesAsync: {0}ミリ秒", time2);

// PCでの出力例:
// AppendTextAsync: 9542ミリ秒
// AppendLinesAsync: 8ミリ秒

' 書き込むテキスト(1000行)
Dim texts As IEnumerable(Of String) = Enumerable.Range(1, 1000) _
                                       .Select(Function(n) n.ToString())

' 何らかの手段で取得したStorageFileオブジェクト
Dim sampleFile As StorageFile = ……省略……

' 計測用のタイマーをスタート
Dim stopWatch = New System.Diagnostics.Stopwatch()
stopWatch.Start()

' 1行ずつ追加書き込み
For Each line In texts
  Await FileIO.AppendTextAsync(sampleFile, line)
Next

' タイマーを止めて経過時刻表示
stopWatch.Stop()
Dim time1 As Long = stopWatch.ElapsedMilliseconds
System.Diagnostics.Debug.WriteLine("AppendTextAsync: {0}ミリ秒", time1)

' 何らかの手段で取得したStorageFileオブジェクト(新しいファイルを用意する)
sampleFile = ……省略……

' タイマーをスタート
stopWatch.Restart()

' まとめて追加書き込み
Await FileIO.AppendLinesAsync(sampleFile, texts)

' タイマーを止めて経過時刻表示
stopWatch.Stop()
Dim time2 As Long = stopWatch.ElapsedMilliseconds
System.Diagnostics.Debug.WriteLine("AppendLinesAsync: {0}ミリ秒", time2)

' PCでの出力例:
' AppendTextAsync: 9774ミリ秒
' AppendLinesAsync: 10ミリ秒

追加書き込みのパフォーマンスを比較するコード例(上:C#/下:VB)
筆者の環境では、1行ずつ追加書き込みをした場合、1000行書き込むのに10秒ほどかかってしまった。対して、1回にまとめて書き込んだ場合は、10ミリ秒程度であった。この違いは大きい。
1000行の追加書き込みを行ごとに分けて行うと、まとめて追加書き込みするのに比べて1000倍程度の時間がかかっている。この例の程度のデータ量(=OSのディスクキャッシュに入ってしまう程度)では、追加書き込み処理のほとんどの時間はファイルのオープン/クローズに使われているといえるだろう。
また、ここではコレクションの内容をまとめて書き込むためにAppendLinesAsyncメソッドを使っているが、書き込む内容が一つの文字列になっているならばAppendTextAsyncメソッドを使えばよい。
なお、このコードの冒頭で使っているEnumerableクラス(System.Linq名前空間)のRangeメソッドについては「.NET TIPS:LINQ:数値コレクション内の数値を集計するには?[C#、VB]」を、Enumerableクラス(System.Linq名前空間)のSelect拡張メソッドについては「.NET TIPS:LINQ:コレクション内のオブジェクトが持つ数値を集計するには?[C#、VB]」を、それぞれ参照してほしい。

まとめ

 テキストファイルの読み書きは、Windows.Storage名前空間のFileIOクラスを使うと簡単にできる。ただし、メモリに載りきらないほど大きなファイルや、Unicode以外のエンコーディングのファイルは扱えないなど、簡単に使える代償としての限界もある。簡単に済ませられるときはFileIOクラスを使い、そうでなければ.NET Frameworkの世界に移ってSystem.IO名前空間を使うことになる。うまく使い分けてほしい。

【コラム】 Windows 10のアプリ開発に挑戦するなら必見、厳選MVAコース!

 Microsoft Virtual Academy(MVA)では、マイクロソフトのトレーニングコースが無償で受講できる。多数あるコースの中から、いち早くWindows 10のユニバーサルWindowsアプリ開発に挑戦してみたいという人にぴったりのものを紹介する。

 現時点で詳細な情報はどうしても英語のものになってしまう。ここに挙げた英語のコンテンツは資料をダウンロードできるので、まずそれを読んでからヒアリングに挑戦するとよいだろう。


「WinRT/Metro TIPS」のインデックス

WinRT/Metro TIPS

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。