「御社からもらった CSV、Excel で開いたら郵便番号の先頭の 0 が全部消えてました」— サポートにこの問い合わせが来たことのある開発者は、たぶん一度では済んでいません。
日本の業務データを CSV でやり取りしていると、同じ事故が手を変え品を変え起きます。
- 郵便番号
0010048が10048になる(0落ち) - 商品コード
1-2-3が2001年2月3日になる(勝手に日付化) - 取引先名の漢字が
譁・喧縺・のように化ける(文字化け)
そして厄介なのは、多くの場合あなたのコードは何も悪くないことです。CSV は正しく出力されている。壊しているのは、それを開いた Excel のほうです。
本記事は、この「開いた側が壊す」問題を、自社アプリ側で起こさせない話です。例には ReoGrid を使い、C# で WinForms / WPF アプリに「壊さない」CSV ビューア/インポータを作ります。
なぜ CSV は壊れるのか — 型情報がないから
まず原因をはっきりさせます。CSV が壊れるのは Excel のバグではありません。CSV というフォーマットの構造的な宿命です。
.xlsx は、各セルに「これは文字列」「これは日付」という型の情報を一緒に保存します。一方 CSV は、ただのテキストです。次の 1 行を見てください。
001-0048,1-2-3,2026/6/6
001-0048 が郵便番号なのか引き算なのか、1-2-3 が商品コードなのか日付なのか、ファイル自身は何も言っていません。だから開いた側のアプリが「たぶんこうだろう」と推測するしかない。Excel の推測ルールはこうです。
- 数字に見えるものは数値にする → 先頭の
0は無意味なので落とす(0落ち) 1-2-3や1/2は日付に見える → 日付に変換する(日付化)- 文字コードの指定がない → 環境の既定で読む → Shift_JIS と UTF-8 を取り違えて文字化け
どれも単体では「気の利いた」挙動です。問題は、ユーザーがこの推測を止められないこと。そして、推測しているのが Excel である限り、あなたも止められません。
ここに突破口があります。推測しているのは「開いた側のアプリ」だ — なら、開く側を自分で書けば、推測ルールは丸ごと開発者の手に入ります。
エンコーディングは推測させない — 明示する
文字化けは「文字コードを取り違える」事故です。つまり、取り違えようがなければ起きません。ReoGrid の LoadCSV は読み込みエンコーディングを引数で受け取ります。
using System.Text;
using unvell.ReoGrid;
var sheet = grid.CurrentWorksheet;
// Shift_JIS の CSV を、Shift_JIS だと分かったうえで読む
sheet.LoadCSV("orders.csv", Encoding.GetEncoding("shift_jis"));
推測の余地がありません。ファイルが Shift_JIS なら Shift_JIS、UTF-8 なら Encoding.UTF8 と書くだけです。「環境の既定で読んだら化けた」という事故が、設計からなくなります。
.NET 5 / .NET Core での注意:
Encoding.GetEncoding("shift_jis")は、既定のままだとNotSupportedExceptionになります。System.Text.Encoding.CodePagesパッケージを参照し、アプリ起動時に一度だけプロバイダを登録してください。Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);.NET Framework では不要です。
型は推測させない — まず全部「素のテキスト」で読む
文字コードの次は、0落ち・日付化です。これは「数字に見えるものを数値に、日付に見えるものを日付に」という自動型判定が犯人です。ReoGrid も既定ではこの判定を行います(だからこそ、手で 1500 や数式を入力すれば、ちゃんと数値・式として扱われます)。
CSV を壊さず読むには、この自動判定を読み込みの間だけ止めます。
var sheet = grid.CurrentWorksheet;
// セル値の型を自動判定しない(= 勝手に変換しない)
sheet.SuspendCellAutoDataFormat = true;
sheet.LoadCSV("orders.csv", Encoding.GetEncoding("shift_jis"));
これで、各セルにはファイルに書かれていた文字列がそのまま入ります。0010048 は 0010048 のまま、1-2-3 は 1-2-3 のまま。一旦すべてを「素のテキスト」として読み込んでから、型はこちらが宣言します。
💡
LoadCSVは読み込み前にシートをリセットします。つまり読み込む前に列の書式を設定しても消えます。正しい順番は「自動判定を止める → 読む → 列の型を宣言する」です。
型は宣言する — 列ごとに「これは文字列」「これは日付」
ここからが、Excel にはできない部分です。どの列が何型か、開発者は知っている。その知識をコードに落とします。
読み込みが終わったら自動判定を元に戻し、列ごとに書式を宣言します。
using unvell.ReoGrid.DataFormat;
// 1) 読み込み中は変換させない
sheet.SuspendCellAutoDataFormat = true;
sheet.LoadCSV("orders.csv", Encoding.GetEncoding("shift_jis"));
// 2) 判定を元に戻してから、列の型を宣言する
sheet.SuspendCellAutoDataFormat = false;
// A 列=郵便番号, B 列=商品コード → 文字列として確定(0落ち・日付化を封じる)
sheet.SetRangeDataFormat("A:A", CellDataFormatFlag.Text);
sheet.SetRangeDataFormat("B:B", CellDataFormatFlag.Text);
// E 列=受注日 → 日付として確定。表示書式もこちらが決める
sheet.SetRangeDataFormat("E:E", CellDataFormatFlag.DateTime,
new DateTimeDataFormatter.DateTimeFormatArgs { Format = "yyyy/MM/dd" });
CellDataFormatFlag.Text を宣言した列は、中身が何に見えようと文字列として扱われます。0010048 の先頭ゼロも、1-2-3 のハイフンも、誰も触りません。"A:A" のような列全体のアドレスが使えるので、行数を気にせず列単位で宣言できます。
日付列は逆に、日付だと分かっているからこそ意図した書式で出せます。1-2-3 を Excel が「2001/2/3」と読むのは当てずっぽうでしたが、こちらは E:E が受注日の列だと知っているので、yyyy/MM/dd でも和暦でも好きな形で表示できます(和暦の出し方は 和暦対応の日付セル にまとめてあります)。
数量や金額の列は、SetRangeDataFormat で Number を宣言するか、そのまま放っておけば数値として扱えます。勘所は 「触ってほしくない列だけ Text で守る」 こと。守りたい列を名指しできるのが、推測任せの Excel との決定的な差です。
書き出すときも、エンコーディングはこちらが決める
受け取るだけでなく、出すときも同じ理屈です。ExportAsCSV はエンコーディングを指定できます。
// レガシーな基幹システムが Shift_JIS しか受け付けない → Shift_JIS で出す
sheet.ExportAsCSV("export_sjis.csv", encoding: Encoding.GetEncoding("shift_jis"));
// Excel ユーザーに渡す UTF-8 → BOM 付きで出す
sheet.ExportAsCSV("export_utf8.csv", encoding: new UTF8Encoding(true));
2 つ目の new UTF8Encoding(true) がポイントです。UTF-8 で書いた CSV を Excel で開くと、BOM がないと Excel は Shift_JIS だと思い込んで文字化けさせます。new UTF8Encoding(true) は先頭に BOM を付けて出力するので、Excel が UTF-8 だと認識できます。「UTF-8 で出したのに Excel だけ化ける」の定番原因がこれです。
相手のシステムが要求する文字コードで出す — その判断もまた、出す側のコードが握っています。
そもそも CSV で渡さない、という選択
最後に、一段引いた視点を。ここまでの工夫は全部、「自分のアプリが読む/書くとき」に効きます。でも、出力した CSV を相手がさらに Excel で開くなら、また相手の Excel が推測を始めます。こちらがどれだけ丁寧に書いても、CSV である限り型情報はファイルに乗りません。
相手が Excel ユーザーで、かつ 0落ちや日付化を確実に防ぎたいなら、答えはシンプルです — CSV ではなく .xlsx で渡す。.xlsx はセルごとに「これは文字列」という書式をファイルの中に持てるので、相手が開いても Excel は推測せず、その書式に従います。ReoGrid は宣言した書式そのままで .xlsx を保存できます(Excel ファイルの表示・編集 を参照)。
CSV は軽くて互換性が高い反面、「型を運べない」フォーマットです。システム間連携ならエンコーディングを固定した CSV、人が Excel で開くなら型を持てる .xlsx — 用途で使い分けるのが、結局いちばん壊れません。
まとめ
- CSV が壊れるのは Excel のバグではなく、型情報を持たないファイルを開いた側が中身を推測するから
- 推測しているのは「開く側のアプリ」。だから開く側を自分で書けば、ルールは開発者の手に入る
- エンコーディングは明示する —
LoadCSV(path, Encoding.GetEncoding("shift_jis"))。.NET Core ではCodePagesEncodingProviderの登録を忘れずに - 型の自動判定は止めてから読む —
SuspendCellAutoDataFormat = trueで、素のテキストとして読み込む - 型は列ごとに宣言する — 守りたい列は
CellDataFormatFlag.Text、日付列は書式付きでDateTime - 書き出しもエンコーディング指定可。Excel に渡す UTF-8 は BOM 付き(
new UTF8Encoding(true))にしないと化ける - 相手が Excel で開くなら、いっそ型を運べる
.xlsxで渡す
CSV の事故は「気をつける」では減りません。推測の主導権を、開いた側のコードで握ること。それが唯一の確実な対策です。
次に読むもの
- データ書式 — Text / Number / DateTime / Currency など書式 API の全体像
- 和暦(令和)対応の日付セル — 読み込んだ日付列を和暦で表示する
- C# で WinForms / WPF アプリに Excel ファイルを表示・編集する — 型を運べる
.xlsxの読み書き - Office Interop を使わずに C# で Excel ファイルを読み書きする — ライブラリ選定の比較
