LINQ for SPWebCollection Revisited: AsSafeEnumerable

I have previously discussed implementations of some core LINQ extension methods for SPWebCollection, a process complicated by the fact that SPWeb is IDisposable. The core logic is correct, but the lack of try...finally means exceptions will cause leaks. Since this affects any enumeration of the list, not just LINQ-specific operations, I started to look into making the enumerator smarter. This turned out to be much simpler than I expected:

public static IEnumerable<SPWeb> AsSafeEnumerable(this SPWebCollection webs)
{
  foreach (SPWeb web in webs)
  {
    try
    {
      yield return web;
    }
    finally
    {
      web.Dispose();
    }
  }
}

Internally, this logic is wrapped in a generated class (disassembled at the end of this post) that implements IEnumerable<SPWeb> and IEnumerator<SPWeb>. The finally block is compiled into its own method which is called in two places: MoveNext() before moving to the next item, and Dispose(). An often-overlooked tidbit about foreach is that the compiler will generate a try...finally block if the enumerator is (or might be) IDisposable, which ours is (inherited from IEnumerator<T>). So whether the loop advances normally or exits early (exceptions, breaks, etc), the current SPWeb will be disposed properly.

The bonus in handling disposal through the enumerator is that our LINQ extension methods can be delegated to the framework:

public static IEnumerable<SPWeb> Where(this SPWebCollection webs,
                                       Func<SPWeb, bool> predicate)
{
  return webs.AsSafeEnumerable().Where(predicate);
}
public static void ForEach(this SPWebCollection webs,
                           Action<SPWeb> action,
                           Func<SPWeb, bool> predicate)
{
  foreach (SPWeb web in webs.Where(predicate))
    action(web);
}

public static void ForEach(this SPWebCollection webs, Action<SPWeb> action)
{
  webs.ForEach(action, w => true);
}

public static IEnumerable<TResult> Select<TResult>(this SPWebCollection webs,
                                                   Func<SPWeb, TResult> selector,
                                                   Func<SPWeb, bool> predicate)
{
  return webs.Where(predicate).Select(selector);
}

public static IEnumerable<TResult> Select<TResult>(this SPWebCollection webs,
                                                   Func<SPWeb, TResult> selector)
{
  return webs.AsSafeEnumerable().Select(selector);
}

This approach was always possible for collections of normal objects using Cast<T>(), but is now available for SPWebCollection and SPSiteCollection as well.

Using with LINQ

I’ve talked a lot about LINQ-enabling SharePoint collections, but I’ve never posted actual LINQ code using it. With the above extensions defined, we can safely use LINQ against the original collection:

var webs = from w in site.AllWebs
           where string.IsNullOrEmpty(w.Theme)
           orderby w.Title
           select w.Title;

Note in this example that we can use orderby, even though we haven’t defined OrderBy() on SPWebCollection, because the call is chained from the IEnumerable<SPWeb> returned by Where(). However, this won’t compile:

var webs = from w in site.AllWebs
            orderby w.Title
            select w.Title;

Giving the following error:

Could not find an implementation of the query pattern for source type ‘Microsoft.SharePoint.SPWebCollection’.  ‘OrderBy’ not found.  Consider explicitly specifying the type of the range variable ‘w’.

We also don’t want to take the suggestion to specify a type for ‘w’:

var webs = from SPWeb w in site.AllWebs
            orderby w.Title
            select w.Title;

As this inserts a Cast, leaking every SPWeb:

IEnumerable<string> webs2 = site.AllWebs.Cast<SPWeb>().OrderBy<SPWeb, string>(delegate (SPWeb w) {
  return w.Title;
}).Select<SPWeb, string>(delegate (SPWeb w) {
  return w.Title;
});

For operators we haven’t defined, we can always call AsSafeEnumerable() to get an IEnumerable<SPWeb> that has everything:

var lists = from w in site.AllWebs.AsSafeEnumerable()
            from SPList l in w.Lists
            where !l.Hidden && !w.IsRootWeb
            select new { WebTitle = w.Title, ListTitle = l.Title };

AsSafeEnumerable Disassembled

[CompilerGenerated]
private sealed class <AsSafeEnumerable>d__0 : IEnumerable<SPWeb>, IEnumerable,
                                              IEnumerator<SPWeb>, IEnumerator, IDisposable
{
    // Fields
    private int <>1__state;
    private SPWeb <>2__current;
    public SPWebCollection <>3__webs;
    public IEnumerator <>7__wrap2;
    public IDisposable <>7__wrap3;
    private int <>l__initialThreadId;
    public SPWeb <web>5__1;
    public SPWebCollection webs;

    // Methods
    [DebuggerHidden]
    public <AsSafeEnumerable>d__0(int <>1__state)
    {
        this.<>1__state = <>1__state;
        this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId;
    }

    private void <>m__Finally4()
    {
        this.<>1__state = -1;
        this.<>7__wrap3 = this.<>7__wrap2 as IDisposable;
        if (this.<>7__wrap3 != null)
        {
            this.<>7__wrap3.Dispose();
        }
    }

    private void <>m__Finally5()
    {
        this.<>1__state = 1;
        this.<web>5__1.Dispose();
    }

    private bool MoveNext()
    {
        bool flag;
        try
        {
            int num = this.<>1__state;
            if (num != 0)
            {
                if (num != 3)
                {
                    goto Label_009A;
                }
                goto Label_0073;
            }
            this.<>1__state = -1;
            this.<>7__wrap2 = this.webs.GetEnumerator();
            this.<>1__state = 1;
            while (this.<>7__wrap2.MoveNext())
            {
                this.<web>5__1 = (SPWeb) this.<>7__wrap2.Current;
                this.<>1__state = 2;
                this.<>2__current = this.<web>5__1;
                this.<>1__state = 3;
                return true;
            Label_0073:
                this.<>1__state = 2;
                this.<>m__Finally5();
            }
            this.<>m__Finally4();
        Label_009A:
            flag = false;
        }
        fault
        {
            this.System.IDisposable.Dispose();
        }
        return flag;
    }

    [DebuggerHidden]
    IEnumerator<SPWeb> IEnumerable<SPWeb>.GetEnumerator()
    {
        Enumerable.<AsSafeEnumerable>d__0 d__;
        if ((Thread.CurrentThread.ManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2))
        {
            this.<>1__state = 0;
            d__ = this;
        }
        else
        {
            d__ = new Enumerable.<AsSafeEnumerable>d__0(0);
        }
        d__.webs = this.<>3__webs;
        return d__;
    }

    [DebuggerHidden]
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.System.Collections.Generic.IEnumerable<Microsoft.SharePoint.SPWeb>.GetEnumerator();
    }

    [DebuggerHidden]
    void IEnumerator.Reset()
    {
        throw new NotSupportedException();
    }

    void IDisposable.Dispose()
    {
        switch (this.<>1__state)
        {
            case 1:
            case 2:
            case 3:
                try
                {
                    switch (this.<>1__state)
                    {
                    }
                    break;
                    try
                    {
                    }
                    finally
                    {
                        this.<>m__Finally5();
                    }
                }
                finally
                {
                    this.<>m__Finally4();
                }
                break;
        }
    }

    // Properties
    SPWeb IEnumerator<SPWeb>.Current
    {
        [DebuggerHidden]
        get
        {
            return this.<>2__current;
        }
    }

    object IEnumerator.Current
    {
        [DebuggerHidden]
        get
        {
            return this.<>2__current;
        }
    }
}
About these ads

6 Responses to “LINQ for SPWebCollection Revisited: AsSafeEnumerable”

  1. SharePoint Disposal Wish List « Solutionizing .NET Says:

    [...] the enumerator should include proper disposal of the objects it creates—this is precisely what my AsSafeEnumerable() iterator [...]

  2. SPExLib Release: These Are A Few Of My Favorite Things « Solutionizing .NET Says:

    [...] LINQ for SPWebCollection Revisited: AsSafeEnumerable [...]

  3. Keith Habla Code - Solutionizing .NET (Keith Dahlby) - Says:

    [...] LINQ for SPWebCollection Revisited: AsSafeEnumerable [...]

  4. Johan Leino Says:

    Great post, thank u!!

  5. Joe Says:

    Hi,
    what do you thing about this code:
    private static void allweburls(SPWebApplication webapplication, List allwebsinwebapp)
    {
    for (int i = 0; i < webapplication.Sites.Count; i++)
    {
    try
    {
    using (SPSite site = webapplication.Sites[i])
    {
    allwebsinwebapp.AddRange(from web in site.AllWebs select web.Url);
    }
    }
    catch (Exception ex)
    {
    Console.WriteLine(site.Url + " " + ex.Message);
    }
    }
    }

    Do I still need to dispose?
    I'm using the url string list for some code outside sharepoint.

    • Keith Dahlby Says:

      Accumulating the web URLs in a list is a good approach here, and technically there’s no need to Dispose() each element in AllWebs because the SPSite is being immediately cleaned up which disposes all undisposed child SPWeb instances. However, for site collections with many sites it might be a good idea to proactively Dispose() each SPWeb anyway.


Comments are closed.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: