SPList.ParentWeb Leaks Revisited

A while back I looked into whether or not SPList.ParentWeb really needs to be disposed: Is SPList.ParentWeb a Leak? The specific motivation was to investigate why SPList.BreakRoleInheritance() requires Dispose() on ParentWeb property, as seen in Roger’s Dispose Patterns by Example. I stand by my original conclusion that this advice generally does more harm than good, but thought it would be useful to discuss in a bit more detail.

Why dispose ParentWeb?

As I showed before, the code for SPList.ParentWeb works like this:

if (SPUtility.StsCompareStrings(this.m_Lists.Web.ServerRelativeUrl, this.ParentWebUrl))
  return this.m_Lists.Web;

if (this.m_parentWeb == null)
  this.m_parentWeb = this.m_Lists.Web.Site.OpenWeb(this.ParentWebUrl);
return this.m_parentWeb;

In the vast majority of cases, the URLs will match and return the parent collection’s Web, which should not be disposed (unless, of course, you own it and know it is ready for disposal). Only in the exceptional case that the list’s ParentWebUrl indicates it doesn’t live with its parent collection will a new SPWeb be created. I believe it is this exception, rather than the norm, that leads to Roger’s suggestion that ParentWeb should be disposed.

On a curious and somewhat related note, SPListItem.Web doesn’t use ParentWeb:

public SPWeb get_Web()
{
    return this.m_Items.List.Lists.Web;
}

Why BreakRoleInheritance()?

Though BreakRoleInheritance() has been singled out, the real culprit is an internal property:

private SPSecurableObjectImpl get_SecurableObjectImpl()
{
    if (this.m_SecurableObjectImpl == null)
    {
        Guid guidScopeId = (Guid) this.m_arrListProps[0x22, this.m_iRow];
        bool hasUniquePerm = guidScopeId != this.ParentWeb.RoleAssignments.Id;
        this.m_SecurableObjectImpl = new SPSecurableObjectImpl(this.ParentWeb, this, ...);
    }
    return this.m_SecurableObjectImpl;
}

This property is then used in the implementations of several methods/properties from ISecurableObject:

  • SPList.AllRolesForCurrentUser
  • SPList.BreakRoleInheritance()
  • SPList.HasUniqueRoleAssignments
  • SPList.ResetRoleInheritance()
  • SPList.ReusableAcl
  • SPList.RoleAssignments

(Incidentally, SPSecurableObjectImpl is also used by SPWeb and SPListItem to implement that interface.)

SPList.get_SecurableObjectImplSo now we’re dealing with several more points of entry. For example, in SPListItem:

public ISecurableObject get_FirstUniqueAncestor()
{
    this.InitSecurity();
    if (this.RoleAssignments.Id == this.ParentList.RoleAssignments.Id)
    {
        return this.ParentList.FirstUniqueAncestor;
    }
    if (!this.HasUniqueRoleAssignments)
    {
        return this.Web.GetFolder(this.m_strPermUrl).Item;
    }
    return this;
}

I could show several more obscure code paths with the same result, but the specifics are irrelevant. My point is that trying to figure out if SPList.ParentWeb has been referenced is a wasted effort.

What to do?

The way I see it, we have two options:

  1. Assume that ParentWeb is safe enough and if we have memory problems later we can investigate.
  2. Assume that ParentWeb is leaky and figure out a safe way to Dispose() it.

I’m satisfied with the former, but if you’re not — or if you have a circumstance where the ParentWebUrl comparison actually fails (please share) — then your code should check if it needs to dispose first:

SPWeb web = SPContext.Current.Web;
SPList list;

try
{
  list = web.Lists["ListName"];
  list.BreakRoleInheritance(true);
}
finally
{
  if(list.ParentWeb != web)
    list.ParentWeb.Dispose();
}

Or we can use an extension method:

public static void DisposeParentWeb(this SPList list)
{
  if(list.ParentWeb != list.Lists.Web)
    list.ParentWeb.Dispose();
}
Follow

Get every new post delivered to your Inbox.