では、今回は分割実行ボタンのイベントハンドラについて説明していきます。
この部分は処理手順(アルゴリズム)に関する部分であり、最も重要な部分です。
今回と次回で、2通りの考え方を紹介していきますので、少々難しいかもしれませんが、お付き合いください。
分割実行ボタンのイベントハンドラ
少し長いソースコードになりましたので、考え方だけ整理しておきたいと思います。
処理フロー

以上の事を念頭に、コメントとソースコードを読んでみて下さい。
イベントハンドラのソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
private void UxExecDivide_Click(object sender, EventArgs e) { //ヘッダ行数を取得する。数値変換できなかったら0を代入 if (int.TryParse(uxHeaderCount.Text, out int head_cnt) == false) { head_cnt = 0; } //ヘッダ格納用変数の確保 string[] headers = new string[head_cnt]; //CSVファイルの読み込み List<string> lines = File.ReadAllLines(uxCsvFile.Text,Encoding.GetEncoding("shift-jis")).ToList(); //ヘッダー行を別変数にコピー for(int i = 0;i < head_cnt; i ++) { headers[i] = lines[i]; } //読み込んだCSVからヘッダー行数だけ削除 for (int i = 0; i < head_cnt; i++) { lines.RemoveAt(0); //先頭行を削除 } //分割行数の取得する。数値に変換できなければ0を代入 if (int.TryParse(uxDivideCount.Text, out int divid_cnt) == false) { divid_cnt = 0; } //分割保存関数の呼び出し DivideSave(uxOutputFolder.Text, uxCsvFile.Text, headers, lines, divid_cnt); } |
ヘッダを格納する変数はListでも良いのですが、画面から入力される値であるため、文字列配列を使っています。
処理速度やメモリー効率を考えると文字列配列を、後から項目を追加したり、間に挿入したり、ソートするのであればListになります。
しかし、この変数に格納する値は1行程度(普通のCSVはヘッダが1行)であるため、メモリー効率や処理速度を全く気にする必要はなく、本当にどちらでも構いません。
この辺は好みの問題ですし、私自身が数週間後に同じプログラムを1から書けと言われれば、Listにしているかもしれません。
DivideSaveメソッド
分割行数で分割しながらファイルに書き込む処理は、別メソッドにしました。
理由は、この処理までイベントハンドラに記述すると、やたらソースコードが長くなるのと、メソッドとして外だしにしておくほうが、別のアルゴリズムに置き換えやすいからです。
では、処理フローで概要を理解しておきましょう。
処理フロー

少しややこしいので保続しておきます。
要するに、
①ヘッダが除かれたCSVデータから順番に行を取り出し、一時保存用のListオブジェクトに追加する
②分割行数に達したところで、Listオブジェクトの内容を一気にファイルに保存する
という処理を、もう取り出すデータが無くなるまで繰り返します。
ただ、この方法だと分割行数に達する前に、データを取り出し終わる可能性がありますから、それを cnt という名前の変数を使って判断しています。
具体的には、分割行数に達する都度(ファイル保存する都度)、cnt の値をリセット(cnt = 0)していますので、ループ処理が終わった直後にcnt の値が0より大きければ、保存されていないデータが残っていると判断しています。
以上のことを念頭に、コメントとソースを読み進めてください。
DivideSaveのソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
private void DivideSave(string Folder,string fileName,string[] headers,List<string> lines,int divideCnt) { //拡張子を除いたファイル名を取得 string name = Path.GetFileNameWithoutExtension(fileName); //ファイル名から拡張子のみを取得 string ext = Path.GetExtension(fileName); //出力パス(フォルダ名+ファイル名+連番+拡張子)のテンプレートを作成 string template = Path.Combine(Folder, name + "{0}" + ext); int cnt = 0; //行数カウント変数 int file_no = 0; //ファイル連番 List<string> buff = new List<string>(); //一時保存用LIST変数 //全ての行数をループ処理 foreach (string line in lines) { //一時保存用LIST変数に行を追加 buff.Add(line); //行数カウントに1を加算 cnt++; //行数カウント変数が分割行数を超えていないかチェック if (divideCnt <= cnt) { //先頭にヘッダを挿入 buff.InsertRange(0, headers); //出力ファイル名を作成 string path = string.Format(template, file_no); //ファイル保存 File.WriteAllLines(path, buff.ToArray(), Encoding.GetEncoding("shift-jis")); //行数カウント変数をクリア cnt = 0; //ファイル連番に1を加算 file_no++; //一時保存用LIST変数をクリア buff.Clear(); } } //ファイル保存されていないデータがあるかチェック if(cnt > 0) { //先頭にヘッダを挿入 buff.InsertRange(0, headers); //出力ファイル名を作成 string path = string.Format(template, file_no); //ファイル保存 File.WriteAllLines(path, buff.ToArray(), Encoding.GetEncoding("shift-jis")); } } |
ファイル読み込みと書き込み
分割実行イベントハンドラではファイルの読み込み、DivideSaveメソッドではファイルの書き込み処理を行っています。
ファイルの読み込み、書き込みは File クラスを使用していますが、これを使うためにはソースコードの冒頭で次の using が必要になります。
1 |
using System.IO; |
以下は実際の読み込み、書き込みのサンプルです。
1 2 3 4 5 |
//ファイル読み込み File.ReadAllLines(uxCsvFile.Text,Encoding.GetEncoding("shift-jis")).ToList() //ファイル保存 File.WriteAllLines(path, buff.ToArray(), Encoding.GetEncoding("shift-jis")); |
ReadAllLinsメソッドはテキストファイル全体を改行コードで分割し、文字列配列として返してくれます。
WiteAllLines メソッドは逆に、文字列配列を渡すと、改行コードを付加してファイルに書き込んでくれます。
ここでの注意は文字コードです。
文字コードを指定しない場合はUTF-8という文字コードが採用されますので、場合によって文字化けを起こす可能性があります。
その際は、3つ目の引数に Encoding.GetEncoding(文字コード) を記述することで、文字化けを回避できます。
一般的に多いのが SHIFT-JIS なので、今回は Encoding.GetEncoding(“shift-jis”) を指定しています。
まとめ
如何でしたでしょうか。
これでソースコードに関する一通りの説明が完了しました。
少しややこしいかもしれませんが、フローチャートと見比べながら、理解して頂ければと思います。
次回は、別アルゴリズムのDivideSaveメソッドについて紹介したいと思います。