释放对象

上次修改时间: 2010年4月8日

适用范围: SharePoint Foundation 2010

本文内容
关于使用可释放的 SharePoint 对象的介绍
查找错误释放的对象
用于确保释放对象的编码技术
SPSite 对象
SPWeb 对象
其他需要释放的对象
结论

关于使用可释放的 SharePoint 对象的介绍

Microsoft SharePoint Foundation 2010 和 Microsoft SharePoint Server 2010 对象模型中的对象可用作处理 SharePoint Foundation 数据的接口。开发人员经常调入对象模型以从 SharePoint Foundation 2010 和 SharePoint Server 2010 数据存储中读取数据或向其中写入新数据。

SharePoint Foundation 2010 和 SharePoint Server 2010 对象模型包含实现 IDisposable 接口的对象。使用这些对象时必须采取防范措施,防止它们长期保留在 Microsoft .NET Framework 使用的内存中。

具体地说,在使用完这些实现 IDisposable 的 SharePoint 对象后,应该明确释放它们。

在广泛使用 SharePoint 对象的情况下 - 例如,在使用自定义 Web 部件的 SharePoint 网站中 - 如果在使用完 SharePoint 对象之后不释放它们,则可能会导致以下异常行为。

  • SharePoint Foundation 应用程序池频繁进行回收,尤其是在使用高峰期

  • 应用程序崩溃,在调试程序中表现为堆损坏

  • Internet Information Services (IIS) 工作进程使用大量内存

  • 系统和应用程序性能低下

本文可用作指南指导您执行正确的过程来处理和释放实现 IDispose 的 SharePoint 对象。SharePoint 释放检查器工具(该链接可能指向英文页面)也标记了本文中讨论的问题,此工具是一个可免费下载的程序,用于检查程序集中是否存在因不正确处理和释放 SharePoint 对象而导致内存泄露的编码实践。

为何要释放?

一些 SharePoint Foundation 对象(主要是 SPSite 类和 SPWeb 类对象)被创建为托管对象。但是,这些对象使用非托管代码和内存来执行其大多数工作。对象的托管部件比非托管部件小得多。因为较小的托管部件不会给垃圾收集器施加内存压力,所以垃圾收集器不会及时从内存中释放对象。如果对象使用大量非托管内存,则可能导致前面描述的某些异常行为。在 SharePoint Foundation 中处理 IDisposable 对象的调用应用程序在使用完这些对象后必须释放它们。不应该依赖垃圾收集器从内存中自动释放对象。

查找错误释放的对象

通过提出以下问题,可以确定是否可能存在错误释放的对象:

  1. 应用程序池是否频繁进行回收,尤其是在负载过重的情况下(假设应用程序池被设置为在达到内存阈值时进行回收)?

    内存阈值应该在 800 MB 到 1.5 GB 之间,假设 RAM 至少为 2 GB。如果将应用程序池回收设置为在较接近 1 GB 时发生,则效果最佳,但是请根据试验来确定最适合您的环境的设置。如果回收设置太低,则系统可能因应用程序池频繁进行回收而遇到性能问题。如果设置太高,则系统可能会因页面交换、内存碎片和其他问题而遇到性能问题。

  2. 系统性能是否低下,尤其是在负载过重的情况下?

    当内存使用量开始增加时,系统必须进行补偿,例如,通过对内存进行分页和处理内存碎片来进行补偿。

  3. 系统是否崩溃或者用户是否遇到意外错误,如超时或页面不可用错误,尤其是在负载过重的情况下?

    同样,当内存使用量增加或出现内存碎片时,某些功能会发生故障,因为这些功能无法为其他操作分配内存。在许多情况下,代码不会正确处理"内存不足"异常,从而导致虚假错误或误导错误。

  4. 系统是否使用自定义或第三方 Web 部件或自定义应用程序?

    您可能不知道这些 Web 部件必须释放 SharePoint 对象及其原因(假设垃圾收集自动执行此功能)。但并不是所有情况下都是这样。

如果您对第 4 个问题以及其他的一个或多个问题回答"是",则自定义代码很可能未正确释放项目。

如果网站显示前面描述的任意异常行为,则通过检查 ULS 日志(可在 C:\Program Files\Common Files\microsoft shared\Web Server Extensions\14\LOGS 处获得)中是否有与 SPRequest 对象相关的条目,可以确定原因是否是由于错误释放对象而引起的内存泄露。SPSite 和 SPWeb 的每个实例都包含对 SPRequest 对象的引用,而该对象又包含对非托管的 COM 对象的引用,此非托管的 COM 对象处理与数据库服务器之间的通信。SharePoint Foundation 将监视每个特定线程和并行线程中存在的 SPRequest 对象的数目,并在以下三种情况下向日志中添加有用的条目:

  • SPRequest 对象的总数超出了可配置的阈值。

  • SPRequest 对象在线程结束后继续存在。

  • SPRequest 对象已通过垃圾收集从堆中移除

第一种情况最经常发生,尤其是网站使用默认阈值(八个 SPRequest 对象)时。每当 SPRequest 对象的数目超过此阈值时,ULS 日志中就会出现以下条目:

"可能有过量的 SPRequest 对象(对象数)当前在线程 线程号 上未得到释放。请确保正确释放此对象或其父项(如 SPWeb 或 SPSite 对象)。此对象的分配 ID 为:{GUID}"

最佳阈值因网站以及网站上运行的应用程序的性质而异。当网站遇到性能问题时,应该监视安装的 ULS 日志,以了解网站应用程序将创建多少个 SPRequest 对象。这有助于确定网站和应用程序的设计是否会创建太多的 SPRequest 对象。即使错误释放对象 是性能问题的原因,可能也需要重新设计网站或自定义网站应用程序,以减小 SPRequest 对象过量激增所消耗的内存总量。

由于太低的默认阈值可能不适用于许多网站,所以可以通过编辑以下注册表子项来更改此阈值:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\HeapSettings

LocalSPRequestWarnCount = 所需阈值

在确定错误释放对象可能会导致 SPRequest 对象激增并且会徒然增加网站占用的内存之后,可以通过查找以下两个条目来查找错误释放的特定实例。这两条消息均针对由于错误释放 SharePoint 对象而浪费内存的情况,并且都与单一线程上的 SPRequest 对象的数目和状态相关:

  • "在此线程结束之前未释放 SPRequest 对象。为了避免浪费系统资源,请在用完此对象或其父项(如 SPSite 或 SPWeb)之后立即将其释放。现在将释放此对象。分配 ID: {GUID}。要确定在哪里分配了此对象,请在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\HeapSettings 处创建注册表子项。然后在此项下面创建名为 SPRequestStackTrace 且值为 1 的新 DWORD。"

    此消息表明 SPRequest 对象由于在线程结束后仍然存在而被释放。

  • "SPRequest 对象已被垃圾收集器回收,而不是被明确释放。为了避免浪费系统资源,请在用完此对象或其父项(如 SPSite 或 SPWeb)之后立即将其释放。分配 ID: {GUID}。要确定在哪里分配了此对象,请在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\HeapSettings 处创建注册表项。然后在此项下面创建名为 SPRequestStackTrace 且值为 1 的新 DWORD。"

    此消息表明垃圾收集器释放了 SPRequest 对象。

为了找出引起问题的代码,可以在日志中搜索包含分配标识符的条目,或者按照警告中的说明进行操作并向注册表中添加以下子项设置:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\HeapSettings SPRequestStackTrace = 1

此子项设置可确保在出现这些警告时向日志中添加原始 SPRequest 分配(每当创建 SPSite 或 SPWeb 对象时就会发生)的堆栈跟踪。

以下各节所描述的一些编码技术可用于确保正确释放对象。

用于确保释放对象的编码技术

可以采用某些编码技术来确保释放对象。这些技术包括在代码中使用以下内容:

  • Dispose 方法

  • using 语句

  • try、catch 和 finally 块

Dispose 与 Close 方法的用途

SPWeb 对象和 SPSite 对象的 Dispose 和 Close 方法作用相同。Dispose 方法调用对象的 Close 方法。我们建议调用 Dispose 方法,而不是调用 Close 方法,因为 SPWeb 和 SPSite 对象实现 IDisposable 接口,标准 .NET Framework 垃圾收集调用 Dispose 方法从内存中释放与对象关联的任何资源。

using 语句

通过使用 Microsoft Visual C# 和 Visual Basic using 语句,可以自动释放实现 IDisposable 接口的 SharePoint 对象。

以下代码提供了示例。

String str;

using(SPSite oSPsite = new SPSite("https://server"))
{
  using(SPWeb oSPWeb = oSPSite.OpenWeb())
   {
       str = oSPWeb.Title;
       str = oSPWeb.Url;
   }
}  
Dim str As String

Using oSPsite As New SPSite("https://server")
  Using oSPWeb As SPWeb = oSPSite.OpenWeb()
     str = oSPWeb.Title
     str = oSPWeb.Url
  End Using
End Using

利用 using 语句可以大大简化代码。如 C# 参考(using 语句(该链接可能指向英文页面))中所述,公共语言运行库会将 using 语句转换为 try 和 finally 块,并且会为您释放实现 IDisposable 接口的任何对象。但是,在许多情况下,using 语句并不可取,或者必须慎用并要了解运行库所执行的操作。以下代码示例显示了一个不希望运行库为您构建 finally 块和释放对象的情况。在此情况下,SPContext 返回 SPWeb 对象。

// Do not do this. Dispose() is automatically called on SPWeb. 
using( SPWeb web = SPControl.GetContextWeb(HttpContext.Current)) { ... }
' Do not do this. Dispose() is automatically called on SPWeb. 
Using web As SPWeb = SPControl.GetContextWeb(HttpContext.Current)
    '.......
End Using

SPContext 对象由 SharePoint 框架进行管理并且不应该在代码中明确释放。SPContext.Site、SPContext.Current.Site、SPContext.Web 和 SPContext.Current.Web 返回的 SPSite 和 SPWeb 对象也是如此。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_220 的问题。

每当在同一行上合并 SharePoint 对象模型调用时,必须慎重并知道运行库所做的操作。这种情况引发的泄露最难找到。

在以下代码示例中,SPSite 对象会被实例化但不被释放,因为运行库会确保仅释放 OpenWeb 返回的 SPWeb 对象。

void CombiningCallsLeak()
{
    using (SPWeb web = new SPSite(SPContext.Current.Web.Url).OpenWeb())
    {
        // ... New SPSite will be leaked.
    } // SPWeb object web.Dispose() automatically called.
}
Private Sub CombiningCallsLeak()
    Using web As SPWeb = New SPSite(SPContext.Current.Web.Url).OpenWeb()
        ' ... New SPSite will be leaked.
    End Using ' SPWeb object web.Dispose() automatically called.
End Sub

通过将一个 using 语句嵌套在另一个语句中,可以解决此问题。

void CombiningCallsBestPractice()
{
    using (SPSite siteCollection = new SPSite(SPContext.Current.Web.Url))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
        // Perform operations on site.
        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called.
}
Private Sub CombiningCallsBestPractice()
    Using siteCollection As New SPSite(SPContext.Current.Web.Url)
        Using web As SPWeb = siteCollection.OpenWeb()
            ' Perform operations on site.
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

如果不对 SPSite 对象执行任何操作,则可以像在以下代码示例中那样更加简洁地编写此内容。

void CombiningCallsBestPractice()
{
    using (SPSite siteCollection = new SPSite(SPContext.Current.Web.Url))
    using (SPWeb web = siteCollection.OpenWeb())
        {
        // Perform operations on site.
        } // SPWeb object web.Dispose() automatically called; SPSite object 
          // siteCollection.Dispose() automatically called.
}
Private Sub CombiningCallsBestPractice()
    Using siteCollection As New SPSite(SPContext.Current.Web.Url)
        Using web As SPWeb = siteCollection.OpenWeb()
            ' Perform operations on site.
        End Using ' SPWeb object web.Dispose() automatically called; SPSite object
    End Using
    ' siteCollection.Dispose() automatically called.
End Sub

在其他情况下,必须构建自己的 try、catch 和 finally 块。需要处理异常并因此必须包括 catch 块的情况就是最明显的示例。下节提供了关于何时以及如何使用 try、catch 和 finally 块的指导原则。

try、catch 和 finally 块

每当需要处理异常时,使用 try、catch 和 finally 块显然是有意义的。try/catch 块中的任何代码都应该具有 finally 控制子句,这样可以确保释放实现 IDisposable 的对象。请注意,在以下代码示例中,应该在 catch 块中填充处理异常的代码。决不要将 catch 块留空。另外请记下在释放之前测试 null 的最佳实践。

String str;
SPSite oSPSite = null;
SPWeb oSPWeb = null;

try
{
   oSPSite = new SPSite("https://server");
   oSPWeb = oSPSite.OpenWeb(..);

   str = oSPWeb.Title;
}
catch(Exception e)
{
   // Handle exception, log exception, etc.
}
finally
{
   if (oSPWeb != null)
     oSPWeb.Dispose();

   if (oSPSite != null)
      oSPSite.Dispose();
}
Dim str As String
Dim oSPSite As SPSite = Nothing
Dim oSPWeb As SPWeb = Nothing

Try
   oSPSite = New SPSite("https://server")
   oSPWeb = oSPSite.OpenWeb(..)

   str = oSPWeb.Title
Catch e As Exception
' Handle exception, log exception, etc.
Finally
   If oSPWeb IsNot Nothing Then
 oSPWeb.Dispose()
   End If

   If oSPSite IsNot Nothing Then
  oSPSite.Dispose()
   End If
End Try

在 foreach 块内创建可释放的对象时,为了避免潜在泄露,必需使用 Try 和 finally 块或 using 语句,如以下代码示例中所示。

public static void SPSiteCollectionForEachBestPractice()
{
     string sUrl = "http://spvm";
 
      using (SPSite siteCollectionOuter = new SPSite(sUrl))
     {
         SPWebApplication webApp = siteCollectionOuter.WebApplication;
         SPSiteCollection siteCollections = webApp.Sites;

                  SPSite siteCollectionInner = null;
                  foreach (siteCollectionInner in siteCollections)
             {
                      try  // Should be first statement after foreach.
                      {
                          Console.WriteLine(siteCollectionInner.Url);
                          // Exception occurs here.
                      }
                      finally
                      {
                          if(siteCollectionInner != null)
                          siteCollectionInner.Dispose();
                      }
             }
     } // SPSite object siteCollectionOuter.Dispose() automatically called.
 }
Public Shared Sub SPSiteCollectionForEachBestPractice()
    Dim sUrl As String = "http://spvm"

    Using siteCollectionOuter As New SPSite(sUrl)
        Dim webApp As SPWebApplication = siteCollectionOuter.WebApplication
        Dim siteCollections As SPSiteCollection = webApp.Sites

        Dim siteCollectionInner As SPSite = Nothing
        For Each siteCollectionInner In siteCollections
            Try ' Should be first statement after foreach.
                Console.WriteLine(siteCollectionInner.Url)
                ' Exception occurs here.
            Finally
                If siteCollectionInner IsNot Nothing Then
                    siteCollectionInner.Dispose()
                End If
            End Try
        Next
    End Using
End Sub ' SPSite object siteCollectionOuter.Dispose() automatically called.

Response.Redirect 以及 try 块、catch 块、finally 块和 using 语句

在 try 块中调用 Response.Redirect 之后,将会执行 finally 块。Response.Redirect 最终会生成 ThreadAbortException 异常。出现此异常时,运行库会执行所有 finally 块,然后结束线程。但是,因为 finally 块可能会执行无限制的计算或取消 ThreadAbortException,所以该线程未必会结束。因此,必须先释放对象,然后才能重定向或传输处理。如果代码必须重定向,请按照与以下代码示例类似的方法来实现代码。

String str;
SPSite oSPSite = null;
SPWeb oSPWeb = null;

try
{
   oSPSite = new SPSite("https://server");
   oSPWeb = oSPSite.OpenWeb(..);

   str = oSPWeb.Title;
   if(bDoRedirection)
   {
       if (oSPWeb != null)
          oSPWeb.Dispose();
    
       if (oSPSite != null)
          oSPSite.Dispose();

       Response.Redirect("newpage.aspx");
   }
}
catch(Exception e)
{
}
finally
{
   if (oSPWeb != null)
     oSPWeb.Dispose();

   if (oSPSite != null)
      oSPSite.Dispose();
}
Dim str As String
Dim oSPSite As SPSite = Nothing
Dim oSPWeb As SPWeb = Nothing

Try
   oSPSite = New SPSite("https://server")
   oSPWeb = oSPSite.OpenWeb(..)

   str = oSPWeb.Title
   If bDoRedirection Then
         If oSPWeb IsNot Nothing Then
                 oSPWeb.Dispose()
         End If
         If oSPSite IsNot Nothing Then
              oSPSite.Dispose()
         End If

         Response.Redirect("newpage.aspx")
   End If
Catch e As Exception
Finally
   If oSPWeb IsNot Nothing Then
         oSPWeb.Dispose()
   End If

   If oSPSite IsNot Nothing Then
         oSPSite.Dispose()
   End If
End Try

因为 using 语句会命令运行库创建 finally 块,所以每当在 using 语句内使用 Response.Redirect 时,请确保正确释放对象。以下代码示例演示如何执行此操作。

using (SPSite oSPSite = new SPSite("https://server"))
using (SPWeb oSPWeb = oSPSite.OpenWeb(..))
{
    if (bDoRedirection)
        Response.Redirect("newpage.aspx");
}
Using oSPSite As New SPSite("https://server")
    Using oSPWeb As SPWeb = oSPSite.OpenWeb(..)
        If bDoRedirection Then
            Response.Redirect("newpage.aspx")
        End If
    End Using
End Using

缩短长时间对象保留期的建议

通过遵循以下常规建议,可以缩短 SharePoint 对象的长时间保留期。

  • 如果用 new 运算符创建对象,请确保用于创建的应用程序释放该对象。

    备注

    此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_110 的问题。

    良好的编码实践 #1

    明确释放

    void CreatingSPSiteExplicitDisposeNoLeak()
    {
        SPSite siteCollection = null;
        try
        {
            siteCollection = new SPSite("http://moss");
        }
        finally
        {
            if (siteCollection != null)
                siteCollection.Dispose();
        }
    }
    
    Private Sub CreatingSPSiteExplicitDisposeNoLeak()
        Dim siteCollection As SPSite = Nothing
        Try
            siteCollection = New SPSite("http://moss")
        Finally
            If siteCollection IsNot Nothing Then
                siteCollection.Dispose()
            End If
        End Try
    End Sub
    

    良好的编码实践 #2

    自动释放

    CreatingSPSiteWithAutomaticDisposeNoLeak()
    {
        using (SPSite siteCollection = new SPSite("http://moss"))
        {
        } // SPSite object siteCollection.Dispose() is called automatically.
    }
    
    CreatingSPSiteWithAutomaticDisposeNoLeak()
    {
        using (SPSite siteCollection = new SPSite("http://moss"))
        {
        } // SPSite object siteCollection.Dispose() is called automatically.
    }
    
  • 释放由返回其他 SPWeb 对象的 SharePoint 方法(如 OpenWeb())所创建的项。

    备注

    此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_120 的问题。

    良好的编码实践

    void OpenWebNoLeak()
    {
        using (SPSite siteCollection = new SPSite("http://moss"))
        {
            using (SPWeb web = siteCollection.OpenWeb())
            {
            } // SPWeb object web.Dispose() automatically called.
        }  // SPSite object siteCollection.Dispose() automatically called.
    }
    
    Private Sub OpenWebNoLeak()
            Using siteCollection As New SPSite("http://moss")
                    Using web As SPWeb = siteCollection.OpenWeb()
                    End Using ' SPWeb object web.Dispose() automatically called.
            End Using ' SPSite object siteCollection.Dispose() automatically called.
    End Sub
    
  • 不要在线程之间共享任何 SPRequest 对象(以及通过扩展共享任何包含对 SPRequest 对象的引用的对象)。不支持以下性质的任何编码技术:在两个或更多个线程之间共享 SPRequest 对象,或者在一个线程上创建 SPRequest 对象而在另一个线程上释放该对象。这意味着,无法在静态变量中存储任何包含对 SPRequest 对象的引用的对象。因此,不要将实现 IDisposable 的 SharePoint 对象(例如 SPWeb 或 SPSite)存储在静态变量中。

SPSite 对象

本节描述返回并且必须释放新 SPSite 对象的情况。

通常,调用应用程序无论何时使用新 SPSite 构造函数(任何签名),都应该在用完对象之后调用 Dispose() 方法。如果 SPSite 对象是从 GetContextSite() 中获得的,则该调用应用程序 应该释放对象。因为 SPWeb 和 SPSite 对象会保留通过此方式派生的内部列表,所以释放对象可能会导致 SharePoint 对象模型行为无法预料。SharePoint Foundation 在页面完成之后会在内部枚举此列表以正确释放对象。

SPSiteCollection 类

本节描述 SPSiteCollection 对象中的方法、属性或运算符,这些方法、属性或运算符要求在访问返回的 SPSite 对象之后关闭该对象。

SPSiteCollection.Add 方法

SPSiteCollection.Add 方法将创建并返回新的 SPSite 对象。应该释放从 SPSiteCollection.Add 方法中返回的任何 SPSite 对象。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_240 的问题。

不良的编码实践

void SPSiteCollectionAddLeak()
{
    SPWebApplication webApp = new SPSite("http://moss").WebApplication;
    SPSiteCollection siteCollections = webApp.Sites;
    SPSite siteCollection = siteCollections.Add("sites/myNewSiteCollection", "DOMAIN\\User", 
      "roger.lamb@litwareinc.com");
    // SPSite siteCollection leak.
}
Private Sub SPSiteCollectionAddLeak()
        Dim webApp As SPWebApplication = New SPSite("http://moss").WebApplication
        Dim siteCollections As SPSiteCollection = webApp.Sites
        Dim siteCollection As SPSite = siteCollections.Add("sites/myNewSiteCollection", "DOMAIN\User", "roger.lamb@litwareinc.com")
        ' SPSite siteCollection leak.
End Sub

良好的编码实践

void SPSiteCollectionAddNoLeak()
{
    SPWebApplication webApp = new SPSite("http://moss").WebApplication;
    SPSiteCollection siteCollections = webApp.Sites;
    using (SPSite siteCollection = siteCollections.Add("sites/myNewSiteCollection", "DOMAIN\\User", 
      "roger.lamb@litwareinc.com"))
    {
    } // SPSite object siteCollection.Dispose() automatically called.
}
Private Sub SPSiteCollectionAddNoLeak()
      Dim webApp As SPWebApplication = New SPSite("http://moss").WebApplication
      Dim siteCollections As SPSiteCollection = webApp.Sites
      Using siteCollection As SPSite = siteCollections.Add("sites/myNewSiteCollection", "DOMAIN\User", "roger.lamb@litwareinc.com")
      End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

SPSiteCollection [ ] 索引运算符

SPSiteCollection [] 索引运算符会针对每次访问返回一个新的 SPSite 对象。将会创建 SPSite 实例,即使已经访问了该对象也不例外。以下代码示例演示了 SPSite 对象的不正确释放过程。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_230 的问题。

不良的编码实践 #1

使用索引运算符

void SPSiteCollectionIndexerLeak()
{
    using (SPSite siteCollectionOuter = new SPSite("http://moss"))
    {
        SPWebApplication webApp = siteCollectionOuter.WebApplication;
        SPSiteCollection siteCollections = webApp.Sites;

        SPSite siteCollectionInner = siteCollections[0];
        // SPSite siteCollectionInner leak. 
    } // SPSite object siteCollectionOuter.Dispose() automatically called.
}
Private Sub SPSiteCollectionIndexerLeak()
    Using siteCollectionOuter As New SPSite("http://moss")
        Dim webApp As SPWebApplication = siteCollectionOuter.WebApplication
        Dim siteCollections As SPSiteCollection = webApp.Sites

        Dim siteCollectionInner As SPSite = siteCollections(0)
        ' SPSite siteCollectionInner leak. 
    End Using ' SPSite object siteCollectionOuter.Dispose() automatically called.
End Sub

不良的编码实践 #2

使用 foreach 循环

void SPSiteCollectionForEachLeak()
{
    using (SPSite siteCollectionOuter = new SPSite("http://moss"))
    {
        SPWebApplication webApp = siteCollectionOuter.WebApplication;
        SPSiteCollection siteCollections = webApp.Sites;

        foreach (SPSite siteCollectionInner in siteCollections)
        {
            // SPSite siteCollectionInner leak.
        }
    } // SPSite object siteCollectionOuter.Dispose() automatically called.
}
Private Sub SPSiteCollectionForEachLeak()
    Using siteCollectionOuter As New SPSite("http://moss")
        Dim webApp As SPWebApplication = siteCollectionOuter.WebApplication
        Dim siteCollections As SPSiteCollection = webApp.Sites

        For Each siteCollectionInner As SPSite In siteCollections
            ' SPSite siteCollectionInner leak.
        Next siteCollectionInner
    End Using ' SPSite object siteCollectionOuter.Dispose() automatically called.
End Sub

良好的编码实践 #1

使用索引运算符

void SPSiteCollectionIndexerNoLeak()
{
    using (SPSite siteCollectionOuter = new SPSite("http://moss"))
    {
        SPSite siteCollectionInner = null;
        try
        {
            SPWebApplication webApp = siteCollectionOuter.WebApplication;
            SPSiteCollection siteCollections = webApp.Sites;

            siteCollectionInner = siteCollections[0];
        }
        finally
        {
            if (siteCollectionInner != null)
                siteCollectionInner.Dispose();
        }
    } // SPSite object siteCollectionOuter.Dispose() automatically called.
}
Private Sub SPSiteCollectionIndexerNoLeak()
    Using siteCollectionOuter As New SPSite("http://moss")
        Dim siteCollectionInner As SPSite = Nothing
        Try
            Dim webApp As SPWebApplication = siteCollectionOuter.WebApplication
            Dim siteCollections As SPSiteCollection = webApp.Sites

            siteCollectionInner = siteCollections(0)
        Finally
            If siteCollectionInner IsNot Nothing Then
                siteCollectionInner.Dispose()
            End If
        End Try
    End Using ' SPSite object siteCollectionOuter.Dispose() automatically called.
End Sub

良好的编码实践 #2

使用 foreach 循环

void SPSiteCollectionForEachNoLeak()
{
    using (SPSite siteCollectionOuter = new SPSite("http://yoursite"))
    {
        SPWebApplication webApp = siteCollectionOuter.WebApplication;
        SPSiteCollection siteCollections = webApp.Sites;

        foreach (SPSite siteCollectionInner in siteCollections)
        {
            try
            {
                // ...
            }
            finally
            {
                if(siteCollectionInner != null)
                    siteCollectionInner.Dispose();
            }
        }
    } // SPSite object siteCollectionOuter.Dispose() automatically called.
}
Private Sub SPSiteCollectionForEachNoLeak()

    Using siteCollectionOuter As SPSite = New SPSite("http://yoursite")
        Dim webApp As SPWebApplication = siteCollectionOuter.WebApplication
        Dim siteCollections As SPSiteCollection = webApp.Sites
        For Each siteCollectionInner As SPSite In siteCollections
            Try
                ' ...
            Finally
                If siteCollectionInner IsNot Nothing Then
                    siteCollectionInner.Dispose()
                End If
            End Try
        Next
    End Using
End Sub

SPSite.AllWebs 属性 (SPWebCollection)

本节描述 AllWebs 属性集合中的方法、属性或运算符,这些方法、属性或运算符要求在访问 SPWeb 对象之后关闭该对象。

SPSite.AllWebs.Add 方法

SPSite.AllWebs.Add 方法将创建并返回 SPWeb 对象。应该释放从 SPSite.AllWebs.Add 中返回的任何 SPWeb 对象。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_150 的问题。

不良的编码实践

void AllWebsAddLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        SPWeb web = siteCollection.AllWebs.Add("site-relative URL");
        // SPWeb object leaked.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub AllWebsAddLeak()
    Using siteCollection As New SPSite("http://moss")
        Dim web As SPWeb = siteCollection.AllWebs.Add("site-relative URL")
        ' SPWeb object leaked.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

良好的编码实践

void AllWebsAddNoLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.AllWebs.Add("site-relative URL"))
        {
        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub AllWebsAddNoLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.AllWebs.Add("site-relative URL")
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

SPWebCollection.Add 方法

SPWebCollection.Add 方法将创建并返回需要释放的 SPWeb 对象。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_200 的问题。

不良的编码实践

void SPWebCollectionAddLeak(string strWebUrl)
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            SPWebCollection webCollection = siteCollection.AllWebs; // No AllWebs leak just getting reference.
            SPWeb innerWeb = webCollection.Add(strWebUrl);  // Must dispose innerWeb.
            // innerWeb leak.
        } // SPWeb object outerWeb.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub SPWebCollectionAddLeak(ByVal strWebUrl As String)
    Using siteCollection As New SPSite("http://moss")
        Using outerWeb As SPWeb = siteCollection.OpenWeb()
            Dim webCollection As SPWebCollection = siteCollection.AllWebs ' No AllWebs leak just getting reference.
            Dim innerWeb As SPWeb = webCollection.Add(strWebUrl) ' Must dispose innerWeb.
            ' innerWeb leak.
        End Using ' SPWeb object outerWeb.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

良好的编码实践

void SPWebCollectionAddNoLeak(string strWebUrl)
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            SPWebCollection webCollection = siteCollection.AllWebs; // No AllWebs leak just getting reference.
            using (SPWeb innerWeb = webCollection.Add(strWebUrl))
            {
                //...
            }
        } // SPWeb object outerWeb.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub SPWebCollectionAddNoLeak(ByVal strWebUrl As String)
    Using siteCollection As New SPSite("http://moss")
        Using outerWeb As SPWeb = siteCollection.OpenWeb()
            Dim webCollection As SPWebCollection = siteCollection.AllWebs ' No AllWebs leak just getting reference.
            Using innerWeb As SPWeb = webCollection.Add(strWebUrl)
                '...
            End Using
        End Using ' SPWeb object outerWeb.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

SPSite.AllWebs [ ] 索引运算符

SPSite.AllWebs [] 索引运算符会在每次访问对象后返回一个新的 SPWeb 实例。索引操作过程中将会创建对象,即使已经访问了该对象也不例外。如果未正确关闭,则以下代码示例会将 SPWeb 对象保留在 .NET Framework 垃圾收集器中。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_130 的问题。

不良的编码实践

void AllWebsForEachLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            foreach (SPWeb innerWeb in siteCollection.AllWebs)
            {
                // Explicitly dispose here to avoid out of memory leaks with large number of SPWeb objects.
            }
        } // SPWeb object outerWeb.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub AllWebsForEachLeak()
    Using siteCollection As New SPSite("http://moss")
        Using outerWeb As SPWeb = siteCollection.OpenWeb()
            For Each innerWeb As SPWeb In siteCollection.AllWebs
                ' Explicitly dispose here to avoid out of memory leaks with large number of SPWeb objects.
            Next
        End Using ' SPWeb object outerWeb.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

良好的编码实践 #1

使用 foreach 循环

void AllWebsForEachNoLeakOrMemoryOOM()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            foreach (SPWeb innerWeb in siteCollection.AllWebs)
            {
                try
                {
                    // ...
                }
                finally
                {
                    if(innerWeb != null)
                        innerWeb.Dispose();
                }
            }
        } // SPWeb object outerWeb.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub AllWebsForEachNoLeakOrMemoryOOM()
    Using siteCollection As New SPSite("http://moss")
        Using outerWeb As SPWeb = siteCollection.OpenWeb()
            For Each innerWeb As SPWeb In siteCollection.AllWebs
                Try
                    ' ...
                Finally
                    If innerWeb IsNot Nothing Then
                        innerWeb.Dispose()
                    End If
                End Try
            Next
        End Using ' SPWeb object outerWeb.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

良好的编码实践 #2

使用索引运算符

void AllWebsIndexerNoLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.AllWebs[0])
        {
        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub AllWebsIndexerNoLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.AllWebs(0)
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

SPSite.OpenWeb 和 SPSite. SelfServiceCreateSite 方法

SPSite 对象的 OpenWeb() 方法和 SelfServiceCreateSite 方法(所有签名)将创建 SPWeb 对象并将其返回给调用方。此新对象未存储在 SPSite 对象中,并且未在 SPSite 类中的任何地方被释放。因此,应该释放通过这些方法创建的任何对象。

不良的编码实践

void OpenWebLeak()
{
    using (SPWeb web = new SPSite(SPContext.Current.Web.Url).OpenWeb())
    {
        // SPSite leaked !
    } // SPWeb object web.Dispose() automatically called.
}
Private Sub OpenWebLeak()
    Using web As SPWeb = New SPSite(SPContext.Current.Web.Url).OpenWeb()
        ' SPSite leaked !
    End Using ' SPWeb object web.Dispose() automatically called.
End Sub

良好的编码实践

void OpenWebNoLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called.
}
Private Sub OpenWebNoLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

SPSite.RootWeb 属性

前面的指导指明了调用应用程序释放 SPSite.RootWeb 属性应该发生在即将释放使用该属性的 SPSite 对象之前。这不再是正式的指导。释放清理是由 SharePoint Foundation 和 SharePoint Server 自动处理的。此外,SPSite 属性 LockIssue、Owner 和 SecondaryContact 在内部使用了 RootWeb 属性。按照 RootWeb 的更新的指导,每当使用其中的任意属性时对 SPSite.RootWeb 属性调用 Dispose 方法不再是可取的。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_140 的问题。

良好的编码实践

public void RootWebBestPractice()
{
    // New SPSite.
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        SPWeb rootWeb1 = siteCollection.RootWeb;
        // No explicit rootWeb1 dispose required.
    }  // siteCollection automatically disposed by implementing using().
    // rootWeb1 will be Disposed by SPSite.

    // SPContext and SPControl
    SPWeb rootWeb2 = SPContext.Current.Site.RootWeb;
    // Also would apply to SPControl.GetContextSite(Context);
    // No explicit rootWeb2 dispose required because it is obtained from SPContext.Current.Site.
}
Public Sub RootWebBestPractice()
    ' New SPSite.
    Using siteCollection As New SPSite("http://moss")
        Dim rootWeb1 As SPWeb = siteCollection.RootWeb
        ' No explicit rootWeb1 dispose required.
    End Using ' siteCollection automatically disposed by implementing using().
    ' rootWeb1 will be Disposed by SPSite.

    ' SPContext and SPControl
    Dim rootWeb2 As SPWeb = SPContext.Current.Site.RootWeb
    ' Also would apply to SPControl.GetContextSite(Context);
    ' No explicit rootWeb2 dispose required because it is obtained from SPContext.Current.Site.
End Sub

Microsoft.Office.Server.UserProfiles.PersonalSite(仅 Office SharePoint Server 2007)

Microsoft.Office.Server.UserProfiles.PersonalSite 返回必须释放的 SPSite 对象。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_400 的问题。

不良的编码实践

void PersonalSiteLeak()
{
    // Open a site collection.
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        UserProfileManager profileManager = new UserProfileManager(ServerContext.GetContext(siteCollection));
        UserProfile profile = profileManager.GetUserProfile("domain\\username");
        SPSite personalSite = profile.PersonalSite;    // Will leak.
    }
}
Private Sub PersonalSiteLeak()
    ' Open a site collection.
    Using siteCollection As New SPSite("http://moss")
        Dim profileManager As New UserProfileManager(ServerContext.GetContext(siteCollection))
        Dim profile As UserProfile = profileManager.GetUserProfile("domain\username")
        Dim personalSite As SPSite = profile.PersonalSite ' Will leak.
    End Using
End Sub

良好的编码实践

void PersonalSiteNoLeak()
{
    // Open a site collection.
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        UserProfileManager profileManager = new UserProfileManager(ServerContext.GetContext(siteCollection));
        UserProfile profile = profileManager.GetUserProfile("domain\\username");
        using (SPSite personalSite = profile.PersonalSite)
        {
            // ...
        }
    }
}
Private Sub PersonalSiteNoLeak()
    ' Open a site collection.
    Using siteCollection As New SPSite("http://moss")
        Dim profileManager As New UserProfileManager(ServerContext.GetContext(siteCollection))
        Dim profile As UserProfile = profileManager.GetUserProfile("domain\username")
        Using personalSite As SPSite = profile.PersonalSite
            ' ...
        End Using
    End Using
End Sub

在另一种边界情况下,UserProfiles.PersonalSite 会泄露,如以下代码示例中所示。

void PersonalSiteLeak()
{
    // Open a site collection.
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        UserProfileManager profileManager = new UserProfileManager(ServerContext.GetContext(siteCollection));
        UserProfile profile = profileManager.GetUserProfile("domain\\username");
        SPSite personalSite = profile.PersonalSite;    // Will leak.
    }
}
Private Sub PersonalSiteLeak()
    ' Open a site collection.
    Using siteCollection As New SPSite("http://moss")
        Dim profileManager As New UserProfileManager(ServerContext.GetContext(siteCollection))
        Dim profile As UserProfile = profileManager.GetUserProfile("domain\username")
        Dim personalSite As SPSite = profile.PersonalSite ' Will leak.
    End Using
End Sub

可以按照以下代码示例中显示的模式解决此类泄露。

void PersonalSiteNoLeak()
{
    // Open a site collection
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        UserProfileManager profileManager = new UserProfileManager(ServerContext.GetContext(siteCollection));
        UserProfile profile = profileManager.GetUserProfile("domain\\username");
        using (SPSite personalSite = profile.PersonalSite)
        {
            // ...
        }
    }
}
Private Sub PersonalSiteNoLeak()
    ' Open a site collection
    Using siteCollection As New SPSite("http://moss")
        Dim profileManager As New UserProfileManager(ServerContext.GetContext(siteCollection))
        Dim profile As UserProfile = profileManager.GetUserProfile("domain\username")
        Using personalSite As SPSite = profile.PersonalSite
            ' ...
        End Using
    End Using
End Sub

另外请注意,通过从 ProfileLoader 中检索 PersonalSite 对象,可以提高性能(以及避免创建 SPSite 对象),如以下代码示例中所示。

UserProfile myProfile = ProfileLoader.GetProfileLoader().GetUserProfile();
using (SPSite personalSite = myProfile.PersonalSite)
{
     // ...
}
Dim myProfile As UserProfile = ProfileLoader.GetProfileLoader().GetUserProfile()
Using personalSite As SPSite = myProfile.PersonalSite
       ' ...
End Using

此外,如果要为"我的网站"创建 Web 部件,则可以使用不需要释放的 PersonalSite 的实例。

IPersonalPage currentMySitePage = this.Page as IPersonalPage;
if (currentMySitePage != null && !currentMySitePage.IsProfileError)
{
     SPSite personalSite = currentMySitePage.PersonalSite; // Will not leak.
     // ...
}
Dim currentMySitePage As IPersonalPage = TryCast(Me.Page, IPersonalPage)
If currentMySitePage IsNot Nothing AndAlso (Not currentMySitePage.IsProfileError) Then
         Dim personalSite As SPSite = currentMySitePage.PersonalSite ' Will not leak.
         ' ...
End If

SPWeb 对象

本节描述返回并且可能需要释放 SPWeb 对象的情况。

SPWeb.ParentWeb 属性

更新的指导

前面的指导建议调用应用程序应该释放 SPWeb.ParentWeb。这不再是正式的指导。释放清理是由 SharePoint Foundation 和 SharePoint Server 自动处理的。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_170 的问题。

良好的编码实践

using (SPSite site = new SPSite("https://localhost")) 
{
    using (SPWeb web = site.OpenWeb())
    {
        SPList list = web.Lists["Announcements"];
        SPWeb parentWeb = list.ParentWeb; //No explicit dispose required.
    }
}
Using site As New SPSite("https://localhost")
      Using web As SPWeb = site.OpenWeb()
            Dim list As SPList = web.Lists("Announcements")
            Dim parentWeb As SPWeb = list.ParentWeb 'No explicit dispose required.
      End Using
End Using

SPWeb.Webs 属性

本节描述 Webs 属性集合中的方法、属性或运算符,这些方法、属性或运算符要求在访问 SPWeb 对象之后释放该对象。

SPWeb.Webs

SPWeb.Webs 属性将返回 SPWebCollection 对象。必须释放此集合中的 SPWeb 对象。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_180 的问题。

不良的编码实践

void WebsLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            foreach (SPWeb innerWeb in outerWeb.Webs)
            {
                // SPWeb innerWeb leak.
            }
        } // SPWeb object outerWeb.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub WebsLeak()
    Using siteCollection As New SPSite("http://moss")
        Using outerWeb As SPWeb = siteCollection.OpenWeb()
            For Each innerWeb As SPWeb In outerWeb.Webs
                ' SPWeb innerWeb leak.
            Next
        End Using ' SPWeb object outerWeb.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

良好的编码实践

void WebsNoLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            foreach (SPWeb innerWeb in outerWeb.Webs)
            {
                try // Should be first statement after foreach.
                {
                    // ...
                }
                finally
                {
                    if(innerWeb != null)
                        innerWeb.Dispose();
                }
            }
        } // SPWeb object outerWeb.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub WebsNoLeak()
    Using siteCollection As New SPSite("http://moss")
        Using outerWeb As SPWeb = siteCollection.OpenWeb()
            For Each innerWeb As SPWeb In outerWeb.Webs
                Try ' Should be first statement after foreach.
                    ' ...
                Finally
                    If innerWeb IsNot Nothing Then
                        innerWeb.Dispose()
                    End If
                End Try
            Next
        End Using ' SPWeb object outerWeb.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

SPWeb.Webs.Add

SPWeb.Webs.Add 方法(或 SPWebCollection.Add)会创建并返回新的 SPWeb 对象。应该释放从此方法调用中返回的任何 SPWeb 对象。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_190 的问题。

不良的编码实践

void WebsAddLeak(string strWebUrl)
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            SPWeb addedWeb = web.Webs.Add(strWebUrl);   // Will leak.

        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called.
}
Private Sub WebsAddLeak(ByVal strWebUrl As String)
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            Dim addedWeb As SPWeb = web.Webs.Add(strWebUrl) ' Will leak.

        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

良好的编码实践

void WebsAddNoLeak(string strWebUrl)
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            using (SPWeb addedWeb = web.Webs.Add(strWebUrl))
            {
                //..
            }

        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called.
}
Private Sub WebsAddNoLeak(ByVal strWebUrl As String)
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            Using addedWeb As SPWeb = web.Webs.Add(strWebUrl)
                '..
            End Using

        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

SPWeb.Webs[] 索引运算符

SPWeb.Webs[] 索引运算符会针对每次访问返回一个新的 SPWeb 对象。将会通过调用 OpenWeb 方法来创建 SPWeb,即使已经访问了该对象也不例外。以下代码示例会导致在 .NET Framework 使用的内存中长期保留这些对象。

不良的编码实践 #1

使用 For 循环

int i;

SPWeb oSPWeb, oSPWeb2;
SPSite oSPSite = SPControl.GetContextSite(Context);

oSPWeb = oSPSite.OpenWeb();

for(i = 0;i < oSPWeb.Webs.Count;i++)
{
   oSPWeb2 = oSPWeb.Webs[i];
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title);
}
Dim i As Integer

Dim oSPWeb, oSPWeb2 As SPWeb
Dim oSPSite As SPSite = SPControl.GetContextSite(Context)

oSPWeb = oSPSite.OpenWeb()

For i = 0 To oSPWeb.Webs.Count - 1
   oSPWeb2 = oSPWeb.Webs(i)
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title)
Next i

不良的编码实践 #2

使用 foreach 循环

SPWeb oSPWeb, oSPWeb2;
SPSite oSPSite = SPControl.GetContextSite(Context);

oSPWeb = oSPSite.OpenWeb();

foreach(SPWeb oSPWeb2 in oSPWebe.Webs)
{
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title);
}
Dim oSPWeb, oSPWeb2 As SPWeb
Dim oSPSite As SPSite = SPControl.GetContextSite(Context)

oSPWeb = oSPSite.OpenWeb()

For Each oSPWeb2 As SPWeb In oSPWebe.Webs
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title)
Next

建议的修复方法是在每次循环结束后进行释放。

良好的编码实践 #1

使用 For 循环

int i;

SPWeb oSPWeb, oSPWeb2;
SPSite oSPSite = SPControl.GetContextSite(Context);

oSPWeb = oSPSite.OpenWeb();

for(i = 0;i < oSPWeb.Webs.Count;i++)
{
   oSPWeb2 = oSPWeb.Webs[i];
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title);
   oSPWeb2.Dispose();
}

oSPWeb.Dispose();
Dim i As Integer

Dim oSPWeb, oSPWeb2 As SPWeb
Dim oSPSite As SPSite = SPControl.GetContextSite(Context)

oSPWeb = oSPSite.OpenWeb()

For i = 0 To oSPWeb.Webs.Count - 1
   oSPWeb2 = oSPWeb.Webs(i)
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title)
   oSPWeb2.Dispose()
Next i

oSPWeb.Dispose()

良好的编码实践 #2

使用 foreach 循环

SPWeb oSPWeb, oSPWeb2;
SPSite oSPSite = SPControl.GetContextSite(Context);

oSPWeb = oSPSite.OpenWeb();

foreach(SPWeb oSPWeb2 in oSPWeb.Webs)
{
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title);
   oSPWeb2.Dispose();
}

oSPWeb.Dispose();
Dim oSPWeb, oSPWeb2 As SPWeb
Dim oSPSite As SPSite = SPControl.GetContextSite(Context)

oSPWeb = oSPSite.OpenWeb()

For Each oSPWeb2 As SPWeb In oSPWeb.Webs
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title)
   oSPWeb2.Dispose()
Next

oSPWeb.Dispose()

良好的编码实践 #3

将 for 循环与自动释放结合使用

int i;

SPWeb oSPWeb, oSPWeb2;
SPSite oSPSite = SPControl.GetContextSite(Context);

using(oSPWeb = oSPSite.OpenWeb())
{
   for(i = 0;i < oSPWeb.Webs.Count;i++)
   {
      Using(oSPWeb2 = oSPWeb.Webs[i])
      {
         BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title);
      }
   }
}
Dim i As Integer
Dim oSPSite As SPSite = SPControl.GetContextSite(Context)

Using oSPWeb As SPWeb = oSPSite.OpenWeb()
    For i = 0 To oSPWeb.Webs.Count - 1
        Using oSPWeb2 As SPWeb = oSPWeb.Webs(i)
                BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title)
        End Using
    Next
End Using

其他需要释放的对象

本节描述何时对其他 SharePoint 对象调用 Dispose 方法。

Microsoft.SharePoint.Portal.SiteData.Area.Web 属性

SharePoint.Portal.SiteData.Area 类的 Web 属性在每次访问对象后都会返回一个新的 SPWeb 对象。每次使用 Area.Web 属性时,都应该相应地调用 Dispose 方法。虽然 Area 和 AreaManager 类现在已过时,但在迁移旧代码时这仍令人担忧。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_500 的问题。

不良的编码实践

void AreaWebLeak()
{
    // AreaManager and Area are obsolete in SharePoint Server, but this
    // should still be noted.
    Area area = AreaManager.GetArea(PortalContext.Current, new Guid("{GUID}"));
    string str = area.Web.Title;
    // SPWeb area.Web leak.
}
Private Sub AreaWebLeak()
    ' AreaManager and Area are obsolete in SharePoint Server, but this
    ' should still be noted.
    Dim area As Area = AreaManager.GetArea(PortalContext.Current, New Guid("{GUID}"))
    Dim str As String = area.Web.Title
    ' SPWeb area.Web leak.
End Sub

良好的编码实践

public void AreaWebNoLeak()
{
    // AreaManager and Area are obsolete but this should still be noted.
    Area area = AreaManager.GetArea(PortalContext.Current, new Guid("{GUID}"));
    using (SPWeb areaWeb = area.Web)
    {
        string str = areaWeb.Title;
    }
}
Public Sub AreaWebNoLeak()
    ' AreaManager and Area are obsolete but this should still be noted.
    Dim area As Area = AreaManager.GetArea(PortalContext.Current, New Guid("{GUID}"))
    Using areaWeb As SPWeb = area.Web
        Dim str As String = areaWeb.Title
    End Using
End Sub

SPControl.GetContextSite and SPControl.GetContextWeb 方法

如果对象是从 SharePoint 上下文对象(SPControl 类中的 GetContextSite 和 GetContextWeb 方法)中获得的,则调用应用程序 应该对此对象调用 Dispose 方法。如果调用的话,可能会导致 SharePoint 对象模型行为不可预料或失败。这是由以此方式派生并保留在 SPSite 和 SPWeb 对象中的内部列表造成的。对象模型在页面完成之后会在内部枚举此列表以正确释放对象。

例如,如果网站是从使用 GetContextSite 方法获得的 SPSite 对象中打开的,则仍应该释放从这些对象中创建的对象。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_210 的问题。

不良的编码实践

void SPControlBADPractice()
{
    SPSite siteCollection = SPControl.GetContextSite(Context);
    siteCollection.Dispose();   // DO NOT DO THIS.
    SPWeb web = SPControl.GetContextWeb(Context);
    web.Dispose();  // DO NOT DO THIS.
}
Private Sub SPControlBADPractice()
    Dim siteCollection As SPSite = SPControl.GetContextSite(Context)
    siteCollection.Dispose() ' DO NOT DO THIS.
    Dim web As SPWeb = SPControl.GetContextWeb(Context)
    web.Dispose() ' DO NOT DO THIS.
End Sub

良好的编码实践

void SPControlBestPractice()
{
    SPSite siteCollection = SPControl.GetContextSite(Context);
    SPWeb web = SPControl.GetContextWeb(Context);
    // Do NOT call Dispose().
}
Private Sub SPControlBestPractice()
      Dim siteCollection As SPSite = SPControl.GetContextSite(Context)
      Dim web As SPWeb = SPControl.GetContextWeb(Context)
      ' Do NOT call Dispose().
End Sub

Microsoft.SharePoint.WebPartPages.SPLimitedWebPartManager

SPLimitedWebPartManager 类包含对必须释放的内部 SPWeb 对象的引用。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_160 的问题。

不良的编码实践

void SPLimitedWebPartManagerLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            SPFile page = web.GetFile("Source_Folder_Name/Source_Page");
            SPLimitedWebPartManager webPartManager =
                page.GetLimitedWebPartManager(PersonalizationScope.Shared);
            // SPWeb object webPartManager.Web leaked.
        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub SPLimitedWebPartManagerLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            Dim page As SPFile = web.GetFile("Source_Folder_Name/Source_Page")
            Dim webPartManager As SPLimitedWebPartManager = page.GetLimitedWebPartManager(PersonalizationScope.Shared)
            ' SPWeb object webPartManager.Web leaked.
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

良好的编码实践

void SPLimitedWebPartManagerLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            SPFile page = web.GetFile("Source_Folder_Name/Source_Page");
            SPLimitedWebPartManager webPartManager =
                page.GetLimitedWebPartManager(PersonalizationScope.Shared);
                webPartManaber.Web.Dispose();
        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub SPLimitedWebPartManagerLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            Dim page As SPFile = web.GetFile("Source_Folder_Name/Source_Page")
            Dim webPartManager As SPLimitedWebPartManager = page.GetLimitedWebPartManager(PersonalizationScope.Shared)
            webPartManaber.Web.Dispose()
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Microsoft.SharePoint.Publishing.PublishingWeb

备注

Microsoft.SharePoint.Publishing 命名空间属于 SharePoint Server 2010。本节适用于 SharePoint Server 2010,但不适用于 SharePoint Foundation 2010。

PublishingWeb 类的 GetPublishingWebs 方法将返回 PublishingWebCollection 对象。必须对每个枚举的 innerPubWeb 对象调用 Close 方法。如果调用的只是 GetPublishingWeb 方法,则不需要调用 Close。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_300 的问题。

不良的编码实践

void PublishingWebCollectionLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            // Passing in SPWeb object that you own, no dispose needed on
            // outerPubWeb.
            PublishingWeb outerPubWeb = PublishingWeb.GetPublishingWeb(web);

            PublishingWebCollection pubWebCollection = outerPubWeb.GetPublishingWebs();
            foreach (PublishingWeb innerPubWeb in pubWebCollection)
            {
                // innerPubWeb leak.
            }
            // PublishingWeb will leak for each innerPubWeb referenced
        } // SPWeb object web.Dispose() automatically called.
    } // SPSite object siteCollection.Dispose() automatically called.
}
Private Sub PublishingWebCollectionLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            ' Passing in SPWeb object that you own, no dispose needed on
            ' outerPubWeb.
            Dim outerPubWeb As PublishingWeb = PublishingWeb.GetPublishingWeb(web)

            Dim pubWebCollection As PublishingWebCollection = outerPubWeb.GetPublishingWebs()
            For Each innerPubWeb As PublishingWeb In pubWebCollection
                ' innerPubWeb leak.
            Next
            ' PublishingWeb will leak for each innerPubWeb referenced
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

良好的编码实践

void PublishingWebCollectionNoLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            // Passing in SPWeb object that you own, no dispose needed on
            // outerPubWeb.
            PublishingWeb outerPubWeb = PublishingWeb.GetPublishingWeb(web);
            PublishingWebCollection pubWebCollection = outerPubWeb.GetPublishingWebs();
            foreach (PublishingWeb innerPubWeb in pubWebCollection)
            {
                try
                {
                    // ...
                }
                finally
                {
                    if(innerPubWeb != null)
                        innerPubWeb.Close();
                }
            }
        }  // SPWeb object web.Dispose() automatically called.
    } // SPSite object siteCollection.Dispose() automatically called.
}
Private Sub PublishingWebCollectionNoLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            ' Passing in SPWeb object that you own, no dispose needed on
            ' outerPubWeb.
            Dim outerPubWeb As PublishingWeb = PublishingWeb.GetPublishingWeb(web)
            Dim pubWebCollection As PublishingWebCollection = outerPubWeb.GetPublishingWebs()
            For Each innerPubWeb As PublishingWeb In pubWebCollection
                Try
                    ' ...
                Finally
                    If innerPubWeb IsNot Nothing Then
                        innerPubWeb.Close()
                    End If
                End Try
            Next
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

备注

同样,需要对每个 PublishingWeb 对象(该对象是通过对 Microsoft.SharePoint.Publishing.PublishingWeb.GetPublishingWebs 返回的 PublishingWebCollection 调用 Add 方法创建的)调用 Close。有关代码示例,请参阅 GetPublishingWebs() 方法。此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_310 的问题。

Microsoft.SharePoint.Publishing.PublishingWeb.GetVariation 方法返回必须释放的 PublishingWeb 对象。

备注

此最佳实践可解决被 SharePoint 释放检查器工具(该链接可能指向英文页面)标识为 SPDisposeCheckID_320 的问题。

不良的编码实践

void GetVariationLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web);  // Passing in SPWeb object, so no Close() needed
            VariationLabel variationLabel = Variations.Current.UserAccessibleLabels[0];
            PublishingWeb variationPublishingWeb = publishingWeb.GetVariation(variationLabel);  // Must be Closed().
            // ...
        } // SPWeb object outerWeb.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub GetVariationLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            Dim publishingWeb As PublishingWeb = PublishingWeb.GetPublishingWeb(web) ' Passing in SPWeb object, so no Close() needed
            Dim variationLabel As VariationLabel = Variations.Current.UserAccessibleLabels(0)
            Dim variationPublishingWeb As PublishingWeb = publishingWeb.GetVariation(variationLabel) ' Must be Closed().
            ' ...
        End Using ' SPWeb object outerWeb.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

良好的编码实践

void GetVariationNoLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            PublishingWeb variationPublishingWeb = null;
            try
            {
                PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web);  // Passing in SPWeb object, so no Close() needed.
                VariationLabel variationLabel = Variations.Current.UserAccessibleLabels[0];
                variationPublishingWeb = publishingWeb.GetVariation(variationLabel);  // Must be Closed().
                // ...
            }
            finally
            {
                if(variationPublishingWeb != null)
                    variationPublishingWeb.Close();
            }
        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub GetVariationNoLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            Dim variationPublishingWeb As PublishingWeb = Nothing
            Try
                Dim publishingWeb As PublishingWeb = PublishingWeb.GetPublishingWeb(web) ' Passing in SPWeb object, so no Close() needed.
                Dim variationLabel As VariationLabel = Variations.Current.UserAccessibleLabels(0)
                variationPublishingWeb = publishingWeb.GetVariation(variationLabel) ' Must be Closed().
                ' ...
            Finally
                If variationPublishingWeb IsNot Nothing Then
                    variationPublishingWeb.Close()
                End If
            End Try
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

交叉法释放模式

以下示例演示了在类中跨方法保持 SPSite 和 SPWeb 对象的常见做法。有时此设计模式是必需的,但是请确保不要忽视了在完成交叉法调用后调用 Dispose 的适当时间。以下代码示例显示了一种模式,在该模式中,当类超出范围时 SPSite 和 SPWeb 将会泄露。

public class CrossMethodLeak
{
    private SPSite _siteCollection = null;
    private SPWeb _web = null;

    public void MethodA()
    {
        _siteCollection = new SPSite("http://moss");
        _web = _siteCollection.OpenWeb();
    }

    public void MethodB()
    {
        if (_web != null)
        {
            string title = _web.Title;
        }
    }

    public void MethodC()
    {
        if (_web != null)
        {
            string name = _web.Name;
        }
    }
}
Public Class CrossMethodLeak
    Private _siteCollection As SPSite = Nothing
    Private _web As SPWeb = Nothing

    Public Sub MethodA()
        _siteCollection = New SPSite("http://moss")
        _web = _siteCollection.OpenWeb()
    End Sub

    Public Sub MethodB()
        If _web IsNot Nothing Then
            Dim title As String = _web.Title
        End If
    End Sub

    Public Sub MethodC()
        If _web IsNot Nothing Then
            Dim name As String = _web.Name
        End If
    End Sub
End Class

结论

由于许多 SharePoint 对象实现 IDisposable 接口,所以在使用这些对象时必须谨慎,以避免在内存中保留它们。通过遵循释放 SharePoint 对象的指导原则,如本文中所述,可有助于确保自定义代码的可靠性。

鸣谢

感谢以下人员在本文撰写过程中提供的信息和指导:

  • Steve Sheppard,Microsoft Corporation

  • Chris Gideon,Microsoft Corporation

  • Rashid Aga,Microsoft Corporation

请参阅

其他资源

SharePoint 释放检查器工具(该链接可能指向英文页面)

模式和做法 SharePoint 指导(该链接可能指向英文页面)

Windows SharePoint Services 开发人员中心

SharePoint Server 开发人员中心

SharePoint 产品和技术自定义最佳方案

SharePoint Server 2007 最佳实践资源中心(该链接可能指向英文页面)

Roger Lamb 的 SharePoint 开发人员博客(该链接可能指向英文页面)

最佳实践:使用 SharePoint 对象模型时的常见代码问题