C#でのプログラミングにおいて、文字列の操作は避けて通れない要素です。
通常、文字列の結合にはstring.ConcatやStringBuilderが使われますが、ストリーム(Stream)に近い感覚で文字列を書き込んだり、読み込んだりしたい場面があります。
そのような時に重宝するのがStringWriterクラスとStringReaderクラスです。
これらは、メモリ上の文字列をあたかも「ファイル」のように扱うための橋渡し役として機能します。
本記事では、初心者から中級者の方に向けて、これら2つのクラスの基本的な概念から、実務で役立つ応用的な活用法までを詳しく解説していきます。
C#におけるStringWriterとStringReaderの役割
C#の入出力処理(I/O)では、TextWriterやTextReaderという抽象クラスが基盤となっています。
これらは本来、ファイルやネットワークストリームに対して文字を書き込んだり読み込んだりするための共通インターフェースを提供します。
StringWriterとStringReaderは、この入出力の仕組みを「文字列(string)」に対して適用したものです。

TextWriterとTextReaderの継承関係
まず理解しておくべきは、これらがどのクラスを継承しているかという点です。
StringWriterはTextWriterを、StringReaderはTextReaderをそれぞれ継承しています。
| クラス名 | 親クラス | 役割 |
|---|---|---|
| StringWriter | TextWriter | 文字列(StringBuilder)に対してテキストを書き込む |
| StringReader | TextReader | 文字列からテキストを1文字、または1行ずつ読み込む |
この継承関係のおかげで、「ファイルへの書き込みを想定して作られたメソッド」に対して、ファイル名の代わりにStringWriterを渡すことで、結果を直接文字列として受け取るといった柔軟なプログラミングが可能になります。
StringWriterの基本的な使い方と仕組み
StringWriterは、内部でStringBuilderを保持しており、そこに文字を蓄積していく仕組みになっています。
主に、複雑なフォーマットの文字列を構築する場合や、ストリーム出力を要求する外部ライブラリとの連携に使用されます。

StringBuilderとの密接な関係
StringWriterのコンストラクタには、既存のStringBuilderを渡すことも可能です。
何も指定しない場合は、内部で自動的に新しいインスタンスが作成されます。
基本的な書き込みのコード例
以下のサンプルコードでは、StringWriterを使用して複数の行を書き込み、最終的に一つの文字列として出力する方法を示します。
using System;
using System.IO;
using System.Text;
class Program
{
static void Main()
{
// StringWriterのインスタンスを生成
// usingステートメントを使用することで確実にリソースを解放します
using (StringWriter writer = new StringWriter())
{
// 文字列の書き込み
writer.WriteLine("--- レポート開始 ---");
writer.WriteLine("作成日: {0}", DateTime.Now.ToString("yyyy/MM/dd"));
writer.WriteLine("ステータス: 正常");
// 追記
writer.Write("詳細情報: ");
writer.Write("システムチェック完了。");
// 内部のStringBuilderに蓄積された内容をstringとして取得
string result = writer.ToString();
// 結果を表示
Console.WriteLine(result);
}
}
}
--- レポート開始 ---
作成日: 2026/01/16
ステータス: 正常
詳細情報: システムチェック完了。
なぜStringBuilderではなくStringWriterを使うのか
一見すると「StringBuilderで十分ではないか」と感じるかもしれません。
しかし、StringWriterの真価は、TextWriterを引数に取る既存のメソッドを活用できる点にあります。
例えば、XMLのシリアライズ(オブジェクトをXML形式の文字列に変換する処理)を行う際、多くのライブラリは出力先としてTextWriterを要求します。
ここでStringWriterを渡すことで、ファイルを作らずにメモリ内でXML文字列を生成できるのです。
StringReaderによる文字列の効率的な読み込み
次に、StringReaderについて見ていきましょう。
これはStringWriterとは逆で、既存の巨大な文字列をストリームとして扱い、1行ずつ、あるいは特定の文字数ずつ読み取るために使用します。

行単位での読み込み処理
例えば、ログデータやCSV形式の文字列を受け取った際、string.Split('\n')を使って配列に分割することもできますが、非常に大きな文字列の場合、配列を作成するとメモリを大量に消費してしまいます。
StringReaderを使えば、メモリ効率良く1行ずつ処理できます。
文字列の読み取りコード例
using System;
using System.IO;
class Program
{
static void Main()
{
string multiLineData = "ID,名前,スコア\n1,田中,85\n2,佐藤,92\n3,鈴木,78";
// StringReaderを使用して文字列を読み込む
using (StringReader reader = new StringReader(multiLineData))
{
string line;
int lineCount = 0;
// ReadLine()は行がなくなるとnullを返します
while ((line = reader.ReadLine()) != null)
{
lineCount++;
Console.WriteLine("行 {0}: {1}", lineCount, line);
}
}
}
}
行 1: ID,名前,スコア
行 2: 1,田中,85
行 3: 2,佐藤,92
行 4: 3,鈴木,78
ReadメソッドとPeekメソッド
StringReaderには、行単位以外にも詳細な読み取りメソッドが用意されています。
Read(): 次の1文字を読み込み、文字コードを返します。読み取る文字がない場合は-1を返します。Peek(): 次の1文字を確認しますが、読み取り位置を進めません。「次に何が来るか」を確認してから処理を分岐させたい時に便利です。ReadToEnd(): 現在の位置から最後までを一気に読み込みます。
応用編:外部ライブラリやシリアル化での活用
実務で最もStringWriterが登場する場面の一つが、シリアル化(Serialization)です。
ここでは、C#標準のXmlSerializerを用いた例を紹介します。

XMLシリアライザーとの連携
通常、XmlSerializer.Serializeメソッドは、第1引数に出力先のストリームやTextWriterを求めます。
ここにStringWriterを指定します。
using System;
using System.IO;
using System.Xml.Serialization;
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
class Program
{
static void Main()
{
User user = new User { Name = "Tech太郎", Age = 25 };
// XMLシリアライザーの準備
XmlSerializer serializer = new XmlSerializer(typeof(User));
// StringWriterを使ってメモリ内でXMLを作成
using (StringWriter sw = new StringWriter())
{
serializer.Serialize(sw, user);
// 生成されたXMLを表示
string xmlContent = sw.ToString();
Console.WriteLine("生成されたXML:");
Console.WriteLine(xmlContent);
}
}
}
<?xml version="1.0" encoding="utf-16"?>
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Tech太郎</Name>
<Age>25</Age>
</User>
このように、本来ファイル出力を想定しているAPIに対して、「出力を文字列として横取りする」というテクニックは非常に強力です。
これは単体テスト(Unit Test)で、コンソール出力やファイル出力を検証したい場合にも応用できます。
StringWriter/ReaderとStringBuilderの使い分け
最後に、よくある疑問である「いつ何を使えばいいのか」を整理します。
StringBuilderは純粋な文字列の加工に特化していますが、StringWriterはその加工を「ストリーム」という枠組みで行うためのものです。

| 特徴 | StringBuilder | StringWriter / StringReader |
|---|---|---|
| 主な用途 | 文字列の頻繁な結合・編集 | ストリームベースのAPIとの連携 |
| 抽象化 | 文字列操作に特化 | TextWriter / TextReaderとして振る舞う |
| メモリ効率 | 高い | 非常に高い(StringReaderでの逐次読み込み時) |
| 主なメソッド | Append, Insert, Replace | Write, WriteLine / ReadLine, ReadToEnd |
使い分けの指針:
- ループ内で単純に文字列を繋げたいだけなら、StringBuilderを選びます。
TextWriterやTextReaderを引数に取るメソッド(SerializeやSaveなど)を使いたいなら、StringWriterを選びます。- 複数行にわたる巨大な文字列を、配列化せずに一行ずつ丁寧に処理したいなら、StringReaderを選びます。
特に現代的なアプリケーション開発では、Web APIのレスポンスや設定ファイルの処理など、文字列データをストリームとして扱う機会が増えています。
これらのクラスを正しく使い分けることで、メモリ消費を抑えた、拡張性の高いコードを書くことができるようになります。
まとめ
StringWriterとStringReaderは、一見すると地味なクラスですが、C#のI/Oシステムと文字列操作を繋ぐ非常に重要な役割を担っています。
StringWriterを使えば、ファイルへの書き込みと同じ作法で、メモリ上に柔軟な文字列を構築できます。
特にXMLやJSON、カスタムログの生成において、既存のTextWriter向けAPIをそのまま利用できるメリットは計り知れません。
また、StringReaderを活用することで、巨大なテキストデータをメモリ効率良く、安全に読み進めることが可能になります。
「文字列を単なる値として扱う」段階から一歩進んで、「文字列をデータ流(ストリーム)として扱う」という考え方を取り入れることで、あなたのC#プログラミングの幅はより一層広がるでしょう。
今回紹介したコード例を参考に、ぜひ自身のプロジェクトでも活用してみてください。
