C # — Прикреплять поток файлов при отправке в систему электронной почты.Исключение ObjectDisposedException

#c#

#c#

Вопрос:

Добрый день,

Я пытаюсь прикрепить файл потока файлов к вложению электронной почты, отправляющему. Сценарий таков, что мне нужно создать файл с помощью FileStream , а затем прикрепить его к моей электронной почте.

Пока что вот мой пример кода:

Для создания файла:

 public FileStream CreateFileStream(){
    using (var ms = new MemoryStream())
    {
    var writer = new StreamWriter(ms);

    writer.WriteLine("file content blah blah blahh");
    writer.Flush();

    //You have to rewind the MemoryStream before copying
    ms.Seek(0, SeekOrigin.Begin);

       using (var fs = new FileStream("Filename.txt", FileMode.Create))
       {
          ms.CopyTo(fs);
          return fs;
       }
    }
}
  

И вот мой пример кода для отправки электронной почты

 // attachment
var fileStreamFile = dto.FileStreamFile;

var contentType = new ContentType(MediaTypeNames.Text.Plain);
var attach = new Attachment(dto.FileStreamFile, contentType);
attach.ContentDisposition.FileName = "File.txt";

mail.Attachments.Add(attach);
...
// code for sending here
...
dto.FileStreamFile.Dispose(); // for disposing File Stream
  

fileStreamFile не равно null, но выдает ошибку, которая гласит,

Handle = 'dto.FileStreamFile.Handle' threw an exception of type 'System.ObjectDisposedException'

Когда я комментирую коды прикрепления, отправка электронной почты в любом случае работает нормально. Отправка электронной почты завершается неудачно, когда я включаю свой код для прикрепления FileStream

Любая помощь, пожалуйста? Заранее благодарю вас

Ответ №1:

Ваш код

 using (var fs = new FileStream("Filename.txt", FileMode.Create))
{
    ms.CopyTo(fs);
    return fs; // you are returning here
} // hang on, this is a using statement which will dispose fs!
  

Переведет в это

 FileStream fileStream = new FileStream("Filename.txt", FileMode.Create);
try
{
   return fileStream;
}
finally
{
   if (fileStream != null)
   {
      ((IDisposable)fileStream).Dispose();
   }
}
  

Важно отметить, что если вы return используете try finally , он сохраняет возвращаемое значение в локальной переменной, чтобы возвращать после finally . Которое будет выглядеть примерно так

 FileStream fileStream = new FileStream("Filename.txt", FileMode.Create);
try
{
   temp = fileStream;
}
finally
{
   if (fileStream != null)
   {
      ((IDisposable)fileStream).Dispose();
   }
}

return temp;
  

Мы можем взглянуть на IL, чтобы увидеть, что происходит

 IL_0000: ldstr "someFile"
IL_0005: ldc.i4.2
IL_0006: newobj instance void [mscorlib]System.IO.FileStream::.ctor(string, valuetype[mscorlib]System.IO.FileMode)
// stloc.0 Pops the current value from the top of the evaluation stack and stores it in the 
// local variable list at a specified index. This is your fs reference
IL_000b: stloc.0 
   .try
{
   // ldloc.0 Loads the local variable at a specific index onto the evaluation stack.
   // This is your fs reference
   IL_000c: ldloc.0
   // stloc.1 Pops the current value from the top of the evaluation stack and stores it in 
   // the local variable list at a specified index. This is your fs reference
   IL_000d: stloc.1
   IL_000e: leave.s IL_001a
} // end .try
finally
{
   IL_0010: ldloc.0
   IL_0011: brfalse.s IL_0019     
   IL_0013: ldloc.0
   // oh no we just Disposed fs!!!
   IL_0014: callvirt instance void [mscorlib]System.IDisposable::Dispose()
   IL_0019: endfinally
} // end handler

// ldloc.1 Loads the local variable at a specific index onto the evaluation stack.
// This is your fs reference
IL_001a: ldloc.1
//ret Returns from the current method, pushing a return value (if present) from 
//the callee's evaluation stack onto the caller's evaluation stack.
IL_001b: ret
  

Короче говоря, не возвращайте IDisposable ссылку из using инструкции, поскольку она будет удалена.

Вам нужно будет создать FileStream (без using ) и Dispose его в другом контексте или выполнить рефакторинг вашего кода.

Обновить

Но моя отправка электронной почты не работает после того, как я разместил инструкцию using, охватывающую отправку электронной почты с помощью stream

Возможно, вы чрезмерно усложняете это, попробуйте что-то вроде этого

 using (var stream = new MemoryStream())
   using (var writer = new StreamWriter(stream)) // using UTF-8 encoding by default
      using (var mailClient = new SmtpClient("localhost", 25))
         using (var message = new MailMessage("me@example.com", "you@example.com", "Just testing", "See attachment..."))
         {
            writer.WriteLine("file content blah blah blahh");
            writer.Flush();
            stream.Position = 0; // read from the start of what was written

            message.Attachments.Add(new Attachment(stream, "filename.csv", "text/csv"));

            mailClient.Send(message);
         }
  

Комментарии:

1. Спасибо за совет, сэр. Я уже рефакторил свой код. У меня есть еще один вопрос, поэтому я удалил инструкцию using using , могу ли я передать поток в другой класс, который его использует? например, после отправки электронного письма я хочу его утилизировать. Можно ли использовать что-то вроде dto.FileStreamFile.Dispose(); на основе моего кода выше

2. @AppleCiderYummy да, это сработало бы, и вы определенно хотите его утилизировать, возможно, попробовать, наконец, на случай, если по какой-то причине возникнет исключение. Однако, если возможно, я бы воздержался от использования a FileStream в качестве свойства a DTO , было бы лучше загрузить файловый поток в метод, который вам нужен, и поместить его в using инструкцию там.

3. Я понимаю. Но моя отправка электронной почты не работает после того, как я поместил using заявление, касающееся отправки электронной почты с stream