.Net Framework错误:System.Net.Mail.AttachmentBase处理它所处理的流';不创造
在将其作为bug提交之前,我想获得社区反馈,看看这是否应该被视为bug 创建.Net Framework错误:System.Net.Mail.AttachmentBase处理它所处理的流';不创造,.net,.net,在将其作为bug提交之前,我想获得社区反馈,看看这是否应该被视为bug 创建LinkedResource或从AttachmentBase派生的其他类时,会出现构造函数重载,这些重载采用或。当给定文件名时,AttachmentBase在内部创建一个FileStream,并使用MimePart.SetContent(Stream)为其分配私有MimePart部分内容。当使用接受流的构造函数创建附件库时,它直接将底层内容分配给该流 当AttachmentBase为时,它在内部处理MimePart,后者
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()。我认为可以肯定地说,未来的变化不太可能打破你的工作环境。