C# Read and Write Text Files: Paths, Saved Lines, and Clear Failure Handling
Follow text from a variable into a file and back again. Learn what a path means, when a write replaces data, how line records work and how to verify the content you actually saved.
What you will understand
- Distinguish a path, file contents and an in-memory string.
- Build fixed practice paths and create parent directories.
- Choose replacement or append based on the intended operation.
- Read whole text, arrays of lines or sequential lines deliberately.
- Use UTF-8 consistently and define one-line title records.
- Handle a missing file without disguising unrelated I/O failures.
- Dispose a writer before reading its completed output.
- Validate additions and verify reloaded data in a temporary practice folder.
A path, a file and a string are different things
A string variable lives in the running program. A text file stores encoded characters in a filesystem. A path is a string describing where to find that file; it is not the file contents. File.WriteAllText sends text to that location, while File.ReadAllText retrieves text into a new string value.
Our examples use a dedicated csharp-file-practice directory beneath the process working directory. The program creates that directory and uses fixed filenames so every write has an obvious scope. The first example writes River Maps, then reads the file back instead of merely printing the original variable.
On your own computer, a file written to an ordinary persistent folder can remain after the console program exits. On this website, each Run uses a fresh temporary container: files exist during that execution and are discarded afterward. Separate editors and separate runs do not share saved files. To keep files across runs, copy the complete example into a local console project and use its practice folder.
All filesystem work in the runnable examples stays inside that temporary environment. You are not editing your computer’s files through the browser runner. The expected-output reference shows what a correct program should print; only selecting Run performs the actual isolated write and read.
Program.cs · Write a small file, then read it back
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
River Maps
The program creates a dedicated practice directory, builds a file path, writes UTF-8 text and reads it from the file. saved is the returned string in memory; path is the file location. The File helpers open and close their own handles. In this website runner the directory exists only inside this execution’s temporary container.
Build paths from known pieces and understand the working directory
Path.Combine builds a path using the platform’s separator rules. Combine a trusted folder with a fixed filename instead of manually mixing forward and backward slashes. A relative path is resolved against Directory.GetCurrentDirectory(), which is not necessarily the directory containing Program.cs.
Path.GetFullPath resolves a path string to an absolute location. It does not create anything or check that the file can be opened. Likewise Path.GetFileName extracts the final component from a string; it does not read a file. The path example prints stable facts rather than a machine-specific absolute path.
Directory.CreateDirectory creates the parent folder if necessary and is safe to call when it already exists. Writing a new file does not create a missing chain of parent folders automatically. The intentional caught-error example shows why directory setup belongs before the write.
Path.Combine is not a security boundary for arbitrary user input. A rooted component, traversal segments or filesystem links can redirect a path. These lessons use fixed trusted filenames. Real uploads or user-selected paths need their own authorization and containment design rather than assuming Combine makes them safe.
Program.cs · A path describes a location; combining it does not create it
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
notes.txt
False
True
The combined path is relative and is resolved against the process working directory, not automatically the source-file directory. GetFullPath resolves an absolute path string but does not create a directory or prove it exists. Path.Combine uses the platform’s path rules; these examples use fixed trusted path segments.
Program.cs · Writing a file does not create missing parent directories
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Create the parent directory first
WriteAllText can create the file, but it does not recursively create the missing folder. Directory.CreateDirectory establishes the parent path and can also be called when that directory already exists. It does not guarantee that a later write will succeed under every permission or disk condition.
Decide whether you mean replace or append before choosing a method
File.WriteAllText creates a new file or replaces the contents of an existing one. File.WriteAllLines does the same for a sequence of lines. Choose these when the supplied data is the complete desired snapshot, not merely the next entry.
File.AppendAllText and File.AppendAllLines add data at the end, creating the file when it does not exist if the parent directory is usable. Appending does not remove duplicates. Running the same append twice normally records the same entry twice unless your application adds a different rule.
AppendAllText writes the exact text you give it. It does not insert a newline between your records. The joined-title example demonstrates that a successful append can still produce an unusable format. Line helpers make one-record-per-line intent clearer for this reading list.
Even line appends need a valid existing record boundary. The lab starts with WriteAllLines, which writes line endings for its initial records. An arbitrary imported file that lacks a final newline needs an explicit policy before appending the next line. None of these helpers provides a multi-user transaction or an automatic backup of overwritten content.
Program.cs · Writing all text replaces the existing contents
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Night Trains
The second WriteAllText truncates and replaces the file rather than adding another entry. This is useful when saving a complete new snapshot, but wrong when the intended operation is to append a title. The practice filename is deliberately disposable; do not point this operation at a file you need to keep.
Program.cs · Appending does not invent a line separator
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
River MapsNight Trains
AppendAllText adds exactly the supplied text at the end. Neither string contains a newline, so the titles join together. A text-file append operation does not know your record format. Use line-based helpers or explicitly supply the required separators.
Program.cs · Use line-based methods for one title per line
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Count: 3
1. River Maps
2. Night Trains
3. Quiet Gardens
WriteAllLines writes the complete starting list and AppendAllLines adds another line. Because the file was created with line endings, the next title begins on its own line. ReadAllLines returns strings without their line-ending characters; the loop supplies numbering for display. An arbitrary pre-existing file lacking its final newline needs a deliberate boundary policy before line appends.
Choose whole text, all lines or sequential reading
File.ReadAllText returns one string containing all characters, including line separators. File.ReadAllLines returns an array of line strings without their terminators. Use the shape that matches the next step in your program rather than pretending those return types are interchangeable.
For a small reading list, a string array is easy to count, index and display. ReadAllLines loads the full list into memory, so the lab deliberately uses only a few bounded entries. The compiler-failure example assigns ReadAllText to string[] to make the difference visible.
File.ReadLines provides an enumerable that yields lines as you iterate, avoiding one up-front array of the entire file. Errors may arise during enumeration, not only when obtaining the enumerable. foreach manages its enumerator’s disposal when iteration completes or exits early.
An empty file is not the same as a file containing a blank line. The example writes both and compares their line counts. Do not invent an extra record just because a file ends with a newline, and decide deliberately whether blank lines are permitted in your format.
Program.cs · Choose a read method with the result shape you need
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Compilation fails: string cannot implicitly convert to string[].ReadAllText returns one string, not an array. Use a string variable for the whole content or ReadAllLines when the format is one record per line. Choosing the correct result type is part of understanding the helper rather than blindly changing declarations.
Program.cs · An empty file differs from a file containing one blank line
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Empty file lines: 0
Blank line count: 1
A zero-length file yields an empty line array. One newline represents one empty line. A final newline does not create an extra fictitious record after the last written line. Your application must decide whether blank records are valid instead of assuming all files follow the intended format.
Text records need both an encoding and a record format
Encoding translates characters into bytes when writing and back into characters when reading. These examples specify Encoding.UTF8 at both sides. That keeps accented and Arabic title text readable without relying on a hidden platform-specific encoding choice.
Encoding is not encryption and does not protect confidential data. It also does not define how to separate records or escape special characters. Our line-based list permits one title per line, so an embedded carriage return or newline would split one logical title into more than one physical record.
The final helper therefore rejects titles containing either newline character and trims only outside whitespace. Spaces inside a valid title remain meaningful. This simple format cannot represent arbitrary multiline data; a structured format such as JSON may fit that requirement better, but is beyond this lesson.
Environment.NewLine supplies a platform-appropriate line separator when you construct text manually. You may encounter files created on other platforms, so validate the data format rather than comparing raw bytes to one operating system’s newline convention.
Program.cs · Use a text encoding consistently
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Café Atlas
رحلة صغيرة
UTF-8 represents accented and non-Latin characters in this example. The same encoding is explicitly used for writing and reading. Encoding converts characters to bytes and back; it is not encryption. Console display and font support are separate from the bytes stored in a file.
Handle an expected missing file without hiding every failure
A first-time load may reasonably find no saved reading list. The missing-file example catches FileNotFoundException around the actual read and prints a clear first-use message. It first creates the parent directory so the example specifically concerns a missing file, not an absent directory.
File.Exists can be useful for display hints, but it is not a guarantee that the next read will succeed. Another process may change the file between a check and a read, and a false result can also conceal path or permission problems. Handle the operation that can fail rather than treating an existence check as a complete error policy.
Different failures mean different things: a missing parent directory, insufficient permission, a full disk or another I/O problem should not all become No saved list yet. Catch only what the current boundary can interpret meaningfully. The next lesson develops exception categories and recovery paths in more detail.
An append helper that returns false for invalid title text should not quietly swallow a write failure and claim that the text was invalid. The final lab returns true only after the write finishes; unexpected filesystem failures still propagate for an appropriate caller to handle.
Program.cs · Handle a missing file at the operation boundary
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
No saved list yet
The containing folder exists, but this file has not been created in the fresh execution. The catch handles that specific expected first-use case. Other failures, such as missing permissions, should not be silently reported as an empty list. The next lesson studies broader exception handling in depth.
Close writers so buffered content becomes available
The File read and write helpers shown earlier open and close their own file handles. You do not wrap their returned string or array in using. When you create a StreamWriter yourself, you own a resource that must be disposed.
A using block disposes StreamWriter when the block exits, including when an exception leaves the block. Disposal flushes buffered output and closes the writer. Reading after the block makes that boundary clear; merely constructing a writer and forgetting it can leave buffered data or an open handle.
The stream example uses append: false, so it replaces its dedicated practice file before writing two lines. After the writer closes, File.ReadLines iterates the result. This is a small preview of resource cleanup, not a reason to use streams for every tiny file operation.
Pick the simplest method that meets the size and workflow needs. Whole-file helpers are readable for small snapshots; streaming is useful for sequential processing when loading everything at once would be wasteful. Neither choice automatically makes writes atomic or coordinates simultaneous editors.
Program.cs · Close a writer before reading its completed output
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
River Maps
Night Trains
The using block disposes the writer when the block exits, flushing buffered text and closing its handle before the read. File.ReadLines enumerates lines rather than returning the entire array immediately. foreach disposes its enumerator, including when the loop exits early. These helpers suit larger sequential input, while ReadAllLines is simpler for the small bounded lab.
Reload the file to prove what was saved
Printing a source array proves only that the array exists. The lab reads the file back and checks the reloaded count and titles. This catches choosing replacement where append was intended, losing line separators and forgetting to close a buffered writer.
Test a valid trimmed title, blank text, null and embedded line breaks. Invalid records must leave the stored list unchanged. Add the same title twice to understand the stated policy: duplicates are allowed here and each accepted append creates another record. Do not claim uniqueness without implementing it.
Each lab run deliberately writes a known two-title starting snapshot so the demonstration is repeatable inside the temporary runner. That reset is appropriate for a practice fixture, not an instruction to overwrite a real user’s saved list every time an application starts. A local application should separate one-time initialization from later loading and appending.
The repair and lab operate only on their own practice file. We do not delete unrelated files, export downloads or persist browser accounts. The next lesson will build clearer exception and cleanup policies on top of the I/O boundaries you have now traced.
Guided repair: append without losing the existing list
Adding Quiet Gardens leaves only that title in the file. The second operation replaces the entire snapshot. Preserve the original two titles and add the new line instead.
Program.cs · A second snapshot write accidentally loses earlier entries
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Quiet Gardens
The intended operation was add one title, but the second WriteAllLines saves a replacement snapshot containing only that new title. The code compiles and writes successfully while losing the two earlier entries. Verify reloaded content rather than treating a successful write call as proof of the requirement.
Repair checkpoints
- Keep the first WriteAllLines as the known practice snapshot.
- Replace the second WriteAllLines with AppendAllLines.
- Read the file again rather than printing only the original array.
- Verify all three titles remain in the intended order.
- Recognize that another append would create a duplicate unless a separate uniqueness rule is added.
Reveal the repair and explanation
Program.cs · Append the new entry to the existing line-based file
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
River Maps | Night Trains | Quiet Gardens
The first write creates the known starting snapshot; the second operation extends it. The file has valid line boundaries because the first helper wrote complete lines. Repeating the append would add another copy of the title: append is not deduplication or an automatically idempotent operation.
Independent lab: save and reload a reading list
Complete a reading-list append helper that accepts one meaningful title per line. Reload the actual file after valid and invalid additions to prove what changed.
Acceptance checklist
- Create the dedicated practice directory and fixed reading-list path.
- Keep the initial two-line snapshot only as repeatable practice setup.
- Return false without a write for null, whitespace-only or multiline titles.
- Reject both carriage return and newline inside a supplied title.
- Trim outside whitespace and append exactly one line using UTF-8.
- Return true only after a successful append; do not swallow I/O failures as invalid text.
- Reload with ReadAllLines and print the saved count and each title.
- Explain that website runs are isolated and temporary, while a local project can keep its practice file.
Program.cs · Reading-list file starter
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Added: False
Blank added: False
Saved titles: 2
River Maps
Night Trains
The setup creates the parent directory and a known two-title file. The append helper is unfinished. Complete title validation and one-line append, then reload the actual file to prove that valid additions are saved and invalid additions leave it unchanged.
Show hints
- Validate before calling any file-writing method.
- Use string.IsNullOrWhiteSpace, then check title.Contains for each newline character.
- AppendAllLines can receive a one-element string array containing title.Trim().
Reveal the reference solution
Program.cs · Reading-list file reference solution
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Added: True
Blank added: False
Saved titles: 3
River Maps
Night Trains
Quiet Gardens
The helper rejects null, blank and multiline titles before writing, then appends one trimmed record. Its Boolean reports input acceptance when the append completes; I/O failures still throw and are not disguised as invalid input. The lab resets its starting snapshot on each run for predictable practice output.
Changed-input checks
- A valid title with outside spaces adds one trimmed line.
- Null, blank, carriage-return and newline titles leave file contents unchanged.
- Non-Latin titles survive the UTF-8 round trip.
- Appending the same title twice creates two records under this stated policy.
- Appending to a missing file works when the parent directory already exists.
- A zero-entry starting snapshot can accept its first valid title.
Recap: choose the operation that matches your intent
Identify the path, the text format, whether the operation replaces or appends, and how the saved result will be verified.
Path.Combine- Build a path string from trusted components, not a containment guarantee.
Directory.CreateDirectory- Establish the parent directory before creating a file.
File.WriteAllText / WriteAllLines- Create or replace a complete text snapshot.
File.AppendAllText / AppendAllLines- Add supplied text or lines without automatic deduplication.
File.ReadAllText- Load the whole contents as one string.
File.ReadAllLines- Load a bounded list as strings without line terminators.
File.ReadLines- Enumerate lines sequentially with deferred reading.
using StreamWriter- Dispose an owned writer, flush buffered text and close the handle.
Frequently asked questions
Will files remain after I run this on the website?
No. Each Run uses a fresh temporary container. Files exist during that execution and are discarded afterward. A local console project can write to its own persistent practice folder.
Does WriteAllText add to an existing file?
No. It replaces existing contents. Choose an append helper when adding a new entry is the intended operation.
Does appending text add a newline automatically?
AppendAllText adds exactly the text supplied. The lab uses line helpers and a known line-based starting file; arbitrary imported files need a deliberate final-line policy.
Is Path.Combine enough to make user filenames safe?
No. It builds paths, not authorization or containment. These examples use fixed trusted filenames rather than arbitrary user paths.
Should I call File.Exists before every read?
It may help display intent, but cannot guarantee the later read. Handle the actual operation’s meaningful failure cases as well.
Do File.ReadAllLines and File.ReadAllText need using?
No. Those helpers manage their own handles. A StreamWriter you create directly should be disposed with using.
Why reject multiline titles?
This exercise stores one record per line. Embedded line breaks would split a single title into multiple records. A different format is needed to represent that input faithfully.
Can you predict what the file will contain?
Distinguish paths, replacement, appending, line reads and resource ownership. Read the explanation after checking your answers.
Next step and references
File operations need deliberate paths, formats and write policies. Create the parent directory, distinguish replacement from append, close owned writers and reload data to verify what changed.
Next is C# Exceptions and Resource Cleanup. You will catch failures where recovery is meaningful and use using or finally to release resources predictably.
Official references
Original SovranCode explanations, examples and exercises. Technical details were checked against these references.
- Microsoft Learn: Write text and dispose writers
- Microsoft Learn: File.ReadAllLines
- Microsoft Learn: File.WriteAllText replacement behavior
Last updated: .