プロジェクト内のテキストファイルを読み込む方法 ~Resource~[引越記事]
いきなり追記(2018.10.15)
@Zuishinさんより教えていただいた方法だと、ビルドアクションにResource
を設定したファイルに非常にスマートにアクセスできます。@Zuishinさんありがとうございます。
さて、ファイルの追加、ビルドアクションの設定の仕方は僕の書いたほうを読んでいただくとして、スマートな方法は以下のとおりです。
//@Zuishinさんに教えていただいた方法 string fileCount; var info = Application.GetResourceStream(new Uri("/TextFiles/TextFile1.txt",UriKind.Relative)); using (var sr = new StreamReader(info.Stream)) { fileContent = sr.ReadToEnd(); }
波括弧を除けば3行でファイルの中身を取得できます。スマートです。
頑張って説明してみます。
Application.GetResourceStream()
メソッドでファイルへのストリームを取得、と思いきやこのメソッドの返り値はStreamResourceInfo
です。が、この返り値がファイルへのStream
を持っていますので、それを使ってStreamReader
でファイルの中身を取得します。
さて、GetResourceStream()
メソッドですが、引数に目的のファイルの位置をUriで受け取ります。Uriの部分は、下のようにソリューションエクスプローラにおいて、プロジェクトフォルダを起点としたパス文字列を構成し、UriKind.Relative
で「相対パスですよ」と指定します。
new Uri("/TextFiles/TextFile1.txt",UriKind.Relative)
後は前述のとおりStreamReaderを使ってファイルの中身を取得します。
素晴らしくお手軽ですね。 以上です。 この方法を追加したプロジェクトはこちら
はじめに
(注:コード内にAssembly
クラスが出てきますが、これはSystem.Reflection
名前空間内にあるので、適宜usingしてください)
Visual Studioでアプリケーションのプロジェクトにフォルダやファイルを追加できますが、プログラム中からそれらのファイルにどうやってアクセスするか、の第2弾です。
ファイルのプロパティでビルドアクションを設定できるのですが、埋め込みリソース
やResource
、コンテンツ
と設定することでプログラムからそれらのファイルにアクセスすることができます。
以前に埋め込みリソース
なファイルへのアクセスの仕方について説明しましたが、今回はResource
なファイルへのアクセスの仕方について説明します。
ファイルを追加します
TextFiles
フォルダを追加して、その中にTextFile1.txt
を追加します。TextFile1.txt
に何か適当なものを書き込んでおきます。
ファイルのビルドアクションをResource
に設定します
ソリューションエクスプローラ上でTextFile1.txt
のプロパティを開いて、ビルドアクションをResource
に設定します。
通常、ファイルを追加した場合はデフォルトでResource
になっています。
まずは一度コンパイルします
ビルドアクションにResource
を設定したファイルはコンパイルされてアプリケーション名.g.resources
というファイルにバイナリデータとして書き込まれます。
このファイルはビルドすると作成されるので、まずは一度ビルドしてみましょう。
ビルドが正常終了したら以下のフォルダ内に先ほどのアプリケーション名.g.resources
ファイルができていることが確認できます。
ソリューションフォルダ\プロジェクトフォルダ\obj\Debug
この中にResource
に指定したテキストファイルがバイナリデータで入っているはずです。
アプリケーション名.g.resources
ファイルへのストリームを開く
何はともあれ、ファイルがあればそこへのストリームを開きます。 このプロジェクトに含まれるファイルへアクセスする場合、まずはアセンブリ(今書いているプロジェクトがコンパイルされて出来上がるexeやdllのこと)へアクセスる手段を取得します。
using System.Reflection; //~~~~~~~~~~~~~~~~ public MainWindow() { InitializeComponent(); var assembly = Assembly.GetExecutingAssembly(); }
GetExecutingAssembly()
わかりやすいメソッド名ですね。実行中のアセンブリを取得しろ、と。
そして、アプリケーション名.g.resources
へのストリームを開きます。
using (var stream = assembly.GetManifestResourceStream("AccessToResourceInProject.g.resources")) { ・・・ }
追加したテキストファイルへアクセスする
ここまででアプリケーション名.g.resources
ファイルへアクセスできるようになりました。
では、このファイルの中に入っているだろうテキストファイルへはどのようにアクセスしたらよいのでしょうか。
ここで使用するのがSystem.Resources.ResourceReaderクラス
です。
このクラスを使用してアプリケーション名.g.resources
ファイル内のリソースへアクセスするためのストリームを開きます。
using (var stream = assembly.GetManifestResourceStream("AccessToResourceInProject.g.resources")) { if (stream != null) { using (var rr = new ResourceReader(stream)) { ・・・ } } }
この変数rr
がアプリケーション名.g.resources
ファイル内のリソースファイルへアクセスするための手段となるわけです。
このResourceReaderというクラス、名前からしてさぞResourceなファイルへのアクセスを楽にしてくれそうですね。
ファイルへアクセスします
インテリセンスによると、やはりそのものずばりといったメソッドがあります。GetResourceData()
。
もうゴールは目の前な気がします。
GetResourceData()
を使う場合、以下のようになります。
resourceName
にはフォルダ名とファイル名を全部小文字にしてリソースへのパスを作って入れておきます。
ここで気を付けることはもう一つあって、フォルダ名とファイル名を「.」でつなぐのではなく「/」でつなぐということ。
string fileContent = ""; using (var stream = assembly.GetManifestResourceStream("AccessToResourceInProject.g.resources")) { if (stream != null) { using (var rr = new ResourceReader(stream)) { if (rr != null) { var resourceName = "textfiles/textfile1.txt"; string type; byte[] resourceData; rr.GetResourceData(resourceName,out type,out resourceData); if (resourceData != null) { fileContent = Encoding.UTF8.GetString(resourceData); } } } } }
GetResourceData()に必要な引数を渡して呼び出すと、resourceDataの中に指定したファイルの中身がbyte配列で入ります。 今回はテキストファイルなのでそれを文字列に変換すると以下のような値が取得できます。
あれ?
ファイルの中身は取得できているようですが、なんだか様子がおかしいです。
取得した文字列の先頭にJ\0\0\0
がついています。
これが何なのかはわかりませんが、ほしいのはこの4バイト分を除いた文字列なので、その場合は以下のようにして先頭4バイトを除いたデータを文字列に変換するとよいかもしれません。
fileContent = Encoding.UTF8.GetString(resourceData.Skip(4).ToArray());
おあつらえ向きと思ったGetSourceData()ですが、それほど使いやすくはないように感じました。
もう一つ
string fileContent = ""; using (var stream = assembly.GetManifestResourceStream("AccessToResourceInProject.g.resources")) { if (stream != null) { using (var rr = new ResourceReader(stream)) { if (rr != null) { var resourceName = "textfiles/textfile1.txt"; foreach (DictionaryEntry resource in rr) { if ((string) resource.Key == resourceName) { using (var sr = new StreamReader((Stream) resource.Value)) { fileContent = sr.ReadToEnd(); } } } } } } }
rr
はResourceReaderのインスタンスですが、これをforeachで回すと、DictionaryEntryクラスのデータを順に取得することができます。
このDictionaryEntryクラスのデータがキー/バリューのペアになっているので、Keyプロパティが目的のファイルと同じパス文字列かをチェックし、そうであればValueプロパティがStreamクラスのデータなので、StreamReaderと合わせてReadToEnd()で中身のテキストを取得できます。
全然お手軽ではないですね。
ビルドアクションに埋め込みリソース
を指定して、ここの手順でファイルを扱ったほうが楽な気がしました。
おわりに
なんだか思ってたんと違う感じになってしまいましたが、ビルドアクションにResource
を設定したファイルへのアクセス方法の一つはわかりました。
しかし、これだけ面倒ということは、まず間違いなく僕のアプローチが間違っているはずです。
正しい方法があれば、それを書きたいと思います。
今回使ったプロジェクトはこちら