from:http://blogs.msdn.com/rogerla/archive/2008/02/12/sharepoint-2007-and-wss-3-0-dispose-patterns-by-example.aspx
Overview
Windows SharePoint Services (WSS 3.0) and Microsoft Office SharePoint Server (MOSS 2007) have some gotchas with serious implications that every SharePoint application developer needs to be intimately familiar with before deploying into production MOSS 2007 farms. In particular, Microsoft.SharePoint.SPSite , Microsoft.SharePoint.SPWeb , and the often overlooked (MOSS 2007) Microsoft.SharePoint.Publishing.PublishingWeb objects need to be carefully examined and disposed of properly in order to avoid potential memory leaks.
My objective with this blog post was to consolidate several sources compiled by Microsoft's top subject matter experts into a simplified quick reference format which can be used to increase awareness as well as jump start into a SharePoint custom code review. Look for a follow-up post where I will do a high level walk-through using the windows debugger (windbg) to help identify common SharePoint memory leaks.
Note: This Blog will be updated periodically as new dispose leak guidance are released.
Why Dispose?
SPSite and SPWeb classes both implement the IDisposable interface. Microsoft .NET requires objects that implement the IDisposable interface to properly cleanup the unmanaged resources by explicitly calling the Dispose() method when you are finished using them. Internally, SPSite and SPWeb both hold references to an "internal class Microsoft.SharePoint.Library.SPRequest" which holds on to unmanaged COM resources. The consequence of not explicitly disposing unmanaged resources in a timely fashion can lead to not having enough memory for further allocations and quickly consumes memory. Under the hood, when reviewing dump files we see the that the managed objects used by SPSite and SPWeb are relatively small and it's the unmanaged resources that are most concerning and account for approximately 1MB to 2MB for each object instance! Omitting to explicitly call Dispose() means the .NET (non-deterministic) garbage collector gets out of sync with the finalizer and the unmanaged memory does not get reclaimed in a timely manner possibly blocking future memory allocations. For further reading I recommend reviewing Stefan Goßner's blog Dealing with Memory Pressure problems in MOSS/WSS .
The unmanaged memory leaks can grow very quickly especially when traversing through frequently called areas like site navigation code and item event receivers. The lack of proper Dispose() hygiene can increase your risk of frequent IIS Application Domain recycles (see Steve Sheppard's blog Overlapped Recycling And SharePoint: Why SharePoint Requires It), Out Of Memory (OOM) exceptions, high memory consumption, and poor performing SharePoint production environments.
To Dispose or not Dispose?! That is the question...
To make matters more confusing for SharePoint developers there are times when SPSite and SPWeb objects should not be disposed and are cleaned up by SharePoint and ASP.NET after page processing is completed. In addition, there are cases when developers indirectly call a property on a object that creates and holds an internal reference to a SPSite or SPWeb object (for example SPSite.RootWeb property). Understanding the origin and the scope the object was created is paramount when determining whether or not to explicitly call dispose.
Dispose Patterns
When writing customized SharePoint code you need to be aware of the scope and context of each SPSite, SPWeb, and PublishingWeb objects lifetime. When objects are created and destroyed in the same method or iteration scope (foreach or do/while loop) they are the easiest to clean handle. Things become more complex to review when developers create objects in one method and dispose in another. Areas to be aware of are assigning objects to class variables or static/global variables which may hold on to the object reference across method calls.
With that said, below are the Dispose most common patterns you need to be aware of when developing customized SharePoint code.
Microsoft.SharePoint.SPList.BreakRoleInheritance (Added 4/11/08)
- When using the SPList.BreakRoleInheritance() method a internal call to ParentWeb property is called and must be disposed by the caller. Note that SPListItem.BreakRoleInheritance() and SPWeb.BreakRoleInheritance() DO NOT require a call to Dispose.
void SPListBreakRoleInheritanceLeak() { using (SPSite siteCollection = new SPSite("http://moss")) { using (SPWeb web = siteCollection.OpenWeb()) { SPList list = web.Lists["ListName"]; list.BreakRoleInheritance(true); } // SPWeb object outerWeb.Dispose() automatically called } // SPSite object siteCollection.Dispose() automatically called } void SPListBreakRoleInheritanceBestPractice() { using (SPSite siteCollection = new SPSite("http://moss")) { using (SPWeb web = siteCollection.OpenWeb()) { SPList list = web.Lists["ListName"]; list.BreakRoleInheritance(true); list.ParentWeb.Dispose(); // Best practice } // SPWeb object outerWeb.Dispose() automatically called } // SPSite object siteCollection.Dispose() automatically called }
Microsoft.SharePoint.WebPartPages.SPLimitedWebPartManager
- When using the SPLimitedWebPartManager class a reference to an internal SPWeb object which needs to be disposed.
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 web.Dispose() automatically called } // SPSite object siteCollection.Dispose() automatically called } void SPLimitedWebPartManagerBestPractice() { using (SPSite siteCollection = new SPSite("http://moss")) { using (SPWeb web = siteCollection.OpenWeb()) { SPFile page = web.GetFile("Source_Folder_Name/Source_Page"); using (SPLimitedWebPartManager webPartManager = page.GetLimitedWebPartManager(PersonalizationScope.Shared)) { webPartManager.Web.Dispose(); } } // SPWeb object web.Dispose() automatically called } // SPSite object siteCollection.Dispose() automatically called }
Microsoft.SharePoint.Publishing.PublishingWeb (MOSS 2007 Only)
- When using the PublishingWeb class GetPublishingWebs() method which returns a PublishingWebCollection you are required to call Close() on the enumerated innerPubWeb objects. Note that when you are only calling GetPublishingWeb() method without using the GetPublishingWebs() collection you are not required to explicitly call close.
void PublishingWebCollectionLeak() { using (SPSite siteCollection = new SPSite("http://moss")) { using (SPWeb web = siteCollection.OpenWeb()) { PublishingWeb outerPubWeb = PublishingWeb.GetPublishingWeb(web); PublishingWebCollection pubWebCollection = outerPubWeb.GetPublishingWebs(); foreach (PublishingWeb innerPubWeb in pubWebCollection) { } } // SPWeb object web.Dispose() automatically called } // SPSite object siteCollection.Dispose() automatically called } void PublishingWebCollectionBestPractice() { using (SPSite siteCollection = new SPSite("http://moss")) { using (SPWeb web = siteCollection.OpenWeb()) { PublishingWeb outerPubWeb = PublishingWeb.GetPublishingWeb(web); PublishingWebCollection pubWebCollection = outerPubWeb.GetPublishingWebs(); foreach (PublishingWeb innerPubWeb in pubWebCollection) { // innerPubWeb referenced so we must call outerPubWeb.Close() // otherwise, we will leak each and every innerPubWeb object innerPubWeb.Close(); } } // SPWeb object web.Dispose() automatically called } // SPSite object siteCollection.Dispose() automatically called } void GetPublishingWebBestPractice() { using (SPSite siteCollection = new SPSite("http://moss")) { using (SPWeb web = siteCollection.OpenWeb()) { // As long as you are not using the collection you don't have // to call singlePubWeb.Close() explicitly PublishingWeb singlePubWeb = PublishingWeb.GetPublishingWeb(web); } // SPWeb object web.Dispose() automatically called } // SPSite object siteCollection.Dispose() automatically called }
Microsoft.SharePoint.Publishing.PublishingWebCollection (MOSS 2007 Only)
- When using the PublishingWebCollection class GetPublishingWebs().Add() method you are required to call close on the newly added PublishingWeb object. For a code sample refer to the MOSS SDK GetPublishingWebs() sample.
Microsoft.SharePoint.SPSite
Note: With C# you can automatically have the Dispose() called for you when the object leaves the scope by wrapping the code with the using() { } statement.
void CreatingSPSiteLeak() { SPSite siteCollection = new SPSite("http://moss"); } void CreatingSPSiteExplicitDisposeBestPractice() { SPSite siteCollection = new SPSite("http://moss"); siteCollection.Dispose(); } void CreatingSPSiteWithAutomaticDisposeBestPractice() { using (SPSite siteCollection = new SPSite("http://moss")) { } // SPSite object siteCollection.Dispose() automatically called }
Avoid the following pattern which collapses SPSite and SPWeb calls. The example returns the SPWeb site object wrapped by a using statement which gets disposed but there is no way to dispose the underlying SPSite object.
void OpenWebLeak() { using (SPWeb web = new SPSite(SPContext.Current.Web.Url).OpenWeb()) { } // SPWeb object web.Dispose() automatically called } void OpenWebBestPractice() { using (SPSite siteCollection = new SPSite("http://moss")) { using (SPWeb web = siteCollection.OpenWeb()) { } // SPWeb object web.Dispose() automatically called } // SPSite object siteCollection.Dispose() automatically called }
void RootWebLeak() { using (SPSite siteCollection = new SPSite("http://moss")) { SPWeb siteRootWeb = siteCollection.RootWeb; // Internal reference to SPWeb siteRootWeb string sTitle = siteRootWeb.Title; string sUrl = siteRootWeb.Url; } // SPSite object siteCollection.Dispose() automatically called } void RootWebBestPractice() { using (SPSite siteCollection = new SPSite("http://moss")) { using (SPWeb siteRootWeb = siteCollection.RootWeb) // Internal reference to SPWeb siteRootWeb { string sTitle = siteRootWeb.Title; string sUrl = siteRootWeb.Url; } // SPWeb object siteRootWeb.Dispose() automatically called } // SPSite object siteCollection.Dispose() automatically called }
- LockIssue property indirectly creates a instance of SPWeb object on the RootWeb property which needs to be disposed.
void LockIssueLeak() { using (SPSite siteCollection = new SPSite("http://moss")) { string strLockIssue = siteCollection.LockIssue; } // SPSite object siteCollection.Dispose() automatically called } void LockIssueBestPractice() { using (SPSite siteCollection = new SPSite("http://moss")) { string strLockIssue = siteCollection.LockIssue; siteCollection.RootWeb.Dispose(); } // SPSite object siteCollection.Dispose() automatically called }
- Owner property indirectly creates a instance of SPWeb object on the RootWeb property which needs to be disposed.
void OwnerLeak() { using (SPSite siteCollection = new SPSite("http://moss")) { SPUser ownerUser = siteCollection.Owner; } // SPSite object siteCollection.Dispose() automatically called } void OwnerBestPractice() { using (SPSite siteCollection = new SPSite("http://moss")) { SPUser ownerUser = siteCollection.Owner; siteCollection.RootWeb.Dispose(); } // SPSite object siteCollection.Dispose() automatically called }
- SecondaryContact property indirectly creates a instance of SPWeb object on the RootWeb property which needs to be disposed.
void SecondaryContactLeak() { using (SPSite siteCollection = new SPSite("http://moss")) { SPUser secondaryContactUser = siteCollection.SecondaryContact; } // SPSite object siteCollection.Dispose() automatically called } void SecondaryContactBestPractice() { using (SPSite siteCollection = new SPSite("http://moss")) { SPUser secondaryContactUser = siteCollection.SecondaryContact; siteCollection.RootWeb.Dispose(); } // SPSite object siteCollection.Dispose() automatically called }
void AllWebsForEachLeak() { using (SPSite siteCollection = new SPSite("http://moss")) { using (SPWeb outerWeb = siteCollection.OpenWeb()) { foreach (SPWeb innerWeb in siteCollection.AllWebs) { } } // SPWeb object outerWeb.Dispose() automatically called } // SPSite object siteCollection.Dispose() automatically called } void AllWebsForEachBestPractice() { using (SPSite siteCollection = new SPSite("http://moss")) { using (SPWeb outerWeb = siteCollection.OpenWeb()) { foreach (SPWeb innerWeb in siteCollection.AllWebs) { innerWeb.Dispose(); } } // SPWeb object outerWeb.Dispose() automatically called } // SPSite object siteCollection.Dispose() automatically called } void AllWebsIndexerLeak() { using (SPSite siteCollection = new SPSite("http://moss")) { SPWeb web = siteCollection.AllWebs[0]; } // SPSite object siteCollection.Dispose() automatically called } void AllWebsIndexerBestPractice() { 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 }
- AllWebs.Add() returns a instance of SPWeb object which needs to be disposed.
void AllWebsAddLeak() { using (SPSite siteCollection = new SPSite("http://moss")) { SPWeb web = siteCollection.AllWebs.Add("site-relative URL"); } // SPSite object siteCollection.Dispose() automatically called } void AllWebsAddBestPractice() { 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 }
Microsoft.SharePoint.SPWeb
void ParentWebLeak() { using (SPSite siteCollection = new SPSite("http://moss")) { using (SPWeb outerWeb = siteCollection.OpenWeb()) { SPWeb parentWeb = outerWeb.ParentWeb; // Internal reference to SPWeb parentWeb string sTitle = parentWeb.Title; string sUrl = parentWeb.Url; } // SPWeb object outerWeb.Dispose() automatically called } // SPSite object siteCollection.Dispose() automatically called } void ParentWebBestPractice() { using (SPSite siteCollection = new SPSite("http://moss")) { using (SPWeb outerWeb = siteCollection.OpenWeb()) { using (SPWeb parentWeb = outerWeb.ParentWeb) // Internal reference to SPWeb parentWeb { string sTitle = parentWeb.Title; string sUrl = parentWeb.Url; } // SPWeb object parentWeb.Dispose() automatically called } // SPWeb object outerWeb.Dispose() automatically called } // SPSite object siteCollection.Dispose() automatically called }
- SPWeb.Webs property returns SPWeb object that needs to be disposed
void WebsLeak() { using (SPSite siteCollection = new SPSite("http://moss")) { using (SPWeb outerWeb = siteCollection.OpenWeb()) { foreach (SPWeb innerWeb in outerWeb.Webs) { } } // SPWeb object outerWeb.Dispose() automatically called } // SPSite object siteCollection.Dispose() automatically called } void WebsBestPractice() { using (SPSite siteCollection = new SPSite("http://moss")) { using (SPWeb outerWeb = siteCollection.OpenWeb()) { foreach (SPWeb innerWeb in outerWeb.Webs) { innerWeb.Dispose(); } } // SPWeb object outerWeb.Dispose() automatically called } // SPSite object siteCollection.Dispose() automatically called }
Microsoft.SharePoint.WebControls.SPControl
- GetContextSite(Context) and GetContextWeb(Context) methods return SPSite and SPWeb objects respectively that DO NOT need a call to Dispose() and will be disposed automatically by SharePoint.
void SPControlBADPractice() { SPSite siteCollection = SPControl.GetContextSite(Context); siteCollection.Dispose(); SPWeb web = SPControl.GetContextWeb(Context); web.Dispose(); } void SPControlBestPractice() { SPSite siteCollection = SPControl.GetContextSite(Context); SPWeb web = SPControl.GetContextWeb(Context); // Do NOT call Dispose() }
Microsoft.SharePoint.SPContext
- SPContext.Current.Site & SPContext.Site as well as SPContext.Current.Web & SPContext.Web properties return SPSite and SPWeb objects respectively that DO NOT need a call to Dispose() and will be disposed automatically by SharePoint.
void SPContextBADPractice() { SPSite siteCollection = SPContext.Current.Site; siteCollection.Dispose(); SPWeb web = SPContext.Current.Web; web.Dispose(); } void SPContextBestPractice() { SPSite siteCollection = SPContext.Current.Site; SPWeb web = SPContext.Current.Web; // Do NOT call Dispose() // 2nd Best Practice example which demonstrates combining calls in same scope using (SPWeb webRoot = SPContext.Current.Site.RootWeb) { // SPSite is ok since it came from SPContext and // RootWeb will be disposed automatically by C# using statement // this is a fairly common pattern to keep an eye out for } }
Microsoft.SharePoint.Administration.SPSiteCollection
- SPSiteCollection[] indexer returns a SPSite object which needs to be disposed.
void SPSiteCollectionIndexerLeak() { SPWebApplication webApp = new SPSite("http://moss").WebApplication; SPSiteCollection siteCollections = webApp.Sites; SPSite siteCollection = siteCollections[0]; } void SPSiteCollectionIndexerBestPractice() { SPWebApplication webApp = new SPSite("http://moss").WebApplication; SPSiteCollection siteCollections = webApp.Sites; SPSite siteCollection = siteCollections[0]; siteCollection.Dispose(); } void SPSiteCollectionForEachLeak() { SPWebApplication webApp = new SPSite("http://moss").WebApplication; SPSiteCollection siteCollections = webApp.Sites; foreach (SPSite siteCollection in siteCollections) { } } void SPSiteCollectionForEachBestPractice() { SPWebApplication webApp = new SPSite("http://moss").WebApplication; SPSiteCollection siteCollections = webApp.Sites; foreach (SPSite siteCollection in siteCollections) { siteCollection.Dispose(); } }
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"); } void SPSiteCollectionAddBestPractice() { 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 }
Microsoft.SharePoint.Portal.SiteData.Area.Web (Obsolete)
- Area.Web property returns a SPWeb object which will need to be disposed. Although classes Area and AreaManager are now obsolete in MOSS 2007 it is still of concern when migrating legacy code.
void AreaWebLeak() { // AreaManager and Area are obsolete in MOSS but this should still be noted Area area = AreaManager.GetArea(PortalContext.Current, new Guid("{GUID}")); string str = area.Web.Title; } void AreaWebBestPractice() { // AreaManager and Area are obsolete in MOSS but this should still be noted Area area = AreaManager.GetArea(PortalContext.Current, new Guid("{GUID}")); string str = area.Web.Title; area.Web.Dispose(); }
Cross Method Dispose Patterns
- The following example demonstrates the common practice of holding onto the SPSite and SPWeb objects across methods in a class. There are times where this design pattern is required however make sure you don't overlook the appropriate time to call dispose when you are finished with the cross method calls. Below is an example of this pattern and shows and example of a leak of both SPSite and SPWeb when the class is torn down.
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; } } }
Summary
If you have extended MOSS 2007 or WSS 3.0 with custom code you should digest this post carefully to avoid expensive consequences commonly found in production environments. For a more verbose explanation of many of the topics covered in this blog post I suggest you read Scott Harris's MSDN White Papers Best Practices: Using Disposable Windows SharePoint Services Objects and Best Practices: Common Coding Issues When Using the SharePoint Object Model .
Special thanks to my Microsoft colleagues Scott Harris, Stefan Goßner, Steve Sheppard, Sean Thompson, Lisa Guthrie, Cliff Green, and Rick Caudle for reviewing and helping contribute to this blog post.