在 C# 中,使用“using”会导致错误,是否有比不使用“using”更好的做法?

Bri*_*ler 3 c# memorystream using streamwriter csvhelper

我有以下方法:

    [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;
    }
Run Code Online (Sandbox Code Playgroud)

这是可行的,但正如 中所暗示的SuppressMessage,如果我使用usingso MemoryStreamStreamWriter和/或CsvWriter,当我稍后执行以下代码时,它们将被释放:

    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();
    }
Run Code Online (Sandbox Code Playgroud)

我会收到如下错误:

System.ObjectDisposeException HResult=0x80131622 Message=无法访问关闭的流。源= System.Private.CoreLib StackTrace:在System.ThrowHelper.ThrowObjectDisposeException_StreamClosed(字符串objectName)在System.IO.MemoryStream.Seek(Int64偏移量,SeekOrigin loc)在Enpal.Collenda.Client.SftpUploader.CollendaSftpClient.Upload(MemoryStream内存流)在 C:\projects\FinTech\collenda-finance-listener\Collenda.Client.SftpUploader\CollendaSftpClient.cs:第 59 行

我没有尝试添加或不添加 using 的每一种排列,但我确实尝试添加到所有和其中一些并收到了类似的结果。

当然,简单的“解决方案”只是将它们排除在外并抑制这种情况,但我担心这是否是一个可靠的解决方案以及是否有更好的方法来处理这种情况(以及将来可能出现的类似情况) 。

Gur*_*ron 12

StreamWriter默认情况下关闭底层流Dispose,您可以使用构造函数接受leaveOpen参数并将其设置为true

leaveOpen布尔值,用于在对象被释放
true后使流保持打开状态;StreamWriter否则,false

MemoryStream memoryStream = new();
using StreamWriter streamWriter = new(memoryStream, leaveOpen: true);
Run Code Online (Sandbox Code Playgroud)

此外,当将 包装StreamWriter成时using,不再需要调用Flush - 它会在 上自动发生Dispose


Jon*_*eet 12

之后您尝试从流中读取内容,仅此而已。在这种情况下,您不想处置流(因为您仍然希望能够读取),但可能您确实处置StreamWriter. (这实际上并不重要MemoryStream,但我可以理解想要这样做。)

这里最简单的选择是使用StreamWriter构造函数,它允许您禁止它关闭底层流:

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;
}
Run Code Online (Sandbox Code Playgroud)