Streamをコンストラクタの引数に取るクラスをusingでネストすると、コード分析でCA2202で警告されます。
using (Stream stream = new FileStream("sample.txt", FileMode.Create))
using (StreamWriter writer = new StreamWriter(stream))
{
// writerオブジェクトを使用する処理
} // warning CA2202: Microsoft.Usage : オブジェクト 'stream' は、メソッド '***' 内で 2 回以上破棄される可能性があります。System.ObjectDisposedException の生成を回避するには、オブジェクトに対して Dispose を 2 回以上呼び出さないようにしてください。
よってtry-finallyで破棄するようにします。
Stream stream = null;
try
{
stream = new FileStream("sample.txt", FileMode.Create);
using (StreamWriter writer = new StreamWriter(stream))
{
stream = null; // StreamWriterの生成に成功したならばそのDispose()で破棄されるため、Stream.Dispose()が呼ばれないようにnullを設定
// writerオブジェクトを使用する処理
}
}
finally
{
if (stream != null) stream.Dispose();
}
次のようにすると、"ABC"という文字列を含む"sample.txt"というファイルが作成されます。
string path = "sample.txt";
using (FileStream fs = new FileStream(path, FileMode.Create))
{
byte[] array = new byte[] { 65, 66, 67 }; // ABC
fs.Write(array, 0, array.Length);
} // ここでストリームが閉じられる
ストリームは閉じられるときに書き込まれますので、usingなどで明示的にリソースを解放します。
public FileStream (
string path,
System.IO.FileMode mode,
System.Security.AccessControl.FileSystemRights rights,
System.IO.FileShare share,
int bufferSize,
System.IO.FileOptions options,
System.Security.AccessControl.FileSecurity fileSecurity
);
FileStream(String, FileMode, FileSystemRights, FileShare, Int32, FileOptions, FileSecurity) - FileStream コンストラクター (System.IO) | Microsoft Learn
FileStreamのインスタンスは、次のようにFile.Create()メソッドでも作成できます。
FileStream fs = File.Create(path)File.Create メソッド (String) (System.IO) | MSDN
modeを指定したときに「パス '***' へのアクセスが拒否されました。」としてUnauthorizedAccessExceptionが投げられるならば、それに適合するようにshareも指定します。
| メソッド | |
|---|---|
| CopyTo(Stream) | 現在のストリームから、別のストリームへ書き込む |
| Read(Byte[], Int32, Int32) | 指定位置から指定バイト数だけ読み取り、指定のバッファへ書き込む |
| ReadByte() | 現在位置から1バイト読み取り、それを返す |
| Write(Byte[], Int32, Int32) | |
| WriteByte(Byte) |
| メソッド | |
|---|---|
| CopyToAsync(Stream) | |
| ReadAsync(Byte[], Int32, Int32) | |
| WriteAsync(Byte[], Int32, Int32) | |
| BeginRead(Byte[], Int32, Int32, AsyncCallback, Object) | 非推奨。代わりにReadAsyncを使用する |
| BeginWrite(Byte[], Int32, Int32, AsyncCallback, Object) | 非推奨。代わりにWriteAsyncを使用する |
public override int Read(
byte[] array,
int offset,
int count
)
FileStream.Read メソッド (Byte[], Int32, Int32) (System.IO) | MSDN
[ComVisibleAttribute(false)] [HostProtectionAttribute(SecurityAction.LinkDemand, ExternalThreading = true)] public Task<int> ReadAsync( byte[] buffer, int offset, int count )Stream.ReadAsync メソッド (Byte[], Int32, Int32) (System.IO) | MSDN
public override void Write(
byte[] array,
int offset,
int count
)
FileStream.Write メソッド (Byte[], Int32, Int32) (System.IO) | MSDN
[ComVisibleAttribute(false)]
[HostProtectionAttribute(SecurityAction.LinkDemand, ExternalThreading = true)]
public Task WriteAsync(
byte[] buffer,
int offset,
int count
)
Stream.WriteAsync メソッド (Byte[], Int32, Int32) (System.IO) | MSDN
public async void Method() { // await式に到達するまでは、同期的に実行される using (FileStream fs = new FileStream("sample.txt", FileMode.Create)) { byte[] array = new byte[1000 * 1000]; await fs.WriteAsync(array, 0, array.Length); // ここで呼び出し元へ制御が戻る // 書き込みが完了すると、これ以降が処理される } }
awaitで待たせないと、書き込み完了前にストリームが閉じられることがあります。ファイル アクセスにおける非同期の使用 (C#) | Microsoft Learn
他のプロセスからの読み取りと書き込みを防止できます。FileStream.Lock(Int64, Int64) メソッド (System.IO) | Microsoft Learn
string path = "sample.dat"; FileStream fs1 = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); FileStream fs2 = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); fs1.Lock(0, 1); fs2.ReadByte(); // IOException「プロセスはファイルにアクセスできません。別のプロセスがファイルの一部をロックしています。」
配列がbyte型ならば、Write(Byte[])などのバイト配列向けのメソッドだけで処理できます。しかしそれ以外の型ならば、要素を1つずつ処理します。
int[] items = new int[] { 1, 2, 3 };
// 書き込み
FileStream fileStream = null;
try
{
fileStream = File.Create("sample.dat");
using (BinaryWriter binaryWriter = new BinaryWriter(fileStream))
{
fileStream = null;
foreach (int item in items)
{
binaryWriter.Write(item);
}
}
}
finally
{
if (fileStream != null) fileStream.Dispose();
}
// 読み取り
int[] result;
fileStream = null;
try
{
fileStream = File.OpenRead("sample.dat");
using (BinaryReader binaryReader = new BinaryReader(fileStream))
{
fileStream = null;
long length = binaryReader.BaseStream.Length / sizeof(int);
result = new int[length];
for (int i = 0; i < length; i++)
{
result[i] = binaryReader.ReadInt32();
}
}
}
finally
{
if (fileStream != null) fileStream.Dispose();
}
public MemoryStream ();MemoryStream() - MemoryStream Constructor (System.IO) | Microsoft Learn
byte[] src = new byte[] { 1, 2, 3 };
byte[] dest;
using (MemoryStream memoryStream = new MemoryStream())
{
// 書き込む
memoryStream.Write(src, 0, src.Length);
// ストリームの位置を先頭へ戻す
memoryStream.Seek(0, SeekOrigin.Begin);
// 読み込む
dest = new byte[memoryStream.Length];
int count = memoryStream.Read(dest, 0, dest.Length);
}
ストリームの全体の内容を、他のストリームへ書き込めます。
public virtual void WriteTo (System.IO.Stream stream);MemoryStream.WriteTo(Stream) Method (System.IO) | Microsoft Learn
内部ではStream.Write()で書き込まれます。WriteTo - memorystream.cs
[System.CLSCompliant(false)]
[System.Security.SecurityCritical]
public UnmanagedMemoryStream (
byte* pointer, // アンマネージド メモリへのポインタ
long length, // 使用するメモリの長さ
long capacity, // ストリームに割り当てられたメモリの総量
System.IO.FileAccess access // FileAccessの値
);
UnmanagedMemoryStream(Byte*, Int64, Int64, FileAccess) - UnmanagedMemoryStream Constructor (System.IO) | Microsoft Learn
byte[] array = { 1, 2, 3 };
int size = Marshal.SizeOf(array[0]) * array.Length;
IntPtr intPtr = Marshal.AllocHGlobal(size);
unsafe
{
// IntPtrからbyteポインタを取得する
byte* bytePtr = (byte*)intPtr.ToPointer();
// アンマネージド メモリへのポインタを使用するUnmanagedMemoryStreamオブジェクトを作成する
using (UnmanagedMemoryStream stream = new UnmanagedMemoryStream(bytePtr, array.Length, array.Length, FileAccess.Write))
{
// データを書き込む
stream.Write(array, 0, array.Length);
// ストリームを閉じる
stream.Close();
}
}
Marshal.FreeHGlobal(intPtr);
プリミティブ型のバイナリでの書き込みと、指定のエンコーディングでの文字列の書き込みに対応しています。
// 書き込み FileStream fileStream = null; try { fileStream = File.Open("sample.dat", FileMode.Create); using (BinaryWriter binaryWriter = new BinaryWriter(fileStream)) { fileStream = null; binaryWriter.Write(10); // 0x0a 0x00 0x00 0x00 binaryWriter.Write(1.0f); // 0x00 0x00 0x80 0x3F binaryWriter.Write(true); // 0x01 binaryWriter.Write("ab"); // 0x02 0x61 0x62 } } finally { if (fileStream != null) fileStream.Dispose(); }BinaryWriter クラス (System.IO) | Microsoft Learn
// 読み取り FileStream fileStream = null; try { fileStream = File.Open("sample.dat", FileMode.Open); using (BinaryReader binaryReader = new BinaryReader(fileStream)) { fileStream = null; int a1 = binaryReader.ReadInt32(); // 10 float a2 = binaryReader.ReadSingle(); // 1 bool a3 = binaryReader.ReadBoolean(); // true string a4 = binaryReader.ReadString(); // "ab" // すべてのデータの読み取り byte[] result = new byte[binaryReader.BaseStream.Length]; binaryReader.Read(result, 0, result.Length); } } finally { if (fileStream != null) fileStream.Dispose(); }BinaryReader クラス (System.IO) | Microsoft Learn
ReadString()で読み取った文字列の先頭には、文字列の長さが付加されます。これが期待するものでなければReadBytes()でbyte配列として読み取り、Encoding.GetString()で文字列にデコードします。
BaseStreamプロパティからStreamを取得し、そのPositionがLengthより小さいことで末尾に達していないことを確認できます。binaryfiles - C# checking for binary reader end of file - Stack Overflow
Stream stream = binaryReader.BaseStream;
while(stream.Position < stream.Length)
{
// 読み取り処理
}
public StreamWriter(
string path
)
StreamWriter コンストラクター (String) (System.IO) | MSDN
ファイルが存在する場合には上書きされます。これを追記するようにするには、次の形式でappendをtrueとします。
public StreamWriter(
string path,
bool append // 追記するかどうか
)
StreamWriter コンストラクター (String, Boolean) (System.IO) | MSDN
既定では文字エンコーディングはUTF-8となります。これを変更するには、encodingの指定を追加します。
public StreamWriter(
string path,
bool append, // 追記するかどうか
Encoding encoding // 文字エンコーディング
)
StreamWriter コンストラクター (String, Boolean, Encoding) (System.IO) | MSDN
string path = "sample.txt"; using (StreamWriter sw = new StreamWriter(path)) { sw.Write("ABC"); }
| メソッド | 機能 |
|---|---|
| Write(String) | |
| Write(String, Object) | 書式設定してストリームへ書き込める |
| WriteAsync(String) | 非同期で書き込める |
| WriteLine(String) | 行の終端記号も書き込める |
public override void Write(
string value
)
StreamWriter.Write メソッド (String) (System.IO) | MSDN
public StreamReader(
string path
)
StreamReader コンストラクター (String) (System.IO) | MSDN
pathだけを指定する形式では、detectEncodingFromByteOrderMarksはtrue、encodingはEncoding.UTF8、bufferSizeは1024、さらにFileMode.Open、FileAccess.Read、FileShare.ReadでFileStreamが作成されます。StreamReader - streamreader.cs
FileModeなどを指定できるFileStreamOptionsを指定できるオーバーロードは.NET 6以降でのサポートとなるため、それより前ではFileStreamで読み込みます。
using (FileStream fs = new FileStream("sample.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
byte[] array = new byte[fs.Length];
fs.Read(array, 0, array.Length); // 末尾まで読み込む
string text = Encoding.UTF8.GetString(array); // 文字列に変換する
}
| 型 | プロパティ | |
|---|---|---|
| Encoding | CurrentEncoding | 現在の文字エンコーディング |
| bool | EndOfStream | ストリームの末尾に達しているならば、true |
| 戻り値の型 | メソッド | 読み込み基準 | 読み込み範囲 | 末尾から読み込んだときの戻り値 |
|---|---|---|---|---|
| int | Read() | ストリームの現在位置 | 1文字 | -1 |
| int | Read(Char[], Int32, Int32) | ストリームの指定位置 | 指定文字数 | 0 |
| int | ReadBlock(Char[], Int32, Int32) | ストリームの指定位置 | 指定文字数 | 0 |
| string | ReadLine() | ストリームの現在位置 | 1行 | null |
| string | ReadToEnd() | ストリームの現在位置 | 末尾まで | "" |
| 戻り値の型 | メソッド |
|---|---|
| Task<int> | ReadAsync(Char[], Int32, Int32) |
| Task<int> | ReadBlockAsync(Char[], Int32, Int32) |
| Task<string> | ReadLineAsync() |
| Task<string> | ReadToEndAsync() |
\n、\r、\rnまでが1行と見なされます。 Remarks - StreamReader.ReadLine Method (System.IO) | Microsoft Learn ReadLine - streamreader.cs
public override string ReadToEnd ();StreamReader.ReadToEnd メソッド (System.IO) | Microsoft Learn
using (StreamReader sr = new StreamReader("samle.txt"))
{
string text = sr.ReadToEnd();
}
c# - Difference between StreamReader.Read and StreamReader.ReadBlock - Stack Overflow
ストリームが末尾に達していないときにこのメソッドを呼び出すと、無期限にブロックされます。
内部ではバッファが空になるまで順に読み込まれます。ReadToEnd - streamreader.cs
StringWriterには、stringに情報を書き込むためのTextWriterが実装されています。情報は、基礎になるStringBuilderに格納されます。
このクラスはIDisposableを実装しますが、それで処置するリソースはないため、Dispose()を呼ぶ必要はありません。Remarks - StringWriter Class (System.IO) | Microsoft Learn
文字コードは既定でSystem.Text.UnicodeEncodingとなるため、これを変更するにはこのクラスを継承し、Encodingプロパティから希望する文字コードを返します。xml - XmlWriter encoding UTF-8 using StringWriter in C# - Stack Overflow
StringWriter sw = new StringWriter();
sw.Write(true);
sw.Write("ABC");
string str = sw.ToString(); // "TrueABC"
StringBuilder sb = sw.GetStringBuilder();
StringReaderは、文字列からしか初期化できません。
public StringReader(
string s
)
StringReader(String) コンストラクター (System.IO) | Microsoft Learn
このクラスはIDisposableを実装しますが、それで処置するリソースはないため、Dispose()を呼ぶ必要はありません。Remarks - StringReader Class (System.IO) | Microsoft Learn
書き込み用のクラスなどは用意されていないため、自前で処理します。c# - Best practices for serializing objects to a custom string format for use in an output file - Stack Overflow
定義に従うならばエスケープの処理などが必要ですが、簡易で良ければString.Join()でカンマで連結し、StreamWriterのWriteLine()でファイルに書き込めます。
string[][] data = new string[][]
{
new string[]{ "a", "b", "c" },
new string[]{ "d", "e" }
};
using (StreamWriter streamWriter = new StreamWriter("sample.csv"))
{
foreach (string[] item in data)
{
streamWriter.WriteLine(String.Join(",", item));
}
}
TextFieldParserクラスで読み込めます。
Microsoft.VisualBasic.FileIO.TextFieldParser parser
= new Microsoft.VisualBasic.FileIO.TextFieldParser("sample.csv");
// 区切り文字を設定する。これは既定ではnullとなっており、設定しなければ読み込み時に「区切り記号が Nothing または empty であるため、区切り記号で分けられたフィールドを読み取れません。」としてArgumentExceptionが投げられる
parser.SetDelimiters(",");
List<string[]> result = new List<string[]>();
while (!parser.EndOfData)
{
// 現在の行から、指定形式でフィールドを読み込む
string[] fields = parser.ReadFields();
result.Add(fields);
}
// 結果を配列として得たいならば、リストを配列に変換する
string[][] array = result.ToArray();
TextFieldParserの利用時に「型または名前空間の名前 'FileIO' が名前空間 'Microsoft.VisualBasic' に存在しません」としてエラーとなるときには、Microsoft.VisualBasicを参照に追加します。
| 型 | プロパティ | 内容 | 既定値 |
|---|---|---|---|
| string[] | CommentTokens | コメント行と見なす文字列 | string[0] |
| FieldType | TextFieldType | テキストファイルの形式 | FieldType.Delimited |
| string[] | Delimiters | 区切り記号。TextFieldTypeがDelimitedのときに必須 | null |
| int[] | FieldWidths | テキストファイルの各列の幅。TextFieldTypeがFixedWidthのときに必須 | null |
| bool | TrimWhiteSpace | trueならば、前後の空白を除去する | true |
Delimitersにカンマ以外を設定すれば、CSV以外の形式にも対応できます。またTextFieldTypeをFixedWidthとしてFieldWidthsでその幅を指定すると、固定幅でも読み込めます。
| 列挙子 | フィールドの形式 |
|---|---|
| Delimited | 区切り文字で分割 |
| FixedWidth | 固定幅で分割 |
現在の行のすべてのフィールドを、文字列の配列として得られます。そして次にデータが含まれる行までカーソルが進みます。
public string[] ReadFields()TextFieldParser.ReadFields メソッド (Microsoft.VisualBasic.FileIO) | MSDN
StringReader stringReader = new StringReader("AA,\"BB,BB\",\"C\"\"C\"\r\nDD,\"EE\r\nEE\"");
TextFieldParser parser = new TextFieldParser(stringReader);
parser.SetDelimiters(",");
string[] fields1 = parser.ReadFields(); // AA, BB,BB, C\"C
string[] fields2 = parser.ReadFields(); // DD, EE\r\nEE
ReadFields()では空行が読み飛ばされるため、それが期待する動作ではないならば、行を追加するなどの処理が必要です。.net - Why does TextFieldParser.ReadField remove consecutive newlines from middle of a field? - Stack Overflow
これと類似するメソッドとは、下表のような違いがあります。
| 戻り値 | メソッド | 機能 | 構文解析 | カーソル | 空行 |
|---|---|---|---|---|---|
| string[] | ReadFields() | 現在の行を文字配列として読み込む | あり | 次の行へ進む | 無視する |
| string | ReadLine() | 現在の行を文字列として読み込む | なし | 無視しない | |
| string | PeekChars(Int32) | 指定数の文字を読み込む | 進まない | 無視する |
複雑な操作が必要ならば、XmlDocumentを用います。c# - XmlDocument vs XmlWriter - Stack Overflow
.NET 2.0以降はXmlTextWriterの代わりに、このXmlWriterクラスを用います。Remarks - XmlTextWriter Class (System.Xml) | Microsoft Learn
public static System.Xml.XmlWriter Create (
string outputFileName,
System.Xml.XmlWriterSettings settings
);
Create(String, XmlWriterSettings) - XmlWriter.Create Method (System.Xml) | Microsoft Learn
XMLの作成方法はsettingsで指定します。XmlWriterSettings Class (System.Xml) | Microsoft Learn
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
using (XmlWriter writer = XmlWriter.Create("sample.xml", settings))
{
writer.WriteComment("SAMPLE");
writer.WriteStartElement("root");
writer.WriteStartElement("e1");
writer.WriteAttributeString("a1", "10");
writer.WriteStartElement("e2");
writer.WriteAttributeString("a2", "20");
writer.WriteString("ABC");
writer.WriteEndElement();
writer.WriteStartElement("e2");
writer.WriteAttributeString("a3", "30");
writer.WriteString("DEF");
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndElement();
}
このコードは次のようなファイルを出力します。
<?xml version="1.0" encoding="utf-8"?>
<!--SAMPLE-->
<root>
<e1 a1="10">
<e2 a2="20">ABC</e2>
<e2 a3="30">DEF</e2>
</e1>
</root>
最後にClose()を呼ぶ必要がありますが、これはDispose()でも呼ばれます。
public abstract void WriteStartDocument (bool standalone);WriteStartDocument(Boolean) - XmlWriter.WriteStartDocument Method (System.Xml) | Microsoft Learn
standaloneにtrueを渡すことで、XML宣言に"standalone=yes"を追記できます。XML宣言をより柔軟に出力するには、WriteProcessingInstruction()を用います。
public abstract void WriteProcessingInstruction (
string name,
string text
);
XmlWriter.WriteProcessingInstruction(String, String) Method (System.Xml) | Microsoft Learn
.NET 2.0以降はXmlTextReaderの代わりに、このXmlReaderクラスを用います。Remarks - XmlTextReader Class (System.Xml) | Microsoft Learn
public static System.Xml.XmlReader Create (
System.IO.Stream input, // XMLデータを含むストリーム
System.Xml.XmlReaderSettings settings // 新しいXmlReaderインスタンスの設定
);
Create(Stream, XmlReaderSettings) - XmlReader.Create メソッド (System.Xml) | Microsoft Learn
settingsを省略したときはDefault settingsの設定値が用いられます。
using (XmlReader reader = XmlReader.Create("sample.xml"))
{
reader.MoveToContent();
Console.Write(reader.Name); // "root"
reader.ReadToFollowing("e2");
Console.Write(reader.GetAttribute("a2")); // "20"
Console.Write(reader.ReadElementContentAsString()); // "ABC"
XmlNodeType type1 = reader.NodeType; // Whitespace
reader.Skip();
XmlNodeType type2 = reader.NodeType; // Element
Console.Write(reader.GetAttribute("a3")); // "30"
Console.Write(reader.ReadElementContentAsString()); // "DEF"
}
現在のノードとその子孫ノードを読み込める新しいXmlReaderを得られます。これによりアクセスできる範囲を制限できます。
public virtual System.Xml.XmlReader ReadSubtree ();XmlReader.ReadSubtree メソッド (System.Xml) | Microsoft Learn
サブツリー全体が読み込まれると、 Read()はfalseを返します。このメソッドから得られるXmlReaderが閉じられると、元のXmlReaderはサブツリーのノードのEndElementの位置になります。
新しいXmlReaderを閉じるまで、元のXmlReaderを操作しないようにします。そのような操作には対応しておらず、予期せぬ結果となります。Remarks - XmlReader.ReadSubtree Method (System.Xml) | Microsoft Learn
圧縮したファイルをツールで展開できるようにするには、DeflateStreamではなくGZipStreamを用います。c# - GZipStream or DeflateStream class? - Stack Overflow
FileInfo file = new FileInfo("sample.txt");
FileInfo newFile = new FileInfo(file.FullName + ".gz");
using (FileStream original = file.OpenRead()) // 圧縮前のファイル
using (FileStream compressed = newFile.Create()) // 圧縮後のファイル
using (GZipStream gzip = new GZipStream(compressed, CompressionMode.Compress))
{
original.CopyTo(gzip);
}
これを展開するには、CompressionModeをDecompressとします。
FileInfo file = new FileInfo("sample.txt.gz");
FileInfo newFile = new FileInfo("sample.txt");
using (FileStream compressed = file.OpenRead()) // 展開前のファイル
using (FileStream decompressed = newFile.Create()) // 展開後のファイル
using (GZipStream gzip = new GZipStream(compressed, CompressionMode.Decompress))
{
gzip.CopyTo(decompressed);
}
ZIPファイルへストリームから書き込むには、次のようにします。c# - Creating a ZIP Archive in Memory Using System.IO.Compression - Stack Overflow
using (FileStream fileStream = new FileStream("sample.zip", FileMode.Create))
using (ZipArchive archive = new ZipArchive(fileStream, ZipArchiveMode.Create))
{
// File 1
ZipArchiveEntry archiveEntry1 = archive.CreateEntry("file1.txt");
using (StreamWriter stream = new StreamWriter(archiveEntry1.Open()))
{
stream.Write("sample1");
}
// File 2
ZipArchiveEntry archiveEntry2 = archive.CreateEntry("file2.txt");
using (StreamWriter stream = new StreamWriter(archiveEntry2.Open()))
{
stream.Write("sample2");
}
}
逆にストリームへ読み込むには、次のようにします。
using (FileStream fileStream = new FileStream("sample.zip", FileMode.Open))
using (ZipArchive archive = new ZipArchive(fileStream, ZipArchiveMode.Read))
{
// File 1
ZipArchiveEntry archiveEntry1 = archive.GetEntry("file1.txt");
using (StreamReader stream = new StreamReader(archiveEntry1.Open()))
{
string str = stream.ReadLine(); // "sample1"
}
// File 2
ZipArchiveEntry archiveEntry2 = archive.GetEntry("file2.txt");
using (StreamReader stream = new StreamReader(archiveEntry2.Open()))
{
string str = stream.ReadLine(); // "sample2"
}
}