I was almost off my SharePoint disposal kick when Jeremy Jameson had to pull me back in with this post. Resisting the temptation to rehash some stuff I’ve covered before, I thought it might be therapeutic to spend some time discussing what the dispose story should look like. I’m sure the API for WSS 4.0 is more or less locked at this point, but it still might be useful to frame how I think about ownership of these blasted IDisposables.
In my SharePoint fantasy world, the dispose story can be explained in four bullets:
- Use of a disposed object throws an exception; failures to dispose properly are logged.
- IDisposable references retrieved from an instance property are owned by the instance.
- IDisposable references retrieved from a constructor, method or collection indexer are owned by the developer and should always be disposed.
- IDisposable references from an enumerator are owned and disposed by the enumerator.
Let’s examine these a bit more closely.
1. Disposed = Invalid = Use is Exceptional; Discoverability = Good
The leaked object logging is already pretty good, but that’s more than made up for by the epic failure to actually close objects that have been closed. Fixing discoverability would make all other points a matter of convenience.
2. Instance Properties
An instance property suggests “mine”. My context’s Site, my site’s RootWeb, my feature’s Parent, etc. If something is “mine,” then I should be responsible for it. This behavior is already consistent with three exceptions:
This property is tricky because the instance is shared for all consumers of the SPWeb (think SPContext.Web), but will not be cleaned up if the SPWeb is disposed and has no mechanism to notify the child that it has been disposed to null out the reference to it. So what to do? Well in accordance with my fantasy guidelines, there are two options, either of which would resolve the ambiguity:
- Leave ParentWeb as a property and require that the SPWeb dispose its parent as part of the clean-up process.
- Provide SPWeb.GetParentWeb() instead, returning a new SPWeb on each call that is owned by the developer.
Since GetParentWeb() can be implemented as an extension method, I guess I would prefer option 1.
This property is almost fine, but for some reason has an edge case returns a new SPWeb (details here). It should be fine as a property, but that edge case needs to disappear. Why can’t it just return SPList.Lists.Web?
This property returns a new SPSite on each call, so ideally it would be a method instead, perhaps GetPersonalSite. That seems much easier than having UserProfile implement IDisposable to facilitate cleanup of shared instance.
3. Constructors & Methods & Indexers, Oh My!
If properties are “mine”, then everything else is “yours”. Again, the vast majority of the API already fits this behavior. The few discrepancies:
SPControl.GetContextSite() & SPControl.GetContextWeb()
I believe these are the only methods that return IDisposables that SharePoint owns. MSDN clearly indicates that behavior, but for consistency the preferred usage (consistent with our fantasy guidelines) would be to use an SPContext’s Site and Web properties instead.
I’ve already discussed the semi-disposability of PublishingWeb, which just needs a few tweaks to fit nicely in my fantasy model:
- Implement IDisposable! Close() is already implemented, just need the interface to take advantage of our languages’ using constructs.
- I believe ParentPublishingWeb would be fixed if SPWeb.ParentWeb were disposed with the child. Alternatively, change to GetParentPublishingWeb and ensure that the returned PublishingWeb will close the internal SPWeb on dispose.
- PublishingWebCollection.Add() is fine, but the indexer works in such a way that an internal SPWeb is created that is not cleaned up by PublishingWeb.Close(). I would consider this a bug in the current code base, which would naturally be fixed in my fantasy.
4. Enumerating Collections
This isn’t quite as simple as it would seem. When dealing with collections and enumerators, there are essentially two classes of operation:
- Enumerate over all or part of the collection and perform an operation. (LINQ Select, GroupBy, OrderBy)
- Enumerate over the collection to pick an object from the collection to return. (LINQ First, Last, ElementAt)
In my experience, the vast majority fall into the first category, including most of LINQ. These operations shouldn’t need to know that the objects being enumerated are IDisposable; it’s up to the enumerator to behave appropriately. Rather than sacrifice these many useful operations, I suggest that the enumerator should include proper disposal of the objects it creates—this is precisely what my AsSafeEnumerable() iterator does.
The second category can either be handled using for loops and indexers, which return objects that the caller must dispose, or through higher-order functions that allow dispose-safe operation on or selection from the desired element. But again, these seem to be the exception rather than the rule, and can be handled accordingly.
The unfortunate reality is that most of these adjustments could never happen because breaking changes are generally frowned upon. But perhaps in framing our disposal thought process around some simple rules with documented exceptions, it may be easier to get things right without leaning on tools like SPDisposeCheck.