Skip to content

Commit 605ee7f

Browse files
authored
Use ReadExactly in ReadAllBytes and yield in ReadLines (#1681)
* Use ReadExactly in ReadAllBytes and yield in ReadLines * on second thought, open the file lazily in ReadLines
1 parent 9f733e4 commit 605ee7f

File tree

3 files changed

+180
-376
lines changed

3 files changed

+180
-376
lines changed

src/Renci.SshNet/Common/Extensions.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Globalization;
4+
#if !NET
5+
using System.IO;
6+
#endif
47
using System.Net;
58
using System.Net.Sockets;
69
using System.Numerics;
@@ -388,6 +391,24 @@ internal static T[] ToArray<T>(this ArraySegment<T> arraySegment)
388391
Array.Copy(arraySegment.Array, arraySegment.Offset, array, 0, arraySegment.Count);
389392
return array;
390393
}
394+
395+
#pragma warning disable CA1859 // Use concrete types for improved performance
396+
internal static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count)
397+
#pragma warning restore CA1859
398+
{
399+
var totalRead = 0;
400+
401+
while (totalRead < count)
402+
{
403+
var read = stream.Read(buffer, offset + totalRead, count - totalRead);
404+
if (read == 0)
405+
{
406+
throw new EndOfStreamException();
407+
}
408+
409+
totalRead += read;
410+
}
411+
}
391412
#endif
392413
}
393414
}

src/Renci.SshNet/SftpClient.cs

Lines changed: 41 additions & 35 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;
@@ -1727,7 +1728,7 @@ public byte[] ReadAllBytes(string path)
17271728
using (var stream = OpenRead(path))
17281729
{
17291730
var buffer = new byte[stream.Length];
1730-
_ = stream.Read(buffer, 0, buffer.Length);
1731+
stream.ReadExactly(buffer, 0, buffer.Length);
17311732
return buffer;
17321733
}
17331734
}
@@ -1760,23 +1761,7 @@ public string[] ReadAllLines(string path)
17601761
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
17611762
public string[] ReadAllLines(string path, Encoding encoding)
17621763
{
1763-
/*
1764-
* We use the default buffer size for StreamReader - which is 1024 bytes - and the configured buffer size
1765-
* for the SftpFileStream. We may want to revisit this later.
1766-
*/
1767-
1768-
var lines = new List<string>();
1769-
1770-
using (var stream = new StreamReader(OpenRead(path), encoding))
1771-
{
1772-
string? line;
1773-
while ((line = stream.ReadLine()) != null)
1774-
{
1775-
lines.Add(line);
1776-
}
1777-
}
1778-
1779-
return lines.ToArray();
1764+
return ReadLines(path, encoding).ToArray();
17801765
}
17811766

17821767
/// <summary>
@@ -1807,15 +1792,8 @@ public string ReadAllText(string path)
18071792
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
18081793
public string ReadAllText(string path, Encoding encoding)
18091794
{
1810-
/*
1811-
* We use the default buffer size for StreamReader - which is 1024 bytes - and the configured buffer size
1812-
* for the SftpFileStream. We may want to revisit this later.
1813-
*/
1814-
1815-
using (var stream = new StreamReader(OpenRead(path), encoding))
1816-
{
1817-
return stream.ReadToEnd();
1818-
}
1795+
using var sr = new StreamReader(OpenRead(path), encoding);
1796+
return sr.ReadToEnd();
18191797
}
18201798

18211799
/// <summary>
@@ -1825,12 +1803,16 @@ public string ReadAllText(string path, Encoding encoding)
18251803
/// <returns>
18261804
/// The lines of the file.
18271805
/// </returns>
1828-
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <see langword="null"/>.</exception>
1829-
/// <exception cref="SshConnectionException">Client is not connected.</exception>
1830-
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
1806+
/// <remarks>
1807+
/// The lines are enumerated lazily. The opening of the file and any resulting exceptions occur
1808+
/// upon enumeration.
1809+
/// </remarks>
1810+
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <see langword="null"/>. Thrown eagerly.</exception>
1811+
/// <exception cref="SshConnectionException">Client is not connected upon enumeration.</exception>
1812+
/// <exception cref="ObjectDisposedException">The return value is enumerated after the client is disposed.</exception>
18311813
public IEnumerable<string> ReadLines(string path)
18321814
{
1833-
return ReadAllLines(path);
1815+
return ReadLines(path, Encoding.UTF8);
18341816
}
18351817

18361818
/// <summary>
@@ -1841,12 +1823,36 @@ public IEnumerable<string> ReadLines(string path)
18411823
/// <returns>
18421824
/// The lines of the file.
18431825
/// </returns>
1844-
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <see langword="null"/>.</exception>
1845-
/// <exception cref="SshConnectionException">Client is not connected.</exception>
1846-
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
1826+
/// <remarks>
1827+
/// The lines are enumerated lazily. The opening of the file and any resulting exceptions occur
1828+
/// upon enumeration.
1829+
/// </remarks>
1830+
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <see langword="null"/>. Thrown eagerly.</exception>
1831+
/// <exception cref="SshConnectionException">Client is not connected upon enumeration.</exception>
1832+
/// <exception cref="ObjectDisposedException">The return value is enumerated after the client is disposed.</exception>
18471833
public IEnumerable<string> ReadLines(string path, Encoding encoding)
18481834
{
1849-
return ReadAllLines(path, encoding);
1835+
// We allow this usage exception to throw eagerly...
1836+
ThrowHelper.ThrowIfNull(path);
1837+
1838+
// ... but other exceptions will throw lazily i.e. inside the state machine created
1839+
// by yield. We could choose to open the file eagerly as well in order to throw
1840+
// file-related exceptions eagerly (matching what File.ReadLines does), but this
1841+
// complicates double enumeration, and introduces the problem that File.ReadLines
1842+
// has whereby the file is not closed if the return value is not enumerated.
1843+
return Enumerate();
1844+
1845+
IEnumerable<string> Enumerate()
1846+
{
1847+
using var sr = new StreamReader(OpenRead(path), encoding);
1848+
1849+
string? line;
1850+
1851+
while ((line = sr.ReadLine()) != null)
1852+
{
1853+
yield return line;
1854+
}
1855+
}
18501856
}
18511857

18521858
/// <summary>

0 commit comments

Comments
 (0)