【C#】ファイルの読み書き基礎と実践。コピペで使える実用サンプル満載!

当ページのリンクには広告が含まれています。

本記事は、.NET と C# を使ったテキストファイルの読み書きと、ランダムアクセスによるバイナリファイルの読み書きについて解説しています。

ひとくちにファイルの読み書きと言っても、.NET には似たような派生クラスが数多く存在し、また使えるメソッドも多岐に渡ります。

そこで、今回はその中で最もメジャーな Fileクラス、StreamReaderクラス、StreamWriterクラス、FileStreamクラスの4種類を取り上げ、その中でも最もよく使われるメソッドに限定して、簡単なサンプルプログラムと図を交ぜて出来るだけ分かり易く解説ししました。

また、記事の最後には実践を意識したサンプルをいくつかご用意しました。コピペして使えるように関数化していますので、ファイルを扱う必要が生じたときは、是非この記事をご利用下さい。

フォルダやファイルを操作(コピー、削除、移動)するクラスについては、「【実践】C#ファイル/フォルダ操作術。すぐに使えるサンプルコード付き」で詳しく紹介していますので、併せてご活用下さい。

目次

.NETのファイル関連クラスで理解すべきポイント

.NETで用意されているファイル関連のクラス

.NET FileIO の全体図

.NET においては、ファイルを読み書きする代表的なクラスとして、Fileクラス、StreamReaderクラス、StreamWriterクラス、FileStreamクラスが用意されています。

クラス名概要
Fileファイルやディレクトリの作成、コピー、削除など、ファイルシステムに対する操作を提供するクラスであるが、指定した文字列や配列データをまとめてファイルに書き込んだり、読み込むメソッドが用意されている。
StreamReaderテキストファイルからテキストを読み込むためのクラス。
StreamWriterテキストデータをファイルに書き込むためのクラス。
FileStreamバイナリデータに対してランダムアクセスを行うためのクラス。

これらのクラスは System.IO 名前空間に含まれていますので、プログラムの冒頭で名前空間の参照設定が必要になります。また、テキストファイルを扱う場合は文字コードの指定も必要になってくるので、併せて System.Text 名前空間も参照設定しておきます。

具体的には次の2行をプログラムの冒頭に記述します。

File,StreamReader,StreamWriter,RIleStream クラスの継承関係

File,StreamReader,StreamWriter,RIleStream クラスの継承関係の説明図

上図はクラスの継承関係図です。Object は.NET の最上位クラスであり、その直下にFileクラスが存在します。StreamReaderクラス、StreamWriterクラス、FileStreamクラスは、このFileクラスから派生しているのかと思われそうですが、実は別のクラスから派生しています。

ファイルを開く(Open)、閉じる(Close)

ファイルのオープンとクローズのイメージ図

StreamReaderクラス、StreamWriterクラス、FileStreamクラスを使う場合は、読み書きする直前にファイルを開き(Open)、処理が終わったら閉じる(Close)必要があります。

それぞれのクラスを new した時点でファイルが開かれますが、閉じる処理は Closeメソッドを呼ぶ必要があります。

ファイルを読み書きしている際に例外が発生したり、プログラムの不具合により Closeされないケースが発生すると、プログラムが終わってもファイルが開かれたままになり、ファイルにアクセスできなくなります。

この様な事態を避けるため、using を使って次の様に記述することが推奨されています。こう書いておけば自動的に必ずCloseメソッドが呼ばれるようになります。


ここで登場する using は名前空間の指定とは全く別の意味で使われるもので、ファイルのクローズだけではなく、データベースやネットワーク通信においても使われる記述方法です。

文字コードの指定方法

読み書きするファイルがテキストファイルの場合、文字コードの指定が必要となります。
多くの場合、ファイル読み込み系メソッドでは第2引数、書き込み系メソッドでは第3引数に文字コードが指定できますが、省略した場合は UTF-8 が選択されたことになります。

任意の文字コードを指定する場合、Encoding クラスを使用しますが、これは System.Text 名前空間に静的メソッドとして用意されていますので、using System.Text の記述が必要です。

Encoding クラスを使う場合、プロパティで指定する方法と GetEncoding メソッドを使う方法の2通りがあり、両者は微妙にサポートする文字コードが異なります。

文字コードの指定方法文字コードの指定方法
Encodingのプロパティを使う方法Encoding.UTF-8
Encoding.UTF-7
Encoding.UTF-32
Encoding.ASCII
など
EncodingのGetEncodingメソッドを使う方法Encoding.GetEncodings("utf-8")
Encoding.GetEncodings("utf-16")
Encoding.GetEncodings("shift-jis")
Encoding.GetEncodings("uft-32")
など

尚、GetEncoding メソッドで指定可能な文字コード名については、次のコードで取得することが可能です。

1200,Unicode,utf-16
1201,Unicode (Big-Endian),utf-16BE
12000,Unicode (UTF-32),utf-32
12001,Unicode (UTF-32 Big-Endian),utf-32BE
20127,US-ASCII,us-ascii
28591,Western European (ISO),iso-8859-1
65001,Unicode (UTF-8),utf-8

.NET CORE では サポート外のSHIFT-JISを指定できるようにする

.NET Framework では問題なかったのですが、.NET CORE からシフトJISがサポート外となり、 GetEncodingsメソッドで "shift-jis" が指定できなくなりました。

しかし、下記の1行を記述しておくことで、Shift-jisをはじめ数多くの文字コードがサポートされるようになります。

GetEncodingsメソッドを呼ぶ前であれば記述する場所はどこでもよく、GetEncodingsの直前でも良いですし、プログラムの先頭やコンストラクタの中に記述しても構いません。

ファイルの読み込み

ファイルを読み込むには、Fileクラス を使う方法と StreamReaderクラスを使う方法の2通りがあります。
Fileクラスは、ファイルを一括で読み込んでメモリ内で処理したい場合に適しており、StreamReaderはファイルから1行づつ読み込みながら処理したい場合に使います。

とはいえ、両者ともよく似た動作のメソッドが用意されているので、ほぼ同じことが出来るようになっています。

Fileクラスを使った読み込む

Fileクラスを使ったファイル読み込みのイメージ図

Fileクラスを使うと、指定したファイルの内容を一括で読み込むことができます。ReadAllTextとReadAllLinesの違いは、読み込んだ結果を1つの文字列として返すか、改行で分割して配列として返すかの違いです。
この2つは全てメモリに読み込もうとするため、メモリが少ないとメモリオーバーフローの例外が発生します。

一方、ReadLines は 一括で読み込むのではなく、IEnumerable<string> 列挙型として返してくれます。従って、foreach文の中で1行づつ取り出して、条件に一致するものだけメモリ内に取り込むといった場合に使用します。

メソッド戻り値の型説明
ReadAllText(
string path,
Encoding encoding
)
string 指定されたファイルのテキストをすべて読み取り、1つの文字列として返します。
ReadAllLines(
string path,
Encoding encoding
)
string[] 指定されたファイルを読み込み、改行で分割して配列で返します。
ReadLines(
string path,
Encoding encoding
)
IEnumerable<string> 指定されたファイルの各行を列挙可能な文字列として返します。foreach ループで1行づつ読み込みたい場合に使います。

StreamReaderクラスを使った読み込み

StreamReaderを使ったファイル読み込みのイメージ図

StreamReaderクラスは、ファイルから1行づつ読み込んで処理したい場合に用います。
File クラスの ReadLineメソッド動作は同じですが、StreamReaderクラスを実行した後は、必ず閉じる(Close)処理が必要になります。

メソッド戻り値の型説明
ReadLine(
string path,
Encoding encoding
)
string指定されたファイルから1行読み取ります。全ての行を読み終えると null を返します。File.ReadLines() と同じ動作です。
ReadToEnd(
string path,
Encoding encoding
)
string 指定されたファイルを最後まで読み込み、1つの文字列として返します。File.ReadAllText() と同じ動作です。
Close()なしファイルを閉じます。
プロパティ説明
EndOfStreamファイルの終端に達すると true を返します。

ReadLineは呼ぶ毎にテキストファイルから1行づつ取り出すメソッドなので、全ての行を取り出す為にループが必要になります。


全ての行を取り出し終わると ReadLineは null を返しますが、同時に EndOfStream プロパティも true となるため、そのどちらかを while ループの終了条件として利用します。

ReadLine()の戻り値を使ったループのサンプル

EndOfStreamを使ったループのサンプル

一方、ReadToEnd は1度呼ぶだけでファイルの中身を全て読み込んで文字列として返してくれますが、File.ReadAlLTextメソッドの方が1行で書けるので、ReadToEndを使うメリットはほぼ無いと思われます。

ファイルの書き込み

ファイルに書き込むには、Fileクラス を使う方法と、 StreamWriterクラスを使う方法の2通りがあります。
Fileクラスは文字列変数や文字列配列に保持した内容を、簡単にファイル書き込みする場合に使用します。一方、StreamWriterクラスは、何らかの処理をした結果を順次ファイルに書き込みたい場合に使用します。

Fileクラスによる書き込み

Fileクラスを使ったファイル書き込みのイメージ図

Fileクラスを使うと、文字列や配列の中身を1行でファイルに書き込むことが可能です。
文字列の中身をファイルに書き込みたい場合は WriteAllText を、リストや配列など列挙型(IEnumerable)のデータを書き込みたい場合は WriteAllLines を使用します。

メソッド戻り値の型説明
WriteAllText(
string path,
string content,
Encoding encoding
)
なし 指定されたファイルに、指定された文字列のコレクションの各要素を各行として書き込みます。ファイルが存在しない場合は新規に作成し、すでに存在する場合は上書きします。
WriteAllLines(
string path,
IEnumerable<string> content,
Encoding encoding
)
なし指定されたファイルに指定された文字列を書き込みます。ファイルが存在しない場合は新規に作成し、すでに存在する場合は上書きします。

StreamWriterクラスによる書き込み

StreamWriteによる書き込みのイメージ図

StreamWriter クラスは、1行づつファイルに書き込む場合に用います。
WriteメソッドとWriteLineメソッドはともに引数で指定された文字列をファイルに書き込むメソッドですが、Writeメソッドはそのまま書き込むのに対し、WriteLineメソッドは自動的に改行コードを末尾に付加して書き込みます。

メソッド戻り値の型説明
Wite(
object content
)
string引数で指定された値をそのままファイルに書き込みます。
引数は object 以外にも、int ,long ,double,string など様々な型が指定できます。
WriteLine(
 object content
)
string 引数で指定された値の末尾に改行コードを付加し、ファイルに書き込みます。
引数は object 以外にも、int ,long ,double,string など様々な型が指定できます。
Flush()なしオペレーティングシステムの都合により書き込みが遅延させられているデータを、強制的にファイルに書き込みます。
Close()なしファイルを閉じます。

例えば、0~5までの数値を witeメソッドで書き込む場合、次の様になります。

01234

0~5までの数値を witeLineメソッドで書き込むと、次の様になります。

0
1
2
3
4

Writeメソッド、WriteLineメソッドで便利な点は、引数の型を選ばないというところです。
FileクラスのWriteメソッドやWriteLinesメソッドは文字列型しか指定できませんでしたが、StreamWriterにおいて様々な型を直接指定できます。

100
3.1235676
ABCDE
00:00:00.0000060
2024/02/17 19:03:34

ちなみに、File.WriteLines メソッドは文字列配列が指定可能でしたが、StreamWrite.WriteLine はあくまでも1つの値しか指定できません。リストや配列を渡すと期待した通りの結果にはならないのでご注意ください。

System.String[]
System.Collections.Generic.List`1[System.Int32]

ランダムアクセスによるファイルの読み書き

ランダムアクセスのイメージ図

これまでに登場したファイルの読み書きは、ファイルの先頭から末尾にかけて順番に取り出す(=シーケンシャルアクセス)方法でした。ンダムアクセスはファイルに対して位置とサイズを指定して読み書きする方法です。

ランダムアクセスにおけるアクセス位置の指定

SeekとReadによるアクセス位置とサイズの指定方法の概要図

読み書きする位置(=アクセス位置)は Seek メソッドを使って指定します。一方、サイズはReadメソッド、書き込むサイズはWriteメソッドの引数で指定します。

Seekメソッドでアクセス位置を指定するには、第一引数(offset)に基準位置からの相対距離、第二引数に基準位置(SeekOrigin.Begin , SeekOrigin.Curent , SeekOrigin.End)を指定します。例えばファイル末尾から数えて100バイトの位置を指定する場合、Seek(100,SeekOrigin.End)と記述します。

基準位置は省略可能で、省略した場合は SeekOrigin.Begin が指定されたと見なされます。

SeekOrigin.Beginファイルの先頭を基準にする
SeekOrigin.Curent 現在のカレント位置を基準にする
SeekOrigin.End ファイルの末尾を基準にする

注意が必要なのは SeekOrigin.Curent を指定した場合です。ファイルに対してSeekメソッドを実行すると、その位置(基準位置+offset)がカレント位置になります。
しかし、そこでRead メソッドや Writeメソッドを実行すると、カレント位置に読み書きしたサイズが加算されてしまうのです。

Seekを使った場合のカレント位置の変化イメージ

上記の例では、一旦 Seekメソッド で①にカレント位置が移動しますが、Readメソッドを実行したことで②がカレント位置となり、Writeメソッドを実行した時点で③にカレント位置が設定されます。

例えば、特定の位置の値に対して加算した結果を元の位置に書き込みたい場合は、読み込み時のカレント位置と書き込み時のカレント位置を合わせる必要があります。

ランダムアクセスのためのファイルの開き方

FileStreamを使ったランダムアクセスのFileMode指定方法

ランダムアクセスは読み書きを同時に行うことが前提であるため、FileStreamクラスを new するだけで読み書き可能になります。この時、第1引数にファイル名を、第2引数にファイルモードを指定します。

ファイルモードには次の値が指定できます。

ファイルモード説明
FileMode.Createファイルが存在しない場合は作成し、存在する場合は上書きします。
FileMode.CreateNewファイルが存在しない場合は作成し、存在する場合は例外を発生します。
FileMode.Openファイルを開きます。ファイルが存在しない場合は例外を発生します。
FileMode.OpenOrCreateファイルが存在する場合は開き、存在しない場合は作成します。
FileMode.Truncateファイルを開き、ファイルサイズを 0 にします。
FileMode.Appendファイルを末尾に追加モードで開きます。ファイルが存在しない場合は作成します。

ランダムアクセスで用いられるメソッドは次のものがあります。

メソッド戻り値の型説明
Seek(
long offset,
SeekOrigin origin
)
Int64origin と offset で指定した位置にアクセス位置を設定します。
戻り値として設定後のアクセス位置を返します。
Read(
byte[] buffer,
int offset,
int count
)
なし 指定されたバッファに、指定されたオフセット位置から指定されたバイト数を読み込みます。
Write(
byte[] buffer,
int offset,
int count
)
なし指定されたバッファから、指定されたオフセット位置から指定されたバイト数を書き込みます。
Flush()なしオペレーティングシステムの都合により書き込みが遅延させられているデータを、強制的にファイルに書き込みます。
Close()なしファイルを閉じます。

ランダムアクセスを使ったファイルの読み込み

ランダムアクセスによるファイルの読み込みイメージ

ランダムアクセスファイルの指定位置からデータを読み込むにはReadメソッドを使います。
Readメソッドには引数が3つあり、1つ目は読み込んだデータの保存先(下図の例では buff 変数)、2つ目はbuffへの書き込み位置、3つ目は ファイルから読み込むサイズ=buffに書き込むサイズを指定します。

下記はファイルの先頭から3バイトだけ読み込んで、buffの先頭から3バイト分を書き込むサンプルです。

下記はSeekと組み合わせたサンプルです。ファイルの先頭から数えて5バイト目の位置から、Read で3バイト読み込んで、 buff の先頭に書き込んでいます。

ランダムアクセスを使ったファイルの書き込み

ランダムアクセスによるファイルの書き込みイメージ

メモリ(buff変数)に格納されている内容をランダムアクセスファイルに書き込むには Writeメソッドを使用します。
Writeメソッドには引数が3つあり、1つ目は書き込みたいデータが格納されている変数、2つ目は変数からの読み出し位置、3つ目はファイルに書き込みたいサイズを指定します。

下記はbuff 変数の先頭(0バイト目)から3バイトを取り出し、ファイルの先頭に書き込むサンプルです。

下記はSeekと組み合わせたサンプルです。buff の先頭から3バイト分を取り出し、ファイルの先頭から数えて5バイト目の位置にWrite で書き込んでいます。

巨大なファイルを扱うサンプル

巨大なファイルを取り扱う場合、全ての内容をメモリに読み込むことは出来ません。
「メモリが許す範囲のデータサイズだけ読み込んで処理し、結果をファイルに書き出す」という処理をループで繰り返す必要があります。

この章では、下記の4つのケースについてテンプレートとなるプログラムを紹介します。

  • 1つのファイルを加工して別のファイルに出力する
  • 1つのファイルを指定行数ごとに分割する
  • 複数のファイルを1つのファイルに結合する
  • 通常のテキストエディタでは開けない巨大なファイルの一部を表示する。

1つのファイルを加工して別のファイルとして出力する

input_fileで指定されたファイルの中身を func 関数で編集し、output_fileで指定したファイルに書き込むサンプルプログラムです。func を省略すると、単なる文字コード変換プログラムとして動作します。

func の戻り値がそのまま output_file に書き出されるので、 func 内で必要な編集を行なって下さい。
特定の条件に合致する行を省いてoutput_file に書き込みたい場合は、func の戻り値として nullを返せば実現可能です。

このクラスを使ってファイルに含まれるアルファベットを大文字に変換する場合は、次の様に書くことができます。

1つのファイルを指定行数ごとに分割する

指定した行数でファイルを分割するサンプルプログラムです。分割対象のファイルがCSV形式の場合、ヘッダを指定することで、分割されたファイルにもヘッダを含めることが出来ます。

このクラスを使って、ヘッダを1行持つCSVファイルを30行毎に分割する場合、次の様に記述できます。

複数のファイルを1つのファイルに結合する

input_file で指定されたファイル名末尾が連番のファイルを output_file に結合するサンプルプログラムです。
CSVファイルの結合にも対応していて、2つ目以降のファイルについては、header_count で指定した行数分を読み飛ばすようにしています。


input_file に "hoge.txt" を指定すると、 "hoge*.txt" のワイルドカードに一致するファイルを結合しようとするため、"hogehage.txt" や "hogemage.txt" などファイル名の先頭が一致するものは全て結合されてしまうことにご注意ください。

このクラスを使って "hoge2*.txt"のワイルドカードに一致するヘッダ付きCSVファイルを結合する場合、次の様に記述できます。

通常のテキストエディタでは開けない巨大なファイルの一部を表示する。

ランダムアクセスを使って、メモリに読み込めないような巨大なファイルから一部を取り出して表示するサンプルプログラムです。

このクラスを使って "hoge.dll" というバイナリファイルの先頭から256バイトを16進表記で表示する場合、次の様に記述できます。

00000000 | 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 | MZ…………..
00000010 | B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 | ……..@…….
00000020 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | …………….
00000030 | 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 | …………….
00000040 | 0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68 | ……..!..L.!Th
00000050 | 69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F | is program canno
00000060 | 74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20 | t be run in DOS
00000070 | 6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00 | mode….$…….
00000080 | 50 45 00 00 4C 01 03 00 5D EE 5F 81 00 00 00 00 | PE..L…]._…..
00000090 | 00 00 00 00 E0 00 22 20 0B 01 30 00 00 F2 01 00 | ……" ..0…..
000000A0 | 00 06 00 00 00 00 00 00 66 10 02 00 00 20 00 00 | ……..f…. ..
000000B0 | 00 20 02 00 00 00 00 10 00 20 00 00 00 02 00 00 | . ……. ……
000000C0 | 04 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 | …………….
000000D0 | 00 60 02 00 00 02 00 00 00 00 00 00 03 00 60 85 | …………….
000000E0 | 00 00 10 00 00 10 00 00 00 00 10 00 00 10 00 00 | …………….
000000F0 | 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 | …………….

まとめ

今回は.NET と C#を使って、テキストファイルの読み書きとバイナリファイルのランダムアクセスについて解説しました。

今回紹介したクラスには次の特徴があります。

  • FIleクラス
    メモリ内の文字列データを一括してファイルに書き込んだり、読み出したりするときに使う。
    1行記述するだけでファイルへの読み書きが実現出来る。
  • StreamReaderクラス、StreamWriterクラス
    ファイルから1行づつ読み込んで処理したり、逆に書き込みたいときに使う。
    new した後は必ず Closeメソッドでファイルを閉じる必要がある。
  • FileStreamクラス
    バイナリファイルをランダムアクセスし、任意の場所を読み書きしたい場合に使う。
    new した後は必ず Closeメソッドでファイルを閉じる必要がある。

最後の章では巨大なファイルが扱えるよう、極力メモリを使わない方法を用いてファイルの分割、ファイルの結合を行うサンプルと、通常のテキストファイルでは開けないようなファイルに対して、ランダムアクセスで指定した一部のみを取り出して画面に出力するサンプルプログラムを紹介しました。

サンプルプログラムはコピペして使うことを考慮して関数化してあり、また手を加えやすいようコメントも多めに書きました。皆さんのニーズに合った形で修正のうえ、ご利用いただければと思います。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次