.Net Framework错误:System.Net.Mail.AttachmentBase处理它所处理的流';不创造

.Net Framework错误:System.Net.Mail.AttachmentBase处理它所处理的流';不创造,.net,.net,在将其作为bug提交之前,我想获得社区反馈,看看这是否应该被视为bug 创建LinkedResource或从AttachmentBase派生的其他类时,会出现构造函数重载,这些重载采用或。当给定文件名时,AttachmentBase在内部创建一个FileStream,并使用MimePart.SetContent(Stream)为其分配私有MimePart部分内容。当使用接受流的构造函数创建附件库时,它直接将底层内容分配给该流 当AttachmentBase为时,它在内部处理MimePart,后者

在将其作为bug提交之前,我想获得社区反馈,看看这是否应该被视为bug

创建
LinkedResource
或从
AttachmentBase
派生的其他类时,会出现构造函数重载,这些重载采用或。当给定文件名时,
AttachmentBase
在内部创建一个
FileStream
,并使用
MimePart.SetContent(Stream)
为其分配私有
MimePart部分
内容。当使用接受
流的构造函数创建
附件库
时,它直接将底层内容分配给该

AttachmentBase
为时,它在内部处理
MimePart
,后者在内部处理
内容。如果使用了采用
流的构造函数
,那么这个
MimePart
类只是处理了它没有创建的
流!这个错误是一个副作用。处理类/方法不拥有的对象使得以后无法重用该对象。创建一次性对象时,应该能够在垃圾收集之前对该对象进行处理。这允许它释放资源,如
FileStreams
捕获的文件句柄

假设您需要发送大量电子邮件,并且每封电子邮件都有一个图像。图像非常大,所有电子邮件都使用相同的图像。您应该能够将此文件读入
内存流
,并将此
重新用于每封电子邮件中的
链接资源
。这将允许很少的磁盘读取和低内存使用率
Dispose()
在每条消息及其资源上被调用,以在消息发送后释放所有句柄和非托管内存。(同样,如果它实现了
IDisposable
,则应该能够在不再需要它时立即将其释放。调用
Dispose()
的代码应该始终与创建对象的代码相同。)因为此
Dispose()
方法没有正确实现,它只是将底层的
处理为它没有创建的图像。当后续消息尝试使用此
流时
,它们将抛出
ObjectDisposedException

此错误的临时解决方法是为每条消息复制
内存流
,并使用复制的
。这仍然会导致很少的磁盘读取,但现在大映像会在内存中复制很多次

通过遵循正确的模式和实践,允许创建
的代码作为处理它的代码,而不是假设它只会被使用一次,这一切都可以避免。下面是此错误的修复程序。该代码应取代类似的方法。这允许
MimePart
向后兼容。对
AttachmentBase
的更改可能是对在
流中传递的代码所做的重大更改。解决方法是通过自己正确处理传入的

public abstract class AttachmentBase : IDisposable
{
    MimePart part;

    protected AttachmentBase(Stream contentStream)
    {
        part.SetContent(contentStream, true);
    }

    protected AttachmentBase(Stream contentStream, string mediaType)
    {
        part.SetContent(contentStream, null, mediaType, true);
    }

    internal AttachmentBase(Stream contentStream, string name, string mediaType)
    {
        part.SetContent(contentStream, name, mediaType, true);
    }

    protected AttachmentBase(Stream contentStream, ContentType contentType)
    {
        part.SetContent(contentStream, contentType, true);
    }
}

internal class MimePart : MimeBasePart, IDisposable
{
    private bool _keepOpen;

    internal void SetContent(Stream stream, bool keepOpen = false)
    {
        if (stream == null)
        {
            throw new ArgumentNullException("stream");
        }

        if (streamSet && !_keepOpen)
        {
            this.stream.Dispose();
        }

        this.stream = stream;
        streamSet = true;
        streamUsedOnce = false;
        TransferEncoding = TransferEncoding.Base64;
        _keepOpen = keepOpen;
    }

    internal void SetContent(Stream stream, string name, string mimeType, bool keepOpen = false)
    {
        if (stream == null)
        {
            throw new ArgumentNullException("stream");
        }

        if (!string.IsNullOrEmpty(mimeType))
        {
            contentType = new ContentType(mimeType);
        }

        if (!string.IsNullOrEmpty(name))
        {
            ContentType.Name = name;
        }

        SetContent(stream, keepOpen);
    }

    internal void SetContent(Stream stream, ContentType contentType, bool keepOpen = false)
    {
        if (stream == null)
        {
            throw new ArgumentNullException("stream");
        }

        this.contentType = contentType;
        SetContent(stream, keepOpen);
    }

    public void Dispose()
    {
        if (stream != null && !_keepOpen)
        {
            stream.Dispose();
        }
    }
}

我认为我同意KeepOpen选项应该是这个API所支持的,类似于StreamReader所做的。但是,您建议的实现可能必须包括它,直到使用者创建对象的地方,以防止引入破坏性的更改

解决此问题的另一种方法是创建一个包装流,忽略关闭调用,并在完成后使用另一种方法关闭流

例如:

private class UncloseableStreamWrapper : Stream
{
    private readonly Stream _baseStream;
    public UncloseableStreamWrapper(Stream baseStream)
    {
        _baseStream = baseStream;
    }

    public override void Close()
    {
        // DO NOTHING HERE
    }

    public void CloseWrappedStream()
    {
        _baseStream.Close();
    }
    public override long Position
    {
        get
        {
            return _baseStream.Position;
        }
        set
        {
            _baseStream.Position = value;
        }
    }

    public override bool CanRead => _baseStream.CanRead;
    public override bool CanSeek => _baseStream.CanSeek;
    public override bool CanWrite => _baseStream.CanWrite;
    public override long Length => _baseStream.Length;
    public override void Flush() => _baseStream.Flush();
    public override int Read(byte[] buffer, int offset, int count)=> _baseStream.Read(buffer, offset, count);
    public override long Seek(long offset, SeekOrigin origin)=>_baseStream.Seek(offset, origin);
    public override void SetLength(long value) => _baseStream.SetLength(value);
    public override void Write(byte[] buffer, int offset, int count) => _baseStream.Write(buffer, offset, count);
}
然后在你的循环中,你只需重置位置就可以走了

var stream = new UncloseableStreamWrapper(System.IO.File.OpenRead(@"Z:\temp\temp.png"));
var lr = new LinkedResource(stream);
lr.Dispose();

stream.Position = 0;

lr = new LinkedResource(stream);
lr.Dispose();

stream.CloseWrappedStream();
Console.ReadLine();

这里需要注意的一点是,您正在将代码绑定到底层的
MimePart
的实现,该实现可能会随着将来的更新而改变,从而彻底破坏您的代码。

我喜欢您的解决方案,因为它允许重用一个流。正如您所说,它依赖于底层代码的实现来调用
Close()
,而不是
Dispose()
。如果它处理流,那么您必须重写包装器中的
Dispose()
,而不执行任何操作,这将导致
使用
语句中断。在查看了相当多的流源代码之后,MS似乎只使用来自其他Dispose调用的
Dispose()
,并使用
Close()。我认为可以肯定地说,未来的变化不太可能打破你的工作环境。