「御社からもらった CSV、Excel で開いたら郵便番号の先頭の 0 が全部消えてました」— サポートにこの問い合わせが来たことのある開発者は、たぶん一度では済んでいません。

日本の業務データを CSV でやり取りしていると、同じ事故が手を変え品を変え起きます。

  • 郵便番号 001004810048 になる(0落ち
  • 商品コード 1-2-32001年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-31/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"));

これで、各セルにはファイルに書かれていた文字列がそのまま入ります。00100480010048 のまま、1-2-31-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 でも和暦でも好きな形で表示できます(和暦の出し方は 和暦対応の日付セル にまとめてあります)。

数量や金額の列は、SetRangeDataFormatNumber を宣言するか、そのまま放っておけば数値として扱えます。勘所は 「触ってほしくない列だけ 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 の事故は「気をつける」では減りません。推測の主導権を、開いた側のコードで握ること。それが唯一の確実な対策です。


次に読むもの