Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C#, using "using" results in errors, is there a better practice than just not using "using"?

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).

like image 386
Brian Kessler Avatar asked Feb 02 '26 21:02

Brian Kessler


2 Answers

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;
}
like image 93
Jon Skeet Avatar answered Feb 04 '26 10:02

Jon Skeet


StreamWriter by default closes underlying stream on Dispose, you can use constructor accepting leaveOpen parameter and set it to true:

leaveOpen Boolean
true to leave the stream open after the StreamWriter object is disposed; otherwise, false.

MemoryStream memoryStream = new();
using StreamWriter streamWriter = new(memoryStream, leaveOpen: true);

Also when wrapping the StreamWriter into using there is no more need to call Flush - it will happen automatically on Dispose.

like image 32
Guru Stron Avatar answered Feb 04 '26 10:02

Guru Stron



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!