Skip to content

Commit f3bb926

Browse files
committed
Use ReadExactly in ReadAllBytes and yield in ReadLines
1 parent d40bc43 commit f3bb926

File tree

3 files changed

+142
-327
lines changed

3 files changed

+142
-327
lines changed

src/Renci.SshNet/Common/Extensions.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
using System.Collections.Generic;
33
using System.Diagnostics;
44
using System.Globalization;
5+
#if !NET
6+
using System.IO;
7+
#endif
58
using System.Net;
69
using System.Net.Sockets;
710
using System.Numerics;
@@ -406,6 +409,24 @@ internal static T[] ToArray<T>(this ArraySegment<T> arraySegment)
406409
Array.Copy(arraySegment.Array, arraySegment.Offset, array, 0, arraySegment.Count);
407410
return array;
408411
}
412+
413+
#pragma warning disable CA1859 // Use concrete types for improved performance
414+
internal static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count)
415+
#pragma warning restore CA1859
416+
{
417+
var totalRead = 0;
418+
419+
while (totalRead < count)
420+
{
421+
var read = stream.Read(buffer, offset + totalRead, count - totalRead);
422+
if (read == 0)
423+
{
424+
throw new EndOfStreamException();
425+
}
426+
427+
totalRead += read;
428+
}
429+
}
409430
#endif
410431
}
411432
}

src/Renci.SshNet/SftpClient.cs

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Diagnostics.CodeAnalysis;
66
using System.Globalization;
77
using System.IO;
8+
using System.Linq;
89
using System.Net;
910
using System.Runtime.CompilerServices;
1011
using System.Runtime.ExceptionServices;
@@ -1712,7 +1713,7 @@ public byte[] ReadAllBytes(string path)
17121713
using (var stream = OpenRead(path))
17131714
{
17141715
var buffer = new byte[stream.Length];
1715-
_ = stream.Read(buffer, 0, buffer.Length);
1716+
stream.ReadExactly(buffer, 0, buffer.Length);
17161717
return buffer;
17171718
}
17181719
}
@@ -1745,23 +1746,7 @@ public string[] ReadAllLines(string path)
17451746
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
17461747
public string[] ReadAllLines(string path, Encoding encoding)
17471748
{
1748-
/*
1749-
* We use the default buffer size for StreamReader - which is 1024 bytes - and the configured buffer size
1750-
* for the SftpFileStream. We may want to revisit this later.
1751-
*/
1752-
1753-
var lines = new List<string>();
1754-
1755-
using (var stream = new StreamReader(OpenRead(path), encoding))
1756-
{
1757-
string? line;
1758-
while ((line = stream.ReadLine()) != null)
1759-
{
1760-
lines.Add(line);
1761-
}
1762-
}
1763-
1764-
return lines.ToArray();
1749+
return ReadLines(path, encoding).ToArray();
17651750
}
17661751

17671752
/// <summary>
@@ -1792,15 +1777,8 @@ public string ReadAllText(string path)
17921777
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
17931778
public string ReadAllText(string path, Encoding encoding)
17941779
{
1795-
/*
1796-
* We use the default buffer size for StreamReader - which is 1024 bytes - and the configured buffer size
1797-
* for the SftpFileStream. We may want to revisit this later.
1798-
*/
1799-
1800-
using (var stream = new StreamReader(OpenRead(path), encoding))
1801-
{
1802-
return stream.ReadToEnd();
1803-
}
1780+
using var sr = new StreamReader(OpenRead(path), encoding);
1781+
return sr.ReadToEnd();
18041782
}
18051783

18061784
/// <summary>
@@ -1815,7 +1793,7 @@ public string ReadAllText(string path, Encoding encoding)
18151793
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
18161794
public IEnumerable<string> ReadLines(string path)
18171795
{
1818-
return ReadAllLines(path);
1796+
return ReadLines(path, Encoding.UTF8);
18191797
}
18201798

18211799
/// <summary>
@@ -1831,7 +1809,30 @@ public IEnumerable<string> ReadLines(string path)
18311809
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
18321810
public IEnumerable<string> ReadLines(string path, Encoding encoding)
18331811
{
1834-
return ReadAllLines(path, encoding);
1812+
// We open the file eagerly i.e. outside of the state machine created by yield,
1813+
// in order to a) throw resulting (e.g. file-related) exceptions eagerly; and b)
1814+
// to match what File.ReadLines does.
1815+
// This probably makes it behave more predictably/closer to what most people expect.
1816+
// The downside is that if the return value is never enumerated, the file
1817+
// is never closed (we can't do "using" here because it would be disposed
1818+
// as soon as we return). This conundrum also exists with File.ReadLines.
1819+
1820+
var sr = new StreamReader(OpenRead(path), encoding);
1821+
1822+
return Enumerate(sr);
1823+
1824+
static IEnumerable<string> Enumerate(StreamReader sr)
1825+
{
1826+
using (sr)
1827+
{
1828+
string? line;
1829+
1830+
while ((line = sr.ReadLine()) != null)
1831+
{
1832+
yield return line;
1833+
}
1834+
}
1835+
}
18351836
}
18361837

18371838
/// <summary>

0 commit comments

Comments
 (0)