Skip to content

Document trailing newline behavior for ReadLine and ReadAllLines/ReadLines APIs#12493

Open
Copilot wants to merge 5 commits intomainfrom
copilot/update-streamreader-documentation
Open

Document trailing newline behavior for ReadLine and ReadAllLines/ReadLines APIs#12493
Copilot wants to merge 5 commits intomainfrom
copilot/update-streamreader-documentation

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 7, 2026

A trailing newline at the end of a stream or file does not produce a final empty string — "foo\nbar\n" and "foo\nbar" both yield the same two lines. This behavior is common but undocumented, making it a source of confusion.

Affected APIs

Added a note to the ## Remarks section of each:

  • StreamReader.ReadLine
  • TextReader.ReadLine
  • StringReader.ReadLine
  • File.ReadAllLines(string) / File.ReadAllLines(string, Encoding)
  • File.ReadLines(string) / File.ReadLines(string, Encoding)

Example note added (StreamReader.ReadLine)

If the stream ends with a newline sequence, no additional empty line is returned. For example, a stream containing "foo\nbar\n" produces the same two lines ("foo" and "bar") as a stream containing "foo\nbar".

Copilot AI and others added 2 commits April 7, 2026 14:03
… TextReader.ReadLine, StringReader.ReadLine, File.ReadAllLines, and File.ReadLines

Agent-Logs-Url: https://github.com/dotnet/dotnet-api-docs/sessions/26046a78-c7a0-46a9-bccb-394eb343d453

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI changed the title [WIP] Document newline treatment in StreamReader.ReadLine Document trailing newline behavior for ReadLine and ReadAllLines/ReadLines APIs Apr 7, 2026
Copilot AI requested a review from adamsitnik April 7, 2026 14:07
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-io

Copilot AI requested a review from gewarren April 13, 2026 20:51
@gewarren
Copy link
Copy Markdown
Contributor

I will work on the merge conflicts...

@gewarren gewarren marked this pull request as ready for review April 13, 2026 21:12
@gewarren gewarren requested a review from a team as a code owner April 13, 2026 21:12
Copilot AI review requested due to automatic review settings April 13, 2026 21:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR documents a subtle but important behavior of the .NET line-reading APIs: a trailing newline at the end of the input does not produce an extra empty string/line in the returned results, reducing confusion for developers reading streams, strings, and files.

Changes:

  • Added a note to ReadLine remarks for TextReader, StringReader, and StreamReader clarifying trailing-newline behavior.
  • Added the same clarification to File.ReadAllLines (both overloads) and File.ReadLines (both overloads), including examples.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
xml/System.IO/TextReader.xml Adds trailing-newline behavior note to TextReader.ReadLine remarks.
xml/System.IO/StringReader.xml Adds trailing-newline behavior note to StringReader.ReadLine remarks and removes extra blank lines before Examples.
xml/System.IO/StreamReader.xml Adds trailing-newline behavior note to StreamReader.ReadLine remarks.
xml/System.IO/File.xml Adds trailing-newline behavior notes to ReadAllLines and ReadLines remarks (including encoding overloads), plus minor formatting adjustments around Examples.


This method uses <xref:System.Text.Encoding.UTF8*> for the encoding value.

A line is defined as a sequence of characters followed by a carriage return ('\r'), a line feed ('\n'), or a carriage return immediately followed by a line feed. If the file ends with a newline sequence, no additional empty line is returned. For example, a file containing `"line1\nline2\n"` produces the same two lines (`"line1"` and `"line2"`) as a file containing `"line1\nline2"`.
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new line definition implies that a line is always terminated by a CR/LF sequence, but File.ReadLines also returns the final line when the file ends without a trailing newline. Consider updating the definition to include end-of-file/end-of-stream as a valid line terminator.

Suggested change
A line is defined as a sequence of characters followed by a carriage return ('\r'), a line feed ('\n'), or a carriage return immediately followed by a line feed. If the file ends with a newline sequence, no additional empty line is returned. For example, a file containing `"line1\nline2\n"` produces the same two lines (`"line1"` and `"line2"`) as a file containing `"line1\nline2"`.
A line is defined as a sequence of characters followed by a carriage return ('\r'), a line feed ('\n'), or a carriage return immediately followed by a line feed, or as the final sequence of characters in the file or stream if it ends without a newline sequence. If the file ends with a newline sequence, no additional empty line is returned. For example, a file containing `"line1\nline2\n"` produces the same two lines (`"line1"` and `"line2"`) as a file containing `"line1\nline2"`.

Copilot uses AI. Check for mistakes.
- Write the returned collection of lines to a file with the <xref:System.IO.File.WriteAllLines(System.String,System.Collections.Generic.IEnumerable{System.String},System.Text.Encoding)?displayProperty=nameWithType> method, or append them to an existing file with the <xref:System.IO.File.AppendAllLines(System.String,System.Collections.Generic.IEnumerable{System.String},System.Text.Encoding)?displayProperty=nameWithType> method.
- Create an immediately populated instance of a collection that takes an <xref:System.Collections.Generic.IEnumerable`1> collection of strings for its constructor, such as a <xref:System.Collections.Generic.IList`1> or a <xref:System.Collections.Generic.Queue`1>.

A line is defined as a sequence of characters followed by a carriage return ('\r'), a line feed ('\n'), or a carriage return immediately followed by a line feed. If the file ends with a newline sequence, no additional empty line is returned. For example, a file containing `"line1\nline2\n"` produces the same two lines (`"line1"` and `"line2"`) as a file containing `"line1\nline2"`.
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two issues in this added paragraph: (1) it starts with extra leading whitespace compared to the surrounding Markdown in this CDATA block, and (2) the line definition should include end-of-file/end-of-stream as a valid terminator (the last line is returned even without a trailing newline).

Suggested change
A line is defined as a sequence of characters followed by a carriage return ('\r'), a line feed ('\n'), or a carriage return immediately followed by a line feed. If the file ends with a newline sequence, no additional empty line is returned. For example, a file containing `"line1\nline2\n"` produces the same two lines (`"line1"` and `"line2"`) as a file containing `"line1\nline2"`.
A line is defined as a sequence of characters terminated by a carriage return ('\r'), a line feed ('\n'), a carriage return immediately followed by a line feed, or the end of the file or stream. If the file ends with a newline sequence, no additional empty line is returned. For example, a file containing `"line1\nline2\n"` produces the same two lines (`"line1"` and `"line2"`) as a file containing `"line1\nline2"`.

Copilot uses AI. Check for mistakes.
Comment on lines 1196 to 1200
A line is defined as a sequence of characters followed by a carriage return (0x000d), a line feed (0x000a), a carriage return followed by a line feed, <xref:System.Environment.NewLine*?displayProperty=nameWithType>, or the end-of-stream marker. The string that is returned does not contain the terminating carriage return or line feed. The return value is `null` if the end of the input stream has been reached.

If the stream ends with a newline sequence, no additional empty line is returned. For example, a stream containing `"line1\nline2\n"` produces the same two lines (`"line1"` and `"line2"`) as a stream containing `"line1\nline2"`.

If the method throws an <xref:System.OutOfMemoryException> exception, the reader's position in the underlying <xref:System.IO.Stream> is advanced by the number of characters the method was able to read, but the characters that were already read into the internal <xref:System.IO.TextReader.ReadLine*> buffer are discarded. Because the position of the reader in the stream cannot be changed, the characters that were already read are unrecoverable and can be accessed only by reinitializing the <xref:System.IO.TextReader> object. If the initial position within the stream is unknown or the stream does not support seeking, the underlying <xref:System.IO.Stream> also needs to be reinitialized.
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this TextReader docs block, the new note refers to a "stream", but TextReader isn't necessarily backed by a stream (for example, StringReader). Consider rephrasing to refer to the reader's underlying source/input instead of a stream to avoid implying a stream is required.

Suggested change
A line is defined as a sequence of characters followed by a carriage return (0x000d), a line feed (0x000a), a carriage return followed by a line feed, <xref:System.Environment.NewLine*?displayProperty=nameWithType>, or the end-of-stream marker. The string that is returned does not contain the terminating carriage return or line feed. The return value is `null` if the end of the input stream has been reached.
If the stream ends with a newline sequence, no additional empty line is returned. For example, a stream containing `"line1\nline2\n"` produces the same two lines (`"line1"` and `"line2"`) as a stream containing `"line1\nline2"`.
If the method throws an <xref:System.OutOfMemoryException> exception, the reader's position in the underlying <xref:System.IO.Stream> is advanced by the number of characters the method was able to read, but the characters that were already read into the internal <xref:System.IO.TextReader.ReadLine*> buffer are discarded. Because the position of the reader in the stream cannot be changed, the characters that were already read are unrecoverable and can be accessed only by reinitializing the <xref:System.IO.TextReader> object. If the initial position within the stream is unknown or the stream does not support seeking, the underlying <xref:System.IO.Stream> also needs to be reinitialized.
A line is defined as a sequence of characters followed by a carriage return (0x000d), a line feed (0x000a), a carriage return followed by a line feed, <xref:System.Environment.NewLine*?displayProperty=nameWithType>, or the end of the reader's input. The string that is returned does not contain the terminating carriage return or line feed. The return value is `null` if the end of the input has been reached.
If the input ends with a newline sequence, no additional empty line is returned. For example, input containing `"line1\nline2\n"` produces the same two lines (`"line1"` and `"line2"`) as input containing `"line1\nline2"`.
If the method throws an <xref:System.OutOfMemoryException> exception, the reader's position in its underlying source is advanced by the number of characters the method was able to read, but the characters that were already read into the internal <xref:System.IO.TextReader.ReadLine*> buffer are discarded. Because the reader's position in the underlying source cannot be reset, the characters that were already read are unrecoverable and can be accessed only by reinitializing the <xref:System.IO.TextReader> object. If the initial position within the underlying source is unknown or the source does not support repositioning, the underlying source also needs to be reinitialized.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

StreamReader.ReadLine should document how newline at the end of the file is treated

4 participants