ブログ「サイバー少年」

ブログ「サイバー少年」へようこそ!
小学六年生ごろからプログラミングを趣味にしている高校生のブログです。
勉強したことについての記事などを書いています。フリーソフトも制作、公開しています。
(当ブログについて詳しくは「ブログ概要紹介」を参照)

サイバー少年が作ったフリーソフトは「サイバー少年の作品展示場」へ

[C#] StreamWriterクラスと改行の注意点!

今日、C#のStreamWriterクラスでテキストファイルを書き出していて、気づいたことを書きます。

StreamWriterクラスはテキストをファイルに書き出すクラスです。


それで、StreamWriterのインスタンスを格納するwriter変数があったとして、

writer.WriteLine("改行します。");
writer.WriteLine("改行しました。");

こういうプログラムと

writer.WriteLine("改行します。\n改行しました。");

こういうプログラム、書き込む内容は一緒だと思いませんか?


私も今日までそう思っていました。

しかしなんと、違うのです!

なんとWindowsのメモ帳で両者のテキストファイルを見たところ、

前者は"改行します。"と"改行しました。"の間が正常に改行されているのに対し、
後者は間の改行がなされていませんでした!





さて、色々、実験してみたところ、前者のプログラムは

"改行します。" + CR + LF + "改行しました。" + CR + LF

となっていて、後者のプログラムは

"改行します。" + LF + "改行しました。" + CR + LF

となっていることが解りました。


つまり、どういうことかと言うと、C#の改行のエスケープシーケンスである「\n」はLFとして扱われるのです。


string str = "\n";
foreach(char ch in str.GetCharArray()) {
Console.WriteLine((ushort) ch);
}



というコードも書いて実験してみましたが、やはりstrの中身はLFだけでした。


もしかしたらコンパイラによって自由なのかもしれませんが、少なくともMicrosoftのコンパイラでやるとLFになります。


それでまぁ、調べてみたところ「\n」はLFとして処理するコンパイラが多いっぽいです。


そのため、書き込まれたテキストでは"改行します。"と"改行しました。"の間がLFだけになり、

CR + LFで改行とみなすWindowsのメモ帳では、改行なしで表示されてしまったということです。


なぜ「\n」をCR + LFではなくLFとして処理するのかというと、
恐らくchar型に1文字の改行文字として格納できるようにするためなんでしょうね。




ではどうすればいいかと言いますと、

一つは、LFではなくCR + LFとすればいいのですから、

writer.WriteLine("改行します。\r\n改行しました。");

とする方法があります。
「\r」がCR、「\n」がLFとして解釈されます。

しかし、この方法は、Windows等のCR + LFで改行する環境に依存するプログラムなってしまいますので、お勧めできません。


そこで前者のプログラムのように、

writer.WriteLine("改行します。");
writer.WriteLine("改行しました.。");

と、改行をWriteLineで行う方法があります。

WriteLineメソッドが付加する改行は、CR + LFとなるようです。
(Windowsでの話です。多分システムにあわせています。)


もう一つの方法で、

writer.WriteLine("改行します。" + System.Environment.NewLine + "改行しました。");

とする方法があります。

System.Environment.NewLineプロパティは、プログラムの実行環境で用いられる改行コードを取得するプロパティです。

つまり、WindowsではCR + LFが返ってくるわけです。


私、個人的にはこの方法がお勧めです。




ここからは考察です。

Wikipediaで「改行コード」の記事の中にこんなことが書いてありました。

改行コード - Wikipedia
http://ja.wikipedia.org/wiki/%E6%94%B9%E8%A1%8C%E3%82%B3%E3%83%BC%E3%83%89


C言語では'\n'(改行)、'\r'(復帰)の二つのエスケープシーケンスを提供している。言語処理系はこれらのエスケープシーケンスをそれぞれ異なった環境依存のchar型に収まる範囲の数字(例えばUNIXやWindows上の一般的な処理系においては、それぞれ、0x0A、0x0D)に変換する。

ただし、よく使われる出力関数のprintf、puts、入力関数のfgetsなどでは、'\n'に相当する数値(上記の例では0x0A)は特殊な値として処理される。これらの出力関数ではテキストモードで開かれたファイルにデータを出力する時、'\n'の部分がシステムで使用されている改行コード列に変換された文字列がファイルに出力される。



つまり、C言語ではprintfなどの関数に文字列を与えると、「\n」を環境にあう改行コードに変換しているということです。

WindowsならCR + LF、LinuxでならLFといった具合です。


しかし、C#のStreamWriterは環境によって解釈を変えずに、「\n」はLFとしてそのまま出力していました。

なぜなのか…。


Wikipediaの記事の続きにはこんなことも書いてありました。


Java言語でも'\n'(改行)、'\r'(復帰)の二つのエスケープシーケンスを提供している。言語処理系はこれらのエスケープシーケンスを、それぞれ、16ビットの数値0x000A、0x000Dに変換する。(Java言語の char型 は16ビットUTF-16である。)

よく使われるjava.io.PrintStreamクラスのprintメソッドやprintlnメソッド、printfメソッドは、C言語の printf 関数とは異なり、これらの数値を特別扱いせず、環境依存の改行コードに変換しない。



なんとJavaでもC#と同じく環境によって解釈を変えることはしないのです。


C#もJavaも基本的には仮想マシンで動きます。

そのため、C言語はコンパイル時に環境が判るのに対してC#やJavaはコンパイル時に環境が判りません。

つまり、C言語はシステムの改行コードがコンパイル時に判っているのに対し、
C#やJavaではコンパイル時にシステムの改行コードが判っているわけではないのです。

もしかすると、ここにヒントがあるのかもしれません。


しかし、C#のSystem.Environment.NewLineのように仮想マシン上でも実行環境の改行コードを取得することはできますから、

仮想マシンだからできないという結論に導けるかというと、微妙ですね…。

C言語と違う点といえば、改行コードの取得が静的か動的かぐらいです。


というわけで、なぜC#やJavaでは「\n」を適宜、変換しないのか、理由を知ることはできませんでした。




おまけみたいな話ですが、

C#のConsole.WriteLineメソッドでは\nでもちゃんと改行されるんですよ。

コマンド プロンプトは改行コードの扱いがメモ帳と違うのです。


そこで、コマンドプロンプトに三種の改行コードを書いて実験してみました。


・LF単体 … 改行

・CR + LF … 改行

・CR単体 … 改行せずにカーソルが行頭に戻る


という結果になりました。


CR単体のときに改行せずに行頭に戻るだけなのですから、

LF単体のときも、改行はするものの右のほうにカーソルを置いている状態になるんじゃないかと思いますが、

そこは、「\n」がLFとなっているということを察して、CR + LFと同じ挙動になっているみたいです。



というわけで、長くなりましたが、ファイルにテキストを書き出すときは改行の扱いに注意しましょう。

tag:

コメント

C#やJavaでは、プログラマがわざわざ「\n」と明示したならばそこには「\n」である意味があると
かんがえるんですかね。
環境に合わせた改行コードが使いたいなら「Environment.NewLine」を用いればよいのですし。

まあ改行コードを固定化してしまう利点と言われるとよく分からないんですが。

  • 2014/03/04(火) 22:30:50 |
  • URL |
  • div9851 #-
  • [ 編集 ]

Re: div9851

恐らくC言語での「\n」の変換はコンパイル時に行っているわけですが、C#やJavaは動的に取得するというのがミソかもしれません。

動的取得は避けたい理由があったんですかね~。動的取得でも構わないと思うのですが…。

これは、昔の、タイプライタに由来しています。
ttp://www.wdic.org/w/WDIC/CRLF

その後、黎明期に、ドットインパクトプリンターなるものがありまして、
文字の訂正線を要れる場合に2度打ちをします。最初に文字をうって、
CRでもどし、打った文字の上に線を打つ。
こういった場合に使うので別々にしてあります。

重ねうち
ttp://www.daido-it.ac.jp/~oishi/HT167/ht167.html

昔の互換性というかそういうのを考慮してあるって事ですね。

  • 2014/03/12(水) 11:33:01 |
  • URL |
  • 通りすがり #-
  • [ 編集 ]

Re: 通りすがり

CR + LFと二文字にするのは今となっては無駄ですけどね~。
互換性とは恐ろしいもので、バックスラッシュが円記号になる事件も互換性の問題で解消されていないですし。

しかし、「\n」の“n”は“new line”からきているわけですが、これで改行されないというのは詐称ですよね…。
Windowsより気の毒なのは、なぜか new line で改行せずに carriage return で改行するシステムですけどね(笑)

まぁ、最近はAndroidやMac OS XといったUnixベースのシステムが普及しているので、そのうちLFが標準になってくれるといいです。

そうですね~。
統一してくれたいいんですけど。
こういう規格って1回つくるとなかなか。
円記号は、アスキーコードの功罪ですね。
あれも、どうにもなりそうにないですね。

cr+lfは通信プロトコルで使ってるって
書いてありますし、テキストでログインする
環境がある限りはなくなりそうにないですね。
sshはよく使うし(レンタルサーバの作業では標準)、シリアルによる通信端末も
まだまだありますし。
将来的にunicodeで統一なのかなって調べてみたら、こんなものが。。。
ttp://seclan.dll.jp/cccrlf.htm
javaだと、こんな記事もあり、
ttp://www.sgnet.co.jp/java/java03_08.html
これは、unicodeを使ってるってことなのかな??

  • 2014/03/12(水) 22:58:42 |
  • URL |
  • 通りすがり #-
  • [ 編集 ]

Re: 通りすがり

Unicodeには“Line Separator”なるものがあるんですね!
知りませんでした。

Unicodeで統一されるならLSを改行の標準にすればいいでしょうけど、Unicode統一というのは難しいですからね~。

通信においてCR+LFが多いということは、LF統一も難しいところですね。

なんとかならないものか…。

円記号については、最近は強制的にバックスラッシュで統一しようとしている感があります。
日本語キーボードの円記号のキーをなくせば誤用は一気に減ると思うんですけどね…。

わお

たまたま改行コードについて調べていたら
サイバー少年さんの記事に行き着きました。

今まではなんとなく System.Environment.NewLine を使っていたのですが、
こんな理由があったのですね…
いろいろと勉強になりました。

私はC言語からプログラミングをはじめたのですが、
去年の今頃、C#を勉強して間もない頃に、
"¥r¥n" で改行すると聞いて驚いたことを思い出しました。
私的には全部 '¥n' で改行できるものだと思っていましたからね…

  • 2016/03/23(水) 20:38:58 |
  • URL |
  • aridai #-
  • [ 編集 ]

Re: わお

私も、昔WPFかなにかを調べていて自分の記事がヒットしたことありますね(笑)
まあ、当ブログのコンテンツ力がすごいというよりか、自分が調べたくなったことはすでに昔の自分が調べているというだけのことだと思いますが…。

しかし、この記事、久しぶりに見てみたら、オリジナリティにあふれた良い記事ですね。
自分で言うのもなんですが、嫉妬してしまいます。


改行コード、この記事にも書いてあるように、普通にコンソールに出力するということならLFだけでいいですし、むしろCR+LFは変だと思います。
ただ、相手がコンソールだと限らず、Windowsで扱うテキストファイルなどを意識しなければならない場合、改行をリテラルで書くのは避けたり、WriteLineを使うというのは、気をつけないといけない点ですね。

コメントの投稿

トラックバック

トラックバック URL
http://cyberboy6.blog.fc2.com/tb.php/328-01805635
この記事にトラックバックする(FC2ブログユーザー)

当ブログをご利用(閲覧等)になる場合は必ず「当ブログの利用規定」をお守りください。