I have the following method:
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "If we dispose of the csvWriter, it won't be available to write.")]
public static MemoryStream CreateCsvStream(IEnumerable<object> records)
{
MemoryStream memoryStream = new();
StreamWriter streamWriter = new(memoryStream);
CsvWriter csvWriter = new(streamWriter, CultureInfo.InvariantCulture);
csvWriter.Context.TypeConverterCache.AddConverter<bool>(new CollendaBooleanConverter());
csvWriter.Context.TypeConverterCache.AddConverter<bool?>(new CollendaBooleanConverter());
csvWriter.Context.TypeConverterCache.AddConverter<DateOnly>(new CollendaDateOnlyConverter());
csvWriter.Context.TypeConverterCache.AddConverter<DateOnly?>(new CollendaDateOnlyConverter());
csvWriter.Context.TypeConverterCache.AddConverter<decimal>(new CollendaDecimalConverter());
csvWriter.Context.TypeConverterCache.AddConverter<decimal?>(new CollendaDecimalConverter());
csvWriter.WriteRecords(records);
streamWriter.Flush();
return memoryStream;
}
This works, but as hinted in the SuppressMessage
, if I use using
so MemoryStream
, StreamWriter
, and/or CsvWriter
are disposed of, when I later execute the following code:
private void Upload(MemoryStream memoryStream)
{
_sftpClient.Connect();
_ = memoryStream.Seek(0, SeekOrigin.Begin);
string filePath = _collendaSftpConfig?.FilePath
?.InsertTimestamp()
?? throw new InvalidOperationException("CollendaSftpConfig configuration is missing FilePath.");
// Renci.SshNet's Sftp Client seems to have some async support, but it seems much more complicated to consume.
// There is no clear benefit to using it at this time.
_sftpClient.UploadFile(memoryStream, filePath);
_sftpClient.Disconnect();
}
I'll get an error like:
System.ObjectDisposedException HResult=0x80131622 Message=Cannot access a closed Stream. Source=System.Private.CoreLib StackTrace: at System.ThrowHelper.ThrowObjectDisposedException_StreamClosed(String objectName) at System.IO.MemoryStream.Seek(Int64 offset, SeekOrigin loc) at Enpal.Collenda.Client.SftpUploader.CollendaSftpClient.Upload(MemoryStream memoryStream) in C:\projects\FinTech\collenda-finance-listener\Collenda.Client.SftpUploader\CollendaSftpClient.cs:line 59
I did not experiment with every permutation of adding or not adding using to each of these, but I did try adding to all and some of them and received similar results.
Of course, the simple "solution" is just to leave them out and suppress this, but I'm concerned whether this is a reliable solution and whether there may be a better way to handle this situation (and potentially similiar situations in the future).
You're trying to read from the stream afterwards, and that's all. In that case, you don't want to dispose of the stream (because you still want to be able to read) but probably you do want to dispose of the StreamWriter
. (It won't actually matter with MemoryStream
, but I can understand wanting to do it.)
The simplest option here is to use the StreamWriter
constructor that allows you to suppress it closing the underlying stream:
public static MemoryStream CreateCsvStream(IEnumerable<object> records)
{
// No using statement here, because we want the stream to stay open
MemoryStream memoryStream = new();
// We *do* want to dispose of the StreamWriter
using StreamWriter streamWriter = new(memoryStream, leaveOpen: true);
// I assume that CsvWriter implements IDisposable too
using CsvWriter csvWriter = new(streamWriter, CultureInfo.InvariantCulture);
// Write to csvWriter here
...
// No need to flush the StreamWriter - that'll happen when it's disposed
return memoryStream;
}