UML软件工程组织

检查我的电子邮件
Duncan Mackenzie
Microsoft Developer Network
2003年3月26日
摘要:Duncan Mackenzie 介绍如何构建一种工具,使用 Microsoft .NET Framework 的 System.Net 命名空间来检查 POP3 电子邮件帐户中的未读邮件。

适用于:
   Microsoft® Visual Basic® .NET

下载本文的源代码(英文)。

妻子带给我的软件设计灵感

毫无目的地编码就像不带购物清单到商店购物一样。当然,您可能过得很愉快,而且买到了一些东西(某个人在将来的某一天会用到的东西)但却不能解决今天的晚饭问题。这就是我撰写这一专栏的初衷(当然是为了编码,而不是为了购物)。但后来我却把编码的事抛在脑后,写了一些很酷的东西(您不得不照我的思路读下去),因此当最终期限越来越近时,我还没有任何东西可以发表。幸运的是,当我为我的妻子 Laura 设置一台运行 Microsoft® Windows® XP Home 的“新”计算机(其实是我的旧计算机)后,我从中获得了一些灵感。

迄今为止,我从未运行过安装了 XP Home 的计算机(我的计算机通常运行的是连接到域中的 Windows XP Pro),因此 XP Home 的欢迎屏幕(请参见图 1)给我留下了深刻的印象。Laura 也是一样,她甚至还注意到一个我从未发现的功能:如果登录后长时间不使用系统,系统将切换回登录屏幕(或者按 Windows 键 + L),这时屏幕将显示未读的电子邮件数目!这对她来说真是太妙了,因为她可以在经过计算机时快速扫一眼,看看是否有新的电子邮件,而不必输入密码。啊!原本平淡无奇的计算机或电子解决方案却给我的妻子带来这么大的帮助,这种感觉真是好极了!

图 1:欢迎使用 Windows XP!

令人沮丧的是,屏幕上总是显示同样的内容:7 封未读电子邮件,即使 Laura 根本没有任何未读的电子邮件时也是如此。这种设置可太叫人失望了:本来,系统通知您有七封可能会让您非常激动的新邮件,登录后却发现没有任何新邮件。原本欣喜的感觉一扫而光,我要尽快研究研究解决方案。

那七封未读邮件原来在她的 hotmail 收件箱中,而登录屏幕上显示的值是通过具有明确名称的 SHSetUnreadMailCount(英文)API 调用设置的。Laura 现在使用 hotmail 只是接收 MSN® Messenger(英文),因此知道她有多少封未读的 hotmail 邮件并不是很有用。在这个现实面前,我脑子里的那个充满 Microsoft 观念的思维机器立刻飞速运转起来,而且我相信我的朋友们也会如此。突然我有了灵感:我可以创建一个小应用程序,使登录屏幕显示更有用的值。

现在,我要澄清几个细节:Microsoft® Outlook® Express 也可以设置这个值,因此如果使用此应用程序接收电子邮件,则可以设置值,但 Outlook 却不行。我要构建的应用程序应该能够处理几个操作:连接一组 POP3 服务器、获取新邮件的数量并填充相应的注册表值。但是,在应用程序的开发完成之后,我才意识到如果忽略 Outlook,一切努力都将付之东流,因为 Laura 的邮件客户端就是 Outlook,所以我把 Outlook 作为一种附加功能添加到程序中。

根据我的习惯,我将整个工作划分成一系列可以独立完成的任务:

  1. 连接 POP3 服务器,并检查当前的邮件数量。
  2. 允许您使用主机和用户 ID/密码信息来配置一组 POP3 服务器。
  3. 以可靠的方式保存服务器配置信息(包括用户 ID/密码)。
  4. 编写在后台运行的应用程序,并使该应用程序每 n 分钟对已配置的每台服务器进行检查。
  5. 只要邮件数量发生改变,就更新 Windows XP 登录屏幕设置。
  6. 另外,为了更有意思一些,可以使用 Outlook 2000 或 XP 来执行所有相同的任务。

您可以获得全部代码,但我会按顺序介绍各个项目并说明代码是如何在各种情况下工作的。

连接 POP3 服务器

我并非靠编写套接字代码谋生,因此作为 Microsoft 软件的用户,您应对此感到高兴。如果我的目的就是完成这项工作,那么我会寻找其他人的 POP3 组件(尽管我必须要购买它)。这会更有效,也更简单。但我的目的是向您展示一些 Microsoft® .NET 代码,因此我认为对您来说,观看我围绕 System.NET 命名空间和 POP3 spec(英文)编写自己的组件会给您带来乐趣和启发。

第一件事是创建一个小应用程序,用于在一个 Button1_Click 例程中完成所有 POP3 工作,并以一种非常程序化的方式(所有代码都在一个大的过程中)进行工作,直到我让它再次执行任务。要在应用程序中使用此代码,还需要执行一些整理工作。我将它重组为适当的类,并提取一些有关 POP3 命令和服务器信息的特定内容,从而方便您在需要时进行更改。我当时自我感觉很好,想把这个过程称为“refactoring(再分解)”阶段,但是 Microsoft® Word 认为这个词拼错了,我也同意这个结论。

如果您从未使用过 .NET 中的套接字类,您也不必担心,这些类非常简单直观。我只用了几分钟就实现了连接并发送数据。我使用 System.Net.Sockets.TcpClient(英文)的一个实例来创建与 POP3 服务器的连接,在建立连接以后抓取 NetworkStream(英文)对象。

 
Public Sub New(ByVal server As String, _
        ByVal port As Integer, _
        ByVal delay As Integer)
    Try
        m_Client = New TcpClient()
        m_Client.Connect(server, port)
        m_NS = m_Client.GetStream()
        m_Delay = delay
        Dim sResponse As String = GetResponse().Trim
        If sResponse.Substring(0, 3) <> "+OK" Then
            Throw New Exception("连接失败")
        End If
    Catch se As SocketException
        MsgBox(se.Message & vbCrLf & vbCrLf & se.ToString, _
            MsgBoxStyle.Exclamation, "套接字异常!")
        Throw New Exception("连接失败", se)
    Catch ex As Exception
        MsgBox(ex.Message & vbCrLf & vbCrLf & ex.ToString, _
            MsgBoxStyle.Exclamation, "异常!")
        Throw New Exception("连接失败", ex)
    End Try
End Sub

创建开放式连接和数据流之后,接下来要做的就是发送和接收文本。我提取了 NetworkStream 代码,以便读取字节数组并将其写入到几个高级函数中:SendCommandGetResponse。POP3 规范介绍了两种类型的服务器响应,即单行和多行,因此我在两个函数的参数列表中加入了 MultiLine 标记。

 
Private Overloads Function GetResponse() As String
    Return GetResponse(False)
End Function

Private Overloads Function GetResponse( _
        ByVal multiLine As Boolean) As String
    'GetResponse 的任务是等待
    '服务器响应,以便以不同方式完成
    '单行和多行响应端,
    '因此它们的结束条件
    '也应该稍有不同。
    Dim sOutput As String = ""
    Dim input As Integer
    Dim str(4096) As Byte
    Dim startTime As Date = Now
    Dim endCondition As String

    If multiLine Then
        endCondition = vbCrLf & vbCrLf & "."
    Else
        endCondition = vbCrLf
    End If

    Do
        While m_NS.DataAvailable()
            startTime = Now
            input = m_NS.Read(str, 0, 4096)
            sOutput &= ASCIIEncoding.ASCII.GetChars( _
                str, 0, input)
        End While
    Loop Until sOutput.IndexOf(endCondition) >= 0 _
        Or Now.Subtract(startTime).TotalMilliseconds > Me.m_Delay

    If sOutput.IndexOf(endCondition) < 0 Then
        Return sOutput
    Else
        Return sOutput
    End If
End Function

'SendCommand 用于发送字符串
'并接收响应
Public Overloads Function SendCommand( _
        ByVal command As String) As String
    Return SendCommand(command, False)
End Function

Public Overloads Function SendCommand( _
        ByVal command As String, _
        ByVal multiLine As Boolean) As String
    Dim user As Byte()
    user = ASCIIEncoding.ASCII.GetBytes(command)
    m_NS.Write(user, 0, user.GetLength(0))
    Return GetResponse(multiLine)
End Function

注意:我本可以在 NetworkStream 的顶部打开一对友好的流对象,例如 StreamReader(英文)和 StreamWriter(英文),但我还是坚持使用字节数组。如果您要查看有关在 NetworkStream 中使用 StreamReader/StreamWriter 的示例,请参阅由 Andrew Duthiecheck 撰写的这篇 MSDN Magazine 文章(英文)。

成功发送和接收信息后,我需要使用更高级别的套接字代码(就如同这些代码是其他人编写的,而我并不关心其工作原理一样),因此我决定将该代码提取到实用程序类中。然后,在主 POP3Server 类中,我实现了简单的 POP3 命令集,用于检索邮件列表。我使用 USER 和 PASS 命令登录到服务器,获取邮件数量(使用 STAT),然后列出每个邮件的标题(使用 TOP)。我从这些标题中分析出三条重要信息:发件人、主题和邮件 ID。获取邮件 ID 的目的是获得每个邮件的唯一标识符,借助这个标识符,我的程序就能了解邮件何时是最新的。如果发现了新邮件,就会引发一个事件并传递主题和发件人信息。以下代码片段显示了邮件标题的检索,以及新电子邮件事件的触发。如果将这些代码放在完整的源代码环境中查看,您的印象会更加深刻。

 
Dim msg As POP3.Message
If msgCount > 0 Then
    Dim i As Integer
    For i = 1 To msgCount
        '使用 TOP 命令
        '获取邮件的标题
        response = p3.SendCommand( _
            String.Format(Me.TopCmd, i), True)
        msg = ParseResponse(response)

        Dim msgIndex As Integer = 0
        Dim found As Boolean = False
        '检查此邮件是否为新邮件
        Do While msgIndex < Me.m_Messages.Count And Not found
            If Me.m_Messages(msgIndex).ID = msg.ID Then
                found = True
            End If
            msgIndex += 1
        Loop
        If Not found Then
            Me.m_Messages.Add(msg)
            '如果是新邮件,则引发 NewEmail
            '事件,传递发件人和主题
            RaiseEvent NewEmail(CObj(Me), _
                New NewEmailEventArgs( _
                    msg.From, msg.Subject, msg.ID))
        End If
    Next
End If

该事件由我的主应用程序捕获,现在我们来看一下中心应用程序代码。

编写用于系统任务栏的应用程序

我决定通过系统任务栏的图标实现此应用程序,因为这样可以方便且相对自由地访问应用程序的选项和新邮件的通知。

图 2:我创建了一个系统任务栏图标,因为图标多多益善。

我在编程时,把几乎整个应用程序都作为一个 Microsoft® Visual Basic® 模块(对于 C# 类型,这个模块相当于一个所有成员都为静态的类),并且仅使用了一个选项对话框窗体。使用 Visual Basic 6.0 完成编码是非常有趣的。我编写的应用程序使用了系统任务栏图标、计时器和上下文菜单,而不需要使用不可见的窗体。我确实不喜欢创建不可见的窗体,尤其是那些仅在启动的瞬见可以看到的窗体。因此,当我发现并不需要不可见的窗体时,我很高兴。在这个主模块中,我创建了一个 Hans Blomme's wonderful NotifyIconXP(英文)的实例、上下文菜单和一些菜单项,以及一个计时器。然后,我为菜单项、计时器编写了事件处理程序,甚至为通知图标的 BalloonClick 事件(尽管我并不用这个事件,但我认为您可能需要它)也编写了一个事件处理程序。

 

'服务器列表
Dim servers As New POP3ServerCollection()

'轮询电子邮件服务器的计时器
Dim checkTimer As New Timer()

'notifyIcon 提供应用程序的主要用户界面
'通过它可以弹出气泡式信息等。
Dim ni As HansBlomme.Windows.Forms.NotifyIcon

'常规的应用程序首选项
Dim appSettings As Settings

'我将此窗体保留为模块级
'变量,因此使用 ViewOptions 菜单可以避免
'一次打开多个实例
Dim myFrm As frmOptions

Sub Main()
    '从序列化的 xml 文件中
    '加载服务器列表和设置信息 
    Reload()

    '遍历所有加载的
    '服务器,并为 NewE-mail 事件
    '附加一个处理程序。
    Dim srv As POP3Server
    For Each srv In servers
        AddHandler srv.NewE-mail, AddressOf NewE-mail
    Next

    'appSettings 以秒为单位存储间隔,
    '计时器以毫秒为单位进行工作,因此,为使它们协同工作,
    '必须进行一定的转换。
    checkTimer.Interval = appSettings.CheckInterval * 1000
    checkTimer.Enabled = True

    '为计时器的 Tick 事件添加处理程序
    AddHandler checkTimer.Tick, AddressOf CheckE-mail_Tick

    '创建新图标,删除 HansBlomme 部分,以使用
    '标准的 NotifyIcon。此外,还需要对其他代码
    '进行更改
    ni = New HansBlomme.Windows.Forms.NotifyIcon()
    ni.Icon = New Icon(GetType(Main), "mail.ico")
    ni.Visible = True

    '当用户单击由通知图标弹出的气泡式信息时,
    '添加事件处理程序。标准的 NotifyIcon 
    '不提供此事件,因此如果不使用 HansBlomme 图标, 
    '则需要删除此行。
    AddHandler ni.BalloonClick, AddressOf NotificationBalloonClicked

    '设置上下文菜单,包括事件处理程序
    Dim ctxMenu As New ContextMenu()
    ctxMenu.MenuItems.Add("查看选项", AddressOf ViewOptionsForm)
    ctxMenu.MenuItems.Add("退出", AddressOf EndApplication)
    '并将其指定给 NotifyIcon,以便在右击该图标时,
    '它可以弹出。
    ni.ContextMenu = ctxMenu

    '运行应用程序,使用 Application.Run 
    '来创建新的消息循环
    Application.Run()

    '当退出应用程序时
    '隐藏通知图标并
    ni.Visible = False

    '将服务器信息和设置保存到 xml 文件中
    Persist()
End Sub


保存设置

我在 Main() 例程的开头调用了 Reload(),在结尾调用了 Persist()。这两个过程将我的设置和 POP3 服务器信息保存到磁盘(在 Persist 中),并在启动时又加载它们(在 Reload 中)。当您要将对象(甚至是结构复杂的对象)保存到磁盘时,最好执行序列化。这段代码引用的 POP3ServerCollection 类是使用 GotDotNet(英文)中的 Collection Generator(英文)生成的,可以从可下载的源代码中获得。

 

Public Sub Persist()
    '使用序列化将 Settings 对象和
    'POP3 服务器的集合保存到 XML 文件中
    '请注意,与 Background Copying 一文不同,
    '这些文件并未保存到孤立的存储区中。
    '虽然可以将它们保存在孤立的存储区中,但我想
    '这一次应该有所变化。

    '指出 xml 文件的路径,注意
    '使用 IO.Path.Combine... 要比仅
    '将它们连接起来好得多。
    Dim serverSettingsPath As String _
                = IO.Path.Combine( _
                    Application.LocalUserAppDataPath, _
                    "servers.xml")
    Dim settingsPath As String _
        = IO.Path.Combine( _
            Application.LocalUserAppDataPath, _
            "settings.xml")

    Dim myXMLSerializer As Xml.Serialization.XmlSerializer
    Dim settingsFile As IO.StreamWriter

    '保存服务器列表
    myXMLSerializer _
        = New Xml.Serialization.XmlSerializer( _
            GetType(POP3ServerCollection))
    settingsFile _
        = New IO.StreamWriter(serverSettingsPath)
    myXMLSerializer.Serialize(settingsFile, servers)
    settingsFile.Close()

    '保存 Settings 类
    myXMLSerializer _
        = New Xml.Serialization.XmlSerializer( _
            GetType(Settings))
    settingsFile = New IO.StreamWriter(settingsPath)
    myXMLSerializer.Serialize( _
        settingsFile, appSettings)
    settingsFile.Close()
End Sub

Public Sub Reload()
    '与 Persist() 正好相反,此例程
    '从类的 XML 文件中加载两个类,或
    '创建新的实例(如果 XML 文件不存在)
    Dim serverSettingsPath As String _
        = IO.Path.Combine( _
            Application.LocalUserAppDataPath, "servers.xml")
    Dim settingsPath As String _
        = IO.Path.Combine( _
            Application.LocalUserAppDataPath, "settings.xml")

    If IO.File.Exists(serverSettingsPath) Then
        '加载服务器
        Dim settingsFile As IO.StreamReader _
            = New IO.StreamReader(serverSettingsPath)
        Dim myXMLSerializer As _
            New Xml.Serialization.XmlSerializer( _
                GetType(POP3ServerCollection))
        servers = DirectCast( _
            myXMLSerializer.Deserialize(settingsFile), _
            POP3ServerCollection)
        settingsFile.Close()
    Else
        servers = New POP3ServerCollection()
    End If

    If IO.File.Exists(settingsPath) Then
        '加载设置
        Dim settingsFile As IO.StreamReader _
            = New IO.StreamReader(settingsPath)
        Dim myXMLSerializer As _
            New Xml.Serialization.XmlSerializer( _
                GetType(Settings))
        appSettings = DirectCast( _
            myXMLSerializer.Deserialize(settingsFile), _
            Settings)
        settingsFile.Close()
    Else
        appSettings = New Settings()
    End If
End Sub


存储用户 ID/密码

正如您在 Persist/Reload 代码中看到的,我将设置保存到本地应用程序数据路径中。这样可以将信息作为 XML 文件保存到 \Documents and Settings\<userid>\Local Settings\Application Data\POP3\POP3\<version>\ 中。该区域应该受到限制,使得只有您可以访问。但是,这并不能阻止那些具有管理员访问权限的用户,所以我认为最好对信息中的敏感部分再采取一些措施。为了提高安全性,在将用户 ID 和密码保存到磁盘之前,我使用 DPAPI(英文,受这个简单示例 [英文] 的启发)对它们进行了加密。

 

Function EncryptText(ByVal source As String) As String
    '我使用 GotDotNet 中的 DPAPI 组件
    '请查看链接的相关文章。
    Dim dp As _
        New Dpapi.DataProtector(Dpapi.Store.UserStore)
    Dim sourceBytes As Byte() = _
        System.Text.Encoding.Unicode.GetBytes(source)
    Dim encryptedBytes As Byte()
    encryptedBytes = dp.Encrypt(sourceBytes)
    '我可以存储二进制值,但是由于使用
    'XML 作为存储机制,因此我更愿意使用
    '字符串。ToBase64String 处理我的字符串
    Return Convert.ToBase64String(encryptedBytes)
End Function

Function DecryptText(ByVal source As String) As String
    '我使用 GotDotNet 中的 DPAPI 组件
    '请查看链接的相关文章。
    Dim dp As _
        New Dpapi.DataProtector(Dpapi.Store.UserStore)
    Dim sourceBytes As Byte() = _
        Convert.FromBase64String(source)
    Dim decryptedBytes As Byte()
    decryptedBytes = dp.Decrypt(sourceBytes)
    Return System.Text.Encoding.Unicode.GetString(decryptedBytes)
End Function


当然,对这些字符串进行加密之后,我必须做一些额外的工作,才能通过选项窗体查看和/或编辑这些值。

通过选项对话框更改设置

我得承认,在为自己编写代码时,我通常习惯手动更改代码中的值,例如 .config 文件或数据库中的值。这并不是一个好习惯,因为有时候需要将代码提供给其他人,然后必须解释如何更改设置、处理项目符号和创建选项对话框。在这个示例中,我决定事先做好准备,因此我创建了一个小的对话框,在通知图标以外提供了“查看选项”菜单项。

 

Sub ViewOptionsForm( _
    ByVal sender As Object, _
    ByVal e As EventArgs)
    '弹出“选项”对话框
    Try
        '检查对话框是否已启动,
        '如果已启动,将焦点设置到对话框
        If Not myFrm Is Nothing AndAlso myFrm.Visible Then
            myFrm.Focus()
        Else
            '如果尚未启动,则创建一个新的副本
            myFrm = New frmOptions()

            '在窗体中导入数据
            myFrm.servers = servers
            myFrm.appSettings = appSettings

            If myFrm.ShowDialog() = DialogResult.OK Then
                '我在窗体上“克隆”了 appSettings
                '因此在窗体关闭时,
                '您需要弹出一个副本
                appSettings = myFrm.appSettings
                '嗨,您已经更改了所有设置
                '最好是保存它们!
                '这样,即使应用程序以错误的方式结束,
                '您所做的更改仍会被保存。
                Persist()
            End If
            myFrm.Dispose()
            myFrm = Nothing
        End If
    Catch ex As System.Exception
        Debug.WriteLine(ex.ToString)
        Debug.WriteLine(ex.StackTrace)
    End Try
End Sub


为了能够编辑服务器集合,我将数据绑定到一组控件并提供了“新建”和“删除”按钮。这个小的导航控件是我为方便自己使用而编写的,您可能也会用到。如果我不打算使用此控件,本来使用两个按钮来处理 CurrencyManager(英文)的 Position 属性就可以了。

图 3:可以使用“选项”对话框设置 POP3 服务器信息。

由于就像我在前面的“存储用户 ID/密码”一节提到的,已经对用户 ID 和密码进行了加密,因此需要做一些工作以便在数据绑定方案中利用这些值。我为 UserIDPassword 绑定对象的 Parse(英文)和 Format(英文)事件添加了事件处理程序,以使这些值在显示时自动解密,在存储到服务器集合之前自动加密。

 

'设置例程格式并对其进行分析,以解密/加密值
Private Sub encryptedBinding_Format( _
       ByVal sender As Object, _
       ByVal e As System.Windows.Forms.ConvertEventArgs)
    If e.Value <> String.Empty Then
        e.Value = Main.DecryptText(CStr(e.Value))
    End If
End Sub

Private Sub encryptedBinding_Parse( _
       ByVal sender As Object, _
       ByVal e As System.Windows.Forms.ConvertEventArgs)
    If e.Value <> String.Empty Then
        e.Value = Main.EncryptText(CStr(e.Value))
    End If
End Sub


常规的设置并不是数据绑定的,我将它们从 Settings 类中弹出,然后根据需要推入新值。

 

'未对常规设置进行数据绑定
'我只是在控件之间手动
'弹出/推入这些值。
Private Sub PopulateControls()
    If m_appSettings.CheckInterval _
        > Me.checkInterval.Maximum Then
        m_appSettings.CheckInterval _
            = Me.checkInterval.Maximum
    ElseIf m_appSettings.CheckInterval _
        < Me.checkInterval.Minimum Then
        m_appSettings.CheckInterval _
            = Me.checkInterval.Minimum
    End If
    Me.checkInterval.Value = _
        Me.m_appSettings.CheckInterval
    Me.checkOutlook.Checked = _
        Me.m_appSettings.CheckOutlook
    Me.displayPopups.Checked = _
        Me.m_appSettings.DisplayNewMailPopup
End Sub

Private Sub PullControlValues()
    Me.m_appSettings.CheckInterval = _
        Me.checkInterval.Value
    Me.m_appSettings.CheckOutlook = _
        Me.checkOutlook.Checked
    Me.m_appSettings.DisplayNewMailPopup = _
        Me.displayPopups.Checked
End Sub


Outlook

我差点忘记了这个应用程序。我说过我要检查 Outlook 的未读邮件和 POP3 帐户。我不想引用 Outlook 库的原因有两个:

  • 我不希望您使用 Outlook 来编译或运行此代码。
  • 我不希望创建与 Outlook 特定版本相关的依赖关系。

如果不使用对 Outlook 的引用,则必须使用最新绑定,这意味着 Microsoft® IntelliSense® 和其他一切都不能视为对象。但积极的一面是,结果代码可以在 Microsoft® Office 2000、Office XP 和 Office 11 上运行。

 

Public Shared Function GetUnreadMessages() As Integer
    Dim OutlookApp As Object
    Try
        OutlookApp = _
            GetObject(, "Outlook.Application")
    Catch
        OutlookApp = Nothing
    End Try
    '取消注释这段内容,以便在
    '需要时打开 Outlook
    'If OutlookApp Is Nothing Then
    '    OutlookApp = _
    '        CreateObject("Outlook.Application")
    'End If

    If Not OutlookApp Is Nothing Then
        '6 为常数(对于收件箱)
        '由 Outlook 库提供
        '没有引用就没有库
        '也没有常数
        Dim inbox As Object = _
            OutlookApp.Session.GetDefaultFolder(6)
        Return inbox.UnReadItemCount()
    Else
        Return -1
    End If
End Function


请注意,如果 Outlook 未打开,此代码将无法工作。如果我使用 CreateObject(英文),此代码就可以工作,但是我并不想让这个小小的系统任务栏应用程序强制 Outlook 每 n 秒就打开一次。如果该程序未打开,那么它将无法读取 Outlook 收件箱中的未读邮件数。如果无法与 Outlook 相连接,就必须保留未读邮件的值,这一点很重要。将其设置为零将清除上一次的有效数字。

编码问题

在一些 Coding4Fun 专栏文章的结尾,我会提出几个编码问题,如果您对此感兴趣,可以研究研究。在本文中,这个问题是创建与电子邮件有关的任何内容,但不一定必须是 POP3。最好的选择是托管代码(Visual Basic .NET、C#、J# 或托管 C++),但是,具备 COM 接口的非托管组件也是不错的选择。您可以将自己的成果张贴到 GotDotNet(英文),并给我发送电子邮件(地址是 duncanma@microsoft.com),并在其中说明您所做的工作以及为什么这样做。您可以随时给我发送电子邮件,但请您只发送指向代码示例的链接,而不要发送示例本身(我先提前谢谢您)。

资源

在构建本文中的代码时,我使用了 GotDotNet(英文)中的四个示例,我始终关注着用户示例的列表,查找那些实用而且很酷的代码。以下是您可能会感兴趣的示例列表:

本文使用的示例

其他有趣的示例

最后一个示例 SmtpClient 是有关发送邮件的好示例。请注意,System.Web.Mail 在 .NET Framework 中提供了 Smtp 支持,但是它依赖于 CDONTS(尽管该示例只使用了套接字)。以前我从未注意过,但是 boss(英文)如此沉迷于 C#,以至于连该示例名称的结尾都有一个分号!

您对该嗜好有何见解?请将您的见解发送至 duncanma@microsoft.com,并祝编码愉快!

 
 

版权所有:UML软件工程组织