Mar 10, 2010

SharePoint Disposing Myth: You have to dispose SPWeb explicitly!?

It seems to me that every post, article and book tells you to explicitly dispose every SPWeb (excepts them coming from SPContext). But is that right?
Not quite! Every SPSite tracks the SPWebs that it has emitted. A internal list named “m_openedWebs” holds these references. It doesn’t matter from which member of the SPSite you will get the SPWeb (RootWeb, OpenWeb, AllWebs, Add) the SPSite holds a reference on it. When you dispose the SPSite it loops through all SPWebs in “m_openedWebs” and calls the Close method on each. The dispose method of SPWeb does nothing else than calling it’s close method. Means when you dispose a SPSite, all SPWebs that it has emitted will be disposed implicitly! A construct like this
using(SPSite site = new SPSite("http://server/site"))
{
     using(SPWeb web = site.OpenWeb())
     {
       //...
     }
}
is not necessary, because the using for the SPSite will implicitly dispose all of it SPWebs. By the way if you call the dispose method of the SPWeb explicitly it will internally remove itself from the “m_openedWebs” list. Sure there are scenarios where explicitly disposing of SPWebs is important, for example when you loop through the AllWebs property of the SPSite with maybe hundreds of SPWebs. Because every SPWeb consumes a lot of memory! On the other hand you don’t have to be afraid when you pass a SPWeb reference across your API. When you finally dispose the SPSite, all SPWebs it has emitted will be disposed too and you will not cause a memory leak. I think the SharePoint team have implemented this pretty well.

When you create an instance of SPSite you are responsible to dispose it. When to dispose SPWeb that have been emitted by your SPSite depends on the scenario.


What could cause problems indeed is when you still use a SPWeb that has already been disposed, doesn’t matter if it has been disposed implicitly or explicitly. The SPWeb will be reopened but will not be listed in the “m_openedWebs” of the SPSite. Means it can not be implicitly disposed by the SPSite anymore. A better and approach would have been to throw an ObjectDisposedException on all members after the SPWeb has been disposed.

You don’t believe? Just take a look at the following snippet or even better let it run:
Console.WriteLine("Create a SPSite");
SPSite site = new SPSite("http://localhost:101");

Console.WriteLine("Open 5 SPWebs...");
SPWeb web1 = site.RootWeb;
SPWeb web2 = site.OpenWeb();
SPWeb web3 = site.OpenWeb("my");
SPWeb web4 = site.AllWebs[1];
SPWeb web5 = site.AllWebs.Add(Guid.NewGuid().ToString());

int openedWebs = (typeof(SPSite).InvokeMember("m_openedWebs",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField,
null, site, null) as ICollection).Count;      

Console.WriteLine("Acutally opened SPWebs: " + openedWebs.ToString());

Console.WriteLine("Dispose web1 explicitly");
web1.Dispose();

openedWebs = (typeof(SPSite).InvokeMember("m_openedWebs",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField,
null, site, null) as ICollection).Count;     

Console.WriteLine("Dispose the SPSite");         
site.Dispose();

openedWebs = (typeof(SPSite).InvokeMember("m_openedWebs",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField,
null, site, null) as ICollection).Count;

Console.WriteLine("Acutally opened SPWebs: " + openedWebs.ToString());

Console.ReadKey();   


Any feedback?

6 comments:

Torsten said...

Schöne Erklärung.

Der Dispose Checker ist aber anderer Meinung. Ok das ist er ja meistens :)

SharePoint.BestPractices : In method ... Dispose was not called on fileWeb

Christian said...

I suppose the Dispose Checker is wrong! Check out the SPSite with .NET Reflector.

Torsten said...

I know. The Dispose Checker isn't always right. Perhaps we should tell them to change their check routines.

Anonymous said...

If you have about 100 sub sites, you will see why disposing inside the for loop is necessary. The best practice is that we should keep them in the memory as short as possible.

Christian said...

Hello, that's right for this scenario!

Anonymous said...

wieso sollte man eine 100 prozent lösung (dispose aufrufen) nicht einfach verwenden??

sehe da keinen nachteil und im gegenzug keinen vorteil dispose nicht aufzurufen...