restofwaterimpのぎじゅつMemo

SIerに所属してます。企画から運用までやってます。現状、プロジェクトをスクラムで回したい!と試行錯誤中です

【C#】C#プログラミングのイディオム(9章)ファイル操作

前回の続き

restofwaterimp.hatenablog.com

今回からは実践編に。 最初はファイルの操作。 アプリを構築するには外部ファイルとのやりとりがほぼある。 結構、例外とまでは行かないが、段階を踏んでファイルの読み書きをしないといけないが、 旧来のようにtry-catch-finallyではなくusingを利用しようという箇所もあった。

ファイル入力

var filePath = @"/Users/itoutomokazu/Documents/Testing/input.txt";

            /*昔はtry-finallyでリソース解放のため、Dispose()をしていた。今はusingで囲うみたい*/
            if(File.Exists(filePath)){
                //普通はEncoding.UTF8でいけるがshift_jisはGetEncodingを利用する
                using (var reader = new StreamReader(filePath, Encoding.GetEncoding("shift_jis"))) {
                    while(!reader.EndOfStream){
                        var line = reader.ReadLine();
                        Console.WriteLine(line);
                    }
                }
            }
        }

Fileのメソッドを利用する場合はReadAllLinesを利用することもできる。

docs.microsoft.com

ただ、読み切るまで、処理待ちが発生すること。 また、一度にメモリを利用するので、あまり大きなものは処理速度低下に繋がるようである。

.net 4.0以上では File.REadLinesを利用できるようです。

利用 StreamReader File.ReadAllLines File.ReadLines
戻り値 StreamReader string[] IEnumerable
取得タイミング StreamReader.ReadLine()で1行ごと ReadAllLinesでファイル全て 利用する時のみ
LINQで加工 不可 不可

/*最初の3行だけ取得*/
            var top3ines = File.ReadLines(filePath).Take(3);
            foreach(var line in top3ines)
            {
                Console.WriteLine(line);
            }

.netFrameworkが4.0以降を利用しているなら、積極的にReadLinesを利用したほうが良さそう。

ファイル出力

1行ごと出力する方法

            using(var writer = new StreamWriter(filewritePath)){
                writer.WriteLine("Get away , this floor");
                writer.WriteLine("Poo is a man.");
                writer.WriteLine("Where is you");
            }

            /*ファイルに値を追加をする場合には、appendを利用する。記載しないと上書き*/
            using (var writer = new StreamWriter(filewritePath, append:true))
            {
                writer.WriteLine("Get away , this floor");
                writer.WriteLine("Poo is a man.");
                writer.WriteLine("Where is you");
            }

docs.microsoft.com

複数行を一度の出力する場合、ファイルの読み込みと同じで、 File.WriteAllLinesを利用する。

既存のメソッドの利用では、ファイルの上書きまたは末尾に追記しかできない。 ファイルの先頭や間に差し込むことは自前で処理を用意する必要があります。 書籍には、例として、先頭に行を挿入する場合が記載されています。

            using (var stream = new FileStream(filewritePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)){
/* ファイルを途中でクローズせず、やりきること。閉じるとクリアされるから*/
                using (var reader = new StreamReader(stream)) 
                using (var writer = new StreamWriter(stream)){
                    string text = reader.ReadToEnd(); // ファイルの最初から最後まで読み,textに格納
                    stream.Position = 0;              // ポジションを先頭にする
                    writer.WriteLine("new line");     // 書き込み
                    writer.WriteLine("new Line !");
                    writer.Write("text");             // 読み込んでおいたtextを最後に書き込み
                }
            }

あるところに足すと言うより、一度退避して、再度追加したい内容とともに書き込みと言う感じです。 間に挟むとかなら、ターゲットとなる箇所までと、ターゲットとなる箇所以降で退避し、 間に挿入したい値を入れるとか。

ファイルの操作

ファイルの読み書きと同じように、ファイルを読み書きする前の確認もよく使います。 System.IO名前空間のFIle、FileInfo クラス が記載されている。

Fileを使うべきか・・・FileInfoを使うべきか・・・ということもテーマに上がっている。

書籍に書いてあるように、使っているのはFIleクラスを利用することがおおいな〜と思います。

ファイル入出力 (System.IO.File) - Programming/.NET Framework/ファイルシステム - 総武ソフトウェア推進所smdn.jp

docs.microsoft.com

docs.microsoft.com

見ると、Fileはstaticクラスなので、インスタンス化(new)が不要で、FileInfoはそうではない。 コードが少ない方、また例外処理の発生(ファイルがない・・・など)をうまく捕まえやすい方を使うのがいいかと。 いつもはFileクラス使ってますが。

ディレクトリの操作

ファイルの操作と同じくらい、ディレクトリの操作も使います。 業務アプリではファイルがあるか・・・というときに、そもそも該当のディレクトリがあるの? から始まりますので、フルパスをもらったら、Windows系なら¥でディレクトリを区切りながら フォルダチェックを行うことになります。

こちらもファイルと同じく、System.IO名前空間に DirectoryクラスとDirectoryInfoクラスがあります。

Fileと同様、基本はstatic なDirectoryクラスを利用します。

色々見て見ると、1つのフォルダやファイルに対し、複数操作を実施することがあるならば、 xxxInfoを。一つの操作のみで完結するならば、InfoをつけないFileやDirectoryを利用すると良いとのこと。 (ソース常に同じファイルをやディレクトリを何度も記載しなくて済むようにとのこと)

どちらもWindowsならdirやcd、moveなどのコマンド。 macならpwd,ls,cd,などのコマンドに相当することができます。

            /*ディレクトリに存在チェック*/
            if (Directory.Exists(@"/Users/xxxx/Documents/Testing"))
            {
                Console.WriteLine("フォルダある");
            }

            /*現在のディレクトリ取得*/
            var curdir = Directory.GetCurrentDirectory();
            Console.WriteLine(curdir);

            var di = new DirectoryInfo(@"/Users/xxx/Documents/");
            DirectoryInfo[] directories = di.GetDirectories().Where(x => x.Name.Length >=10);
            foreach(var dinfo in directories){
                Console.WriteLine(dinfo.FullName);
            }

            /*LINQで指定した条件に合致するディレクトリ一覧wお列挙*/
            var di2 = new DirectoryInfo(@"/Users/xxx/Documents/");
            var directories2 = di.EnumerateDirectories().Where(x => x.Name.Length >= 10);
            foreach (var dinfo in directories2)
            {
                Console.WriteLine(dinfo.FullName);
            }

パスの操作

取得した絶対パスやファイル名、等を組み合わせたり、バラしたりすることも Pathを利用すればできます。

            var path = @"/Users/itoutomokazu/Documents/Testing/output2.txt";
            var directoryName = Path.GetDirectoryName(path); // ファイルのディレクトリ
            var filename = Path.GetFileName(path); // ファイル名(拡張子付き)
            var extension = Path.GetExtension(path); //ファイルの拡張子
            var filenameWithoutExtension = Path.GetFileNameWithoutExtension(path); //ファイル名(拡張子なし)
            var pathroot = Path.GetPathRoot(path); //ファイルがあるディレクトリのルート

            var path_combine = Path.Combine(directoryName, filename); //ディレクトリとファイル名を組み合わせて、絶対パスを作成(引数は何個でもいい)

アプリとDB以外でファイルでのやりとりが必要な場合に必要となってくる操作。 全部すぐには覚えられませんが、System.IOにFile、Directoryがあることを覚えておけば、 リファレンスをみてなんとか対処できるかな?

つぎは10章 正規表現。知らないと、暗号にしかみえないですからね〜

つづく