如何正确释放 SmtpClient?(C#)

37

VS 2010代码分析报告如下:

警告4 CA2000:Microsoft.Reliability:在方法 'Mailer.SendMessage()' 中,对象 'client' 沿所有异常路径未被处理。在所有引用超出作用域之前,请调用 System.IDisposable.Dispose 对象“client”。

我的代码是:

public void SendMessage()
    {
        SmtpClient client = new SmtpClient();

        client.Send(Message);
        client.Dispose(); 
        DisposeAttachments(); 
    }

如何正确处理客户端?

更新:为了回答Jon的问题,这里是处理附件函数的代码:

private void DisposeAttachments()
{
    foreach (Attachment attachment in Message.Attachments)
    {
        attachment.Dispose();
    }
    Message.Attachments.Dispose();
    Message = null; 
}

Last Update 全课程清单(非常简短)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Mail;

public class Mailer
{
    public MailMessage Message
    {
        get;
        set;
    }

    public Mailer(MailMessage message)
    {
        this.Message = message; 
    }

    public void SendMessage()
    {
        using (SmtpClient client = new SmtpClient())
        {
            client.Send(Message);
        }
        DisposeAttachments(); 
    }

    private void DisposeAttachments()
    {
        foreach (Attachment attachment in Message.Attachments)
        {
            attachment.Dispose();
        }
        Message.Attachments.Dispose();
        Message = null; 
    }
}

7
@JL - 与其手动处理附件,你应该手动处理邮件本身,这样就可以同时处理附件、备用视图和其他部件。请注意,在处理邮件本身时会自动处理其所有组成部分,无需单独处理附件。 - Giorgi
1
在以前的版本(.NET 4.0之前),SmtpClient并没有实现Dispose方法,但其仍为真。 - The Evil Greebo
7个回答

53
public void SendMessage()
{
    using (SmtpClient client = new SmtpClient())
    {
        client.Send(Message);
    }
    DisposeAttachments(); 
}

即使在Send方法调用期间抛出异常,客户端也会被处理。你很少需要显式调用Dispose - 它几乎总是在using语句中。

然而,这里并不清楚附件的作用。你的类是否实现了IDisposable接口?如果是,那么这可能是处理成员变量附件的地方。如果您确保它们在此处得到处理,那么您可能需要:

public void SendMessage()
{
    try
    {
        using (SmtpClient client = new SmtpClient())
        {
            client.Send(Message);
        }
    }
    finally
    {
        DisposeAttachments(); 
    }
}

一旦消息发送完成,我就不需要实例来处理消息或附件了。我是否应该实现一个析构函数呢? - JL.
SmtpClient 没有实现 IDisposable :-) - Steven
1
请注意,如果与 SMTP 服务器的连接中断,则有时会在调用 .Dispose() 而不是 .Send() 期间抛出异常。因此,请将整个 using 块放在 try-catch 中,而不仅仅是 Send() 调用。 - Alex from Jitbit
只有在您想要捕获该异常而不是让它冒泡时才这样做。 - Jon Skeet
1
@JonSkeet 当然你是对的。只是想强调一下,如果你想捕获连接错误 - 将 "dispose()" 调用保留在 TRY 块内。这不是人们所期望的。 - Alex from Jitbit
显示剩余2条评论

13

.NET 4.0中的SmtpClient类现在实现了IDisposable接口,而.NET 2.0中的SmtpClient类缺少此接口(正如Darin所指出的那样)。这是框架中的一个重大变化,当迁移到.NET 4.0时,您应该采取适当的措施。但是,在迁移到.NET 4.0之前,您可以在代码中缓解此问题。以下是一个示例:

var client = new SmtpClient();

// Do not remove this using. In .NET 4.0 SmtpClient implements IDisposable.
using (client as IDisposable)
{
    client.Send(message);
} 

这段代码在.NET 2.0 (+3.0和3.5)以及.NET 4.0下都能正确编译和运行。


1
但是...它会帮助正确处理客户端的释放吗? - Nyerguds
由于早期的3.5版本存在的真正问题是它没有向服务器发送QUIT消息,导致服务器一直等待通信,这显然会在重新连接到同一服务器时引起问题。 - Nyerguds
@Nyerguds:当然,这个结构不会修复那个问题。 - Steven
@Nyerguds 那么当类型转换失败时,到底会是什么空值呢?这种结构的整个设计思路就是能够同时在两种情况下使用(即带有和不带有可释放性资源的情况),因此在这种情况下不会出现任何 NullRefEx 异常。 - Steven
4
为什么不亲自测试一下呢?就像我在写这个回答之前所做的那样。不要只是想象。 - Steven
显示剩余2条评论

10
using (SmtpClient client = new SmtpClient())
{
    client.Send(Message);
    DisposeAttachments(); 
}

有趣的是 - 与 .NET 3.5 不同,SmtpClient 在 .NET 4.0 中实现了IDisposable接口,每天学到新东西。


@Darin 的主要好处是SMTP客户端现在在处理dispose时最终发送QUIT命令 :) 对此非常高兴! - JL.
@Darin: 你开玩笑吧?SmtpClient在.NET 4.0中实现了IDisposable接口?这可是个相当大的破坏性变化。这太伤人了。 - Steven
@Steven,是的,而且最后它似乎通过向服务器发送QUIT命令正确地关闭了连接。 - Darin Dimitrov
1
啧啧啧,IDisposable 滥用。System.Net 团队真是无耻。 - Hans Passant
3
请注意,如果您未使用网络传递方式并且未设置主机属性,则Dispose方法会引发InvalidOperationException异常!http://connect.microsoft.com/VisualStudio/feedback/details/539160/smtpclient-reports-invalidoperationexception-when-disposed-immediatelly-after-sending-mail-and-pickup-directory-is-used - Richard Deeming
@RichardDeeming提供的链接已经失效,但可以在waybackmachine上找到。显然,在.NET 4.5中修复了Dispose中的错误 https://web.archive.org/web/20130724055045/http://connect.microsoft.com/VisualStudio/feedback/details/539160/smtpclient-reports-invalidoperationexception-when-disposed-immediatelly-after-sending-mail-and-pickup-directory-is-used - fuchs777

5
我会这样做:
class Attachments : List<Attachment>, IDisposable
{
  public void Dispose()
  {
    foreach (Attachment a in this)
    {
      a.Dispose();
    }
  }
}

class Mailer : IDisposable
{
  SmtpClient client = new SmtpClient();
  Attachments attachments = new Attachments();

  public SendMessage()
  {
    [... do mail stuff ...]
  }

  public void Dispose()
  {
    this.client.Dispose();
    this.attachments.Dispose();
  }
}


[... somewhere else ...]
using (Mailer mailer = new Mailer())
{
  mailer.SendMail();
}

这将允许重复使用 SmtpClient 对象,如果您想发送多个电子邮件。

3

这是更加简洁的解决方案,可以通过代码审查测试(即使发送失败,也将始终调用dispose):

public void SendMessage()
{
    using (SmtpClient client = new SmtpClient())
    {   
        client.Send(Message);
        DisposeAttachments(); 
    }
}

0
我的原始问题是关于间歇性的发送失败。 例如,第一个Send()成功,第二个Send()失败,第三个Send()成功。 最初我认为我没有正确地处理disposing。 所以我使用了using()
无论如何,后来我添加了UseDefaultCredentials = false,并且Send()终于变得稳定了。 我仍然保留using()函数。

-4
public void SendMessage()
{
    try
    {
        using (SmtpClient client = new SmtpClient())
        {
            client.Send(Message);
        client.dispose()

        }
    }
    finally
    {
        DisposeAttachments(); 
    }
}

1
请尽量提供您的代码,并附上一些描述。 - Radim Köhler
7
using 块内调用 Dispose() 看起来完全是多余的。 - Nyerguds

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接