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; } } }
March 29, 2009 at 11:37 pm
[…] the enumerator should include proper disposal of the objects it creates—this is precisely what my AsSafeEnumerable() iterator […]
June 1, 2009 at 1:22 am
[…] LINQ for SPWebCollection Revisited: AsSafeEnumerable […]
July 22, 2009 at 9:05 pm
[…] LINQ for SPWebCollection Revisited: AsSafeEnumerable […]
May 19, 2010 at 6:21 am
Great post, thank u!!
November 21, 2012 at 10:42 am
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.
January 6, 2013 at 3:10 pm
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.