これをバグとして送信する前に、これをバグとみなすべきかどうかを確認するコミュニティのフィードバックを得たいと思っていました。 AttachmentBase
から派生LinkedResource
または他のクラスを作成する場合.Net Frameworkバグ:System.Net.Mail.AttachmentBaseは作成しなかったストリームを破棄します
、file nameまたはStream
のいずれかを取るコンストラクタのオーバーロードがあります。ファイル名を指定すると、AttachmentBase
は内部でFileStream
を作成し、MimePart.SetContent(Stream)
を使用してプライベートMimePart part
のコンテンツを割り当てます。 Stream
を取るコンストラクタを使用してAttachmentBase
を作成するときは、基になるコンテンツを直接Stream
に割り当てます。
AttachmentBase
がdisposedの場合は、MimePart
が内部処理され、Stream
の内容が内部処理されます。 Stream
を取っているコンストラクタを使用した場合、このMimePart
クラスはちょうどStream
を処理しませんでした。このバグは副作用です。クラス/メソッドが所有していないオブジェクトを破棄すると、そのオブジェクトを後で再利用することは不可能になります。使い捨てオブジェクトを作成する場合、そのオブジェクトはガベージコレクションの前に破棄できる必要があります。これにより、FileStreams
によってキャプチャされたファイルハンドルのようなリソースを解放することができます。
多くのメールを送信する必要があり、各メールに1つの画像があるとします。イメージは非常に大きく、すべての電子メールはこの同じ正確なイメージを使用します。このファイルを読み取ってMemoryStream
に追加し、を各メールのLinkedResource
に再利用できるはずです。これにより、ディスクの読み込みやメモリの使用量が少なくて済みます。 Dispose()
は、メッセージが送信されると、各メッセージとそのリソースで呼び出され、ハンドルと管理されていないメモリを解放します。 (Dispose()
というコードは、常にオブジェクトを作成したのと同じコードである必要があります)。Dispose()
メソッドが正しく実装されていないため、このメソッドは正しく実装されていません。下にあるStream
を作成していないイメージに置いただけです。後続のメッセージがこのStream
を使用しようとすると、ObjectDisposedException
がスローされます。
このバグの一時的な回避策は、メッセージごとにMemoryStream
をコピーし、コピーしたStreams
を使用することです。これにより、ディスクの読み込みはほとんど行われませんが、大きな画像がメモリに一度にコピーされます。
正しいパターンと慣行に従い、Stream
を作成したコードを廃棄して一度だけ使用すると仮定しないようにすることで、これはすべて回避できました。ここにこのバグの修正があります。コードは、同様のメソッドを置き換える必要があります。これにより、MimePart
の下位互換性が確保されます。 AttachmentBase
への変更は、Stream
に渡されるコードの変更点である可能性があります。この問題を解決するには、自分自身を適切に廃棄して渡すStream
を処理することです。
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();
}
}
}
1つのストリームを再利用できるため、回避策が大好きです。あなたが言ったように、それは 'Dispose()'の代わりに 'Close()'を呼び出すための基礎となるコードの実装に依存しています。ストリームを破棄する場合は、何もしないようにラッパーで 'Dispose()'をオーバーライドしなければなりません。これは 'using'ステートメントを中断させます。ストリームソースコードのかなりの部分を見た後、MSは他のdispose呼び出しからのみ 'Dispose()'を使い、他のものは 'Close()'を使います。私は、将来の変更があなたの回避策を破る可能性はほとんどないと主張するのは大体は安全だと思います。 – kkirkfield