ReadAllLines for a Stream object?

There exists a File.ReadAllLines but not a Stream.ReadAllLines.

using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Test_Resources.Resources.Accounts.txt"))
using (StreamReader reader = new StreamReader(stream))
{
// Would prefer string[] result = reader.ReadAllLines();
string result = reader.ReadToEnd();
}

Does there exist a way to do this or do I have to manually loop through the file line by line?

82647 次浏览

You can write a method which reads line by line, like this:

public IEnumerable<string> ReadLines(Func<Stream> streamProvider,
Encoding encoding)
{
using (var stream = streamProvider())
using (var reader = new StreamReader(stream, encoding))
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}

Then call it as:

var lines = ReadLines(() => Assembly.GetExecutingAssembly()
.GetManifestResourceStream(resourceName),
Encoding.UTF8)
.ToList();

The Func<> part is to cope when reading more than once, and to avoid leaving streams open unnecessarily. You could easily wrap that code up in a method, of course.

If you don't need it all in memory at once, you don't even need the ToList...

using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Test_Resources.Resources.Accounts.txt"))
using (StreamReader reader = new StreamReader(stream))
{
// Would prefer string[] result = reader.ReadAllLines();
string[] result = reader.ReadToEnd().Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
}

If you want to use StreamReader then yes, you will have to use ReadLine and loop throught the StreamReader, reading line by line.

Something like that:

string line;


using (StreamReader reader = new StreamReader(stream))
{
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
}

or try

using (StreamReader reader = new StreamReader("file.txt"))
{


string[] content = reader.ReadToEnd().Replace("\n","").Split('\t');
}

The .EndOfStream property can be used in the loop instead of checking if the next line is not null.

List<string> lines = new List<string>();


using (StreamReader reader = new StreamReader("example.txt"))
{
while(!reader.EndOfStream)
{
lines.Add(reader.ReadLine());
}
}

Short Answer

Yes you have to loop line by line.

Details

Here the simplest approach. It is taken from ReadAllLines, File.cs > InternalReadAllLines > ReadLine, StreamReader.cs.

You will see that the reference version handles all line terminator combinations: \r, \n, and \r\n correctly.

ReadLine does not return an extra empty string when the line terminator is \r\n, which is typical in DOS/Windows.

ReadLine also discards any text after the final delimiter.

public static String[] ReadAllLines(this TextReader reader)
{
String line;
    

List<String> lines = new List<String>();


while ((line = reader.ReadLine()) != null)
{
lines.Add(line);
}


return lines.ToArray();
}

While there are reasons to not use ReadAllLines at all, this is what the op asked.

This accepts a TextReader, not just a StreamReader. It supports a StreamReader or a StringReader.

BTW the name StreamReader is an abomination, since it does not read streams, but implements TextReader for files. In contrast a Stream: "Provides a generic view of a sequence of bytes. This is an abstract class." In other words it could be a FileStream - binary stream with possibly no applicable text encoding.

Why Use ReadLine

Text files are post-fix delimited; meaning a new-line terminates each line. Also there are 3 combinations for newlines in common use across Windows, Unix and Mac O/S. Your application may never be ported to another O/S, but it may be expected to read an external file from a foreign O/S.

Split is not equivalent to ReadLine. Split is suited best used for infix delimited strings, such as comma separated lists. It is unsuited for post-fix strings, where the delimiter may be one of three combinations. Split "sees" (parses) \r followed by \n as 2 separate delimiters and returns an empty string. It also returns any text after the final delimiter.

The StringSplitOptions.RemoveEmptyEntries option suggested in some other answers removes all empty lines, including those which were in the original input.

Thus for the input:

line1\r
\r
line3\r\n

ReadLine returns 3 lines. The 2nd is empty. Split creates 4 strings. (There is an additional string after the last \n.) It then removes the 2nd and the 4th. This is not what ReadAllLines does.

Using the following extension method:

public static class Extensions
{
public static IEnumerable<string> ReadAllLines(this StreamReader reader)
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}

It's possible to get to your desired code:

using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Test_Resources.Resources.Accounts.txt"))
using (StreamReader reader = new StreamReader(stream))
{
string[] result = reader.ReadAllLines().ToArray();
}