Summary
The Utf8JsonWriter you create with new(File.Create(...)) owns the underlying `FileStream, and disposing the writer does not close that stream. The file remains locked, so the subsequent File.OpenRead fails with “the process cannot access the file … because it is being used by another process.” The fix is to also dispose the FileStream (or use a using block) or let JsonSerializer.Serialize manage the stream for you.
Root Cause
File.Create(path)returns a FileStream opened withFileShare.None.- Passing that stream to
Utf8JsonWritergives the writer ownership of the writer only, not the stream. - Calling
relevantDataWriter.Dispose()flushes buffers but does not dispose the underlying FileStream. - The FileStream stays open and locked, causing the read attempt to throw.
Why This Happens in Real Systems
- Implicit resource ownership: many APIs (including
Utf8JsonWriter) expect the caller to manage the lifetime of the stream they are given. - FileShare defaults:
File.CreateusesFileShare.None, which prevents any other handle from opening the file until the original handle is closed. - Assumption that Dispose cascades: developers often assume disposing a wrapper object will also close the wrapped stream, which is not true for
Utf8JsonWriter.
Real-World Impact
- Test suites hang or fail intermittently when they try to read files that were just written.
- Production services that generate JSON logs or payloads may lock files, leading to file‑access errors for monitoring or archival tools.
- Resource leakage: open file handles accumulate, eventually hitting the OS limit and causing process crashes.
Example or Code (if necessary and relevant)
// WRONG: only the writer is disposed
Utf8JsonWriter writer = new(File.Create(filePath));
JsonSerializer.Serialize(writer, data);
writer.Flush();
writer.Dispose(); // <-- leaves FileStream open
// CORRECT: dispose the FileStream as well, preferably with using
using FileStream fs = File.Create(filePath);
using Utf8JsonWriter writer = new(fs);
JsonSerializer.Serialize(writer, data);
How Senior Engineers Fix It
- Wrap both the stream and the writer in
usingstatements so they are disposed together. - Prefer the overload
JsonSerializer.Serialize<T>(Stream, T, JsonSerializerOptions?)which handles stream disposal automatically. - If you must create the writer yourself, pass a stream opened with appropriate sharing flags, e.g.
new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read). - Add unit‑test cleanup that asserts the file is not locked after the write operation.
Why Juniors Miss It
- They often confuse the lifetime of the wrapper object with the underlying stream.
- They rely on the IDE’s quick‑fix “dispose” that generates
Dispose()calls for the immediate object only. - Limited exposure to file‑sharing semantics (
FileShare.Nonevs.FileShare.Read) leads to the assumption that a disposed writer releases the file. - Lack of habit to use
usingblocks or the newerusing varsyntax for deterministic resource cleanup.