dummzeuch 1506 Posted August 4, 2023 It has always irked me that in C you can write: while (0<(BytesRead=Read(...))) { // .. } But in Delphi you have to use the much less readable BytesRead := Stream.Read(Buffer, BufSize); while BytesRead > 0 do begin // .. BytesRead := Stream.Read(Buffer, BufSize); end; Today I had enough and wrote these simple helper functions: function TStream_TryRead(_st: TStream; var _Buffer: TBytes; _Count: Int32; out _BytesRead: Int32): Boolean; overload; inline; begin _BytesRead := _st.Read(_Buffer, _Count); Result := (_BytesRead > 0); end; function TStream_TryRead(_st: TStream; var _Buffer; _Count: Int32; out _BytesRead: Int32): Boolean; overload; inline; begin _BytesRead := _st.Read(_Buffer, _Count); Result := (_BytesRead > 0); end; function TStream_TryRead(_st: TStream; _Buffer: TBytes; _Offset, _Count: Int32; out _BytesRead: Int32): Boolean; overload; inline; begin _BytesRead := _st.Read(_Buffer, _Offset, _Count); Result := (_BytesRead > 0); end; function TStream_TryRead(_st: TStream; _Buffer: TBytes; _Offset, _Count: Int64; out _BytesRead: Int64): Boolean; overload; inline; begin _BytesRead := _st.Read64(_Buffer, _Offset, _Count); Result := (_BytesRead > 0); end; With these you can write: while TStream_TryRead(Buffer, BufSize, BytesRead) do begin // .. end; Yes, that's far from being rocket science. It's probably possible to convert these into a class helper, but I can't be bothered. (Original Blog post.) 1 Share this post Link to post
uligerhardt 18 Posted August 5, 2023 Wouldn't a repeat-until loop do the trick? 1 Share this post Link to post
dummzeuch 1506 Posted August 6, 2023 13 hours ago, uligerhardt said: Wouldn't a repeat-until loop do the trick? Care to provide an example? Share this post Link to post
Remy Lebeau 1403 Posted August 6, 2023 9 hours ago, dummzeuch said: Care to provide an example? repeat BytesRead := Stream.Read(Buffer, BufSize); if BytesRead <= 0 then Break; // .. until False; Share this post Link to post
dummzeuch 1506 Posted August 7, 2023 (edited) 13 hours ago, Remy Lebeau said: repeat BytesRead := Stream.Read(Buffer, BufSize); if BytesRead <= 0 then Break; // .. until False; Hm, an endless loop with an obscured break is not quite what I would call elegant and easily readable. But yes, that's one way a repeat loop "would do the trick". Edited August 7, 2023 by dummzeuch Share this post Link to post
David Heffernan 2347 Posted August 7, 2023 Weird repeat loop. I'd do: repeat BytesRead := Stream.Read(Buffer, BufSize); until BytesRead < BufSize; 1 Share this post Link to post
Uwe Raabe 2059 Posted August 7, 2023 @David Heffernan, that was my first idea, too. There is just some code missing that handles the BytesRead > 0 case before the loop exits. Some if-then after the read cannot be avoided in this case. Share this post Link to post
uligerhardt 18 Posted August 7, 2023 I think a repeat is conceptually the right thing here, as you want to read at least once unconditionally. And of course written like David did. 😉 Share this post Link to post
David Heffernan 2347 Posted August 7, 2023 I not advocating the repeat. I think the TryRead is nice. Class helper for TStream would be good. I general TryXXX methods tend to be useful in loads of places. Share this post Link to post
Remy Lebeau 1403 Posted August 7, 2023 (edited) 1 hour ago, David Heffernan said: Weird repeat loop. I'd do: repeat BytesRead := Stream.Read(Buffer, BufSize); until BytesRead < BufSize; Read() is not guaranteed to return as many bytes as requested. The documentation even says so. Use other ReadXXX() methods for that purpose instead. Since Read() can return fewer bytes without actually failing, breaking the loop on a lesser return is wrong if the intent is to read the whole stream. Only a return of -1 (error) or 0 (end of stream) should be the actual breaking condition. I prefer using 'repeat ... until False' because it avoids having to test the return value twice, eg: repeat BytesRead := Stream.Read(Buffer, BufSize); if BytesRead > 0 then ... until BytesRead <= 0; Edited August 7, 2023 by Remy Lebeau Share this post Link to post
David Heffernan 2347 Posted August 7, 2023 49 minutes ago, Remy Lebeau said: Only a return of -1 (error) I didn't see that condition documented. I also hadn't realised that some streams can read fewer than the requested bytes but there still could be more left. That does seem a strange design choice. If you can wait for at least one byte then surely you can wait for all requested. Share this post Link to post
dummzeuch 1506 Posted August 7, 2023 (edited) 2 hours ago, David Heffernan said: I also hadn't realised that some streams can read fewer than the requested bytes but there still could be more left. That does seem a strange design choice. If you can wait for at least one byte then surely you can wait for all requested. That depends on what you want to do with these bytes. It might be crucial to process them as fast as they can be read. E.g. if they come from a device (e.g. via serial port) and you need a time stamp to know when exactly they were received (Yes, I know from experience that this would be far from reliable. We are talking Windows, not some real time OS here.) Edited August 7, 2023 by dummzeuch Share this post Link to post
dummzeuch 1506 Posted August 7, 2023 (edited) 4 hours ago, David Heffernan said: Weird repeat loop. I'd do: repeat BytesRead := Stream.Read(Buffer, BufSize); until BytesRead < BufSize; Shouldn't that be until BytesRead <= 0 ? Hm, And where does the code to process those bytes go in this case? I admit I should have included a call for that in my original example. Something like this: while TStream_TryRead(Buffer, BufSize, BytesRead) do begin Process(Buffer, BytesRead) end; Because what's the point in reading from the stream when you just throw away the data read from it? Unless of course you want to simply empty the stream. Of course, the Process procedure itself could check whether the BytesRead parameter is <= 0. In that case a repeat loop would work: repeat BytesRead := Stream.Read(Buffer, BufSize); Process(Buffer, BytesRead; until BytesRead <= 0; But that just means that the if statement goes into the Process procedure instead of the loop: repeat BytesRead := Stream.Read(Buffer, BufSize); if BytesRead > 0 then Process(Buffer, BytesRead; until BytesRead <= 0; Edited August 7, 2023 by dummzeuch Share this post Link to post
Fr0sT.Brutal 900 Posted August 7, 2023 2 hours ago, David Heffernan said: That does seem a strange design choice. If you can wait for at least one byte then surely you can wait for all requested. This design was made to handle streaming sources (socket, pipe, file being written, etc). At the moment of call there could be no data but next time it could arrive (I'm sure you know it anyway). But it really could puzzle those who are not aware of this behavior and are sure that Read() really waits until it reads requested amount of data. 1 Share this post Link to post
Remy Lebeau 1403 Posted August 7, 2023 5 hours ago, David Heffernan said: I didn't see that condition documented. I also hadn't realised that some streams can read fewer than the requested bytes but there still could be more left. Sadly, not only is -1 not documented, but it's also not always implemented. For example, THandleStream.Read() returns 0 on failure and end-of-file. Which makes error handling practically impossible for some stream types. 5 hours ago, David Heffernan said: That does seem a strange design choice. If you can wait for at least one byte then surely you can wait for all. That is what the ReadXXX() methods are meant for. Read() is the lower level method that they all use internally. 1 Share this post Link to post