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;
        }
    }
}

PowerShell Get-Type: Simplified Generics

PowerShell doesn’t include any syntactic sugar for dealing with generic objects, so a number of people have posted code snippets online showing how to create generic lists, dictionaries, etc. Interestingly, nobody has pointed out that the real problem isn’t creating the objects—New-Object does that without issue (for the most part)—but simply that it’s tedious to build correct closed type references for generics. This post attempts to address this core problem.

If we leverage the $args array, a trivial solution is quickly discovered:

function TypeOf-Generic ([type] $base) {
  $base.MakeGenericType($args)
}

Which is quite simple to use:

$intList = New-Object (TypeOf-Generic System.Collections.Generic.List int)

However, a bit more code yields a more complete solution:

function global:Get-Type (
    $type = $(throw "Please specify a type")
    )
{
  trap [System.Management.Automation.RuntimeException] { throw ($_.Exception.Message) }

  if($args -and $args.Count -gt 0)
  {
    $types = $args[0] -as [type[]]
    if(-not $types) { $types = [type[]] $args }

    if($genericType = [type] ($type + '`' + $types.Count))
    {
      $genericType.MakeGenericType($types)
    }
  }
  else
  {
    [type] $type
  }
}

Trapping RuntimeException allows us to repackage failed casts without the full ErrorRecord. We get a bit of usage flexibility by accepting our type arguments in either a single array or as the full $args array. With this function, all of the following will return valid types:

$a = Get-Type ([System.Collections.ArrayList])
$b = Get-Type System.Collections.Hashtable
$c = Get-Type System.Collections.Generic.List int
$d = Get-Type System.Collections.Generic.Dictionary int,string
$e = Get-Type System.Collections.Generic.Dictionary int $d

Corresponding to:

System.Collections.ArrayList
System.Collections.Hashtable
System.Collections.Generic.List`1[System.Int32]
System.Collections.Generic.Dictionary`2[System.Int32,System.String]
System.Collections.Generic.Dictionary`2[System.Int32,System.Collections.Generic.Dictionary`2[System.Int32,System.String]]

Having delegated the responsibility of resolving type references, we can update Lee Holmes‘s New-GenericObject:

function New-GenericObject(
    $type = $(throw "Please specify a type"),
    [object[]] $typeParameters = $null,
    [object[]] $constructorParameters = @()
    )
{
    $closedType = (Get-Type $type $typeParameters)
    ,[Activator]::CreateInstance($closedType, $constructorParameters)
}

I’ve included a few extra tweaks:

  1. I replaced [string] $typeName with [object] $type and changed $typeParameters from [string[]] to [object[]], specifically to accept [type] values if they are provided.
  2. If $typeParameters is $null, I assume we were passed a closed type; if not, Get-Type fails accordingly.
  3. $constructorParameters needs a default value to get around this exception:

    Exception calling “CreateInstance” with “2” argument(s): “Ambiguous match found.”

Cross-Assembly Generics

A user on Stack Overflow found an interesting quirk when trying to instantiate a generic SortedDictionary in PowerShell. The problem is easy to reproduce:

PS 1> $sdt = Get-Type System.Collections.Generic.SortedDictionary string,string
PS 2> $sdt.FullName
System.Collections.Generic.SortedDictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToke
n=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
PS 3> $sd = New-Object $sdt
New-Object : Cannot find type [System.Collections.Generic.SortedDictionary`2[System.String,System.String]]: make sure t
he assembly containing this type is loaded.
At line:1 char:17
+ $sd = New-Object  <<<< $sdt

Yet the type works fine with New-GenericObject:

PS 4> $sd = New-GenericObject $sdt
PS 5> $sd.GetType().FullName
System.Collections.Generic.SortedDictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToke
n=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

As the answer on Stack Overflow points out, SortedDictionary and System.String live in different assemblies (System.dll and mscorlib.dll, respectively), which apparently confuses the PowerShell type converter. And because New-Object expects -typeName to be a string, it automatically casts our [type] object (which has the necessary assembly information) back to [string]:

PS 6> [string] $sdt
System.Collections.Generic.SortedDictionary`2[System.String,System.String]

If we pass in the type’s FullName instead, everything works as expected:

PS 7> $sd2 = New-Object $sdt.FullName
PS 8> $sd2.GetType().FullName
System.Collections.Generic.SortedDictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToke
n=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

Note that this behavior extends to all assemblies, not just within the System namespace:

PS 11> $l = New-Object (Get-Type System.Collections.Generic.List ([Microsoft.SharePoint.SPWeb]))
New-Object : Cannot find type [System.Collections.Generic.List`1[Microsoft.SharePoint.SPWeb]]: make sure the assembly c
ontaining this type is loaded.
At line:1 char:16
+ $l = New-Object  <<<< (Get-Type System.Collections.Generic.List ([Microsoft.SharePoint.SPWeb]))
PS 12> $l = New-Object (Get-Type System.Collections.Generic.List ([Microsoft.SharePoint.SPWeb])).FullName
PS 13> $l.GetType().FullName
System.Collections.Generic.List`1[[Microsoft.SharePoint.SPWeb, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,
 PublicKeyToken=71e9bce111e9429c]]

If someone on the PowerShell team reads this, it would be awesome if New-Object had a parameter set that accepted a [type] instead of a [string]. Thanks in advance!

Hiding Wiki Library Custom Columns

I was intrigued by Phil Wicklund‘s post on removing custom columns from wiki pages until I saw that his solution requires modifying the underlying template file (DocumentTemplates\wkpstd.aspx) and will affect every wiki on the farm. Fortunately, the control he suggests deleting (ListFieldIterator) respects fields’ ShowInDisplayForm property, which can be set in a number of ways:

  • Feature: Attribute of Field or FieldRef elements in a site column, content type or list template
  • Feature receiver:
    var web = properties.Feature.Parent as SPWeb;
    var field = web.Lists["MyWiki"].Fields["HideMe"];
    field.ShowInDisplayForm = false;
    field.Update();
  • PowerShell:
    $web = Get-SPWeb $url
    $f = $web.Lists['MyWiki'].Fields['HideMe']
    $f.ShowInDisplayForm = $false
    $f.Update()
    $web.Site.Dispose()
  • Sharad Kumar‘s Form Settings editor (CodePlex)

Any of these should be preferred to modifying the hive and breaking supportability.

PowerShell Coalesce and PowerShellASP Query String Parameters

I had fun initially with PowerShellASP, but its sealed classes prevented me from doing much useful with it in SharePoint. I really need to take a look at Andre de Cavaignac‘s PowerShellPages project, though the GPL license is a bit restrictive for my taste. Maybe I’ll just build my own…

Anyway, I actually had a practical use for PoShASP today, and in working with it I decided there needed to be a better way to handle QueryString values. The resulting function is fairly straight-forward, and supports typed and optional parameters:

<% QSParam w x,int,10 y,double z,zzz %>
w = <%= $w %>
x = <%= $x %>
y = <%= $y %>
z = <%= $z %>

With no query string, we get the following:

w =
x = 10
y =
z = zzz

However, if I use a query string like w=42&x=oops&y=3.14&z=pi, the valid values fall through:

w = 42
x = 10
y = 3.14
z = pi

Since ‘oops’ isn’t an [int], we get the default. But first…

PowerShell Coalesce

One of the most underused C# operators is the coalesce operator (??), which evaluates to the right-hand value if the left-hand value is null. PowerShell doesn’t include such an operator (to my knowledge), but we can certainly build a function to do the job. I found an implementation contributed anonymously in the comments here:

function Coalesce-Args {
    ([object[]]($args | ?{$_}) + ,$null)[0]
}

Which can be simplified to this:

function Coalesce-Args {
  (@($args | ?{$_}) + $null)[0]
}
Set-Alias ?? Coalesce-Args

The array subexpression @( ... ) always returns an array, to which we append $null in case the array is empty. Taking the first element yields the desired result. I also define an alias (??) for an easily remembered shorthand, though the usage is different from the binary C# operator. With our new function, my original QueryString pattern:

if( !($n = $Request.QueryString["n"] -as [int]) ) { $n = 10 }

Can be rewritten as:

$n = ?? ($Request.QueryString["n"] -as [int]) 10

QSParam Function

Now that we have Coalesce-Args, we can implement QSParam:

function QSParam {
  $QueryString = $Request.QueryString
  $args | ForEach-Object {
    $arg = @($_)
    $varName = $arg[0];
    $varType = $arg[1] -as [type];

    $qsValue = $QueryString[$varName]
    if ($varType -eq $null) {
      $varValue = Coalesce-Args $qsValue $arg[2] $arg[1]
    } else {
      $varValue = Coalesce-Args ($qsValue -as $varType) `
                                ($arg[2] -as $varType)
    }

    Set-Variable $varName $varValue -Scope script
  }
}

If  PowerShell can’t cast the second value as a type, we use it as a default value. If a third value is specified, that takes priority over the second value (which is how you can specify a default value that would otherwise be confused for a type: QSParam rhymesWithSquid,$null,guid). Finally, we assign the result to a script-scoped variable so we don’t have to dot source the function call.

And in case you don’t follow the PowerShell team blog (or missed the post), Channel 9’s Expert-to-Expert with Erik Meijer and Jeffrey Snover is definitely worth watching if you’re at all interested in the language internals and other geeky stuff. Jeffrey’s enthusiasm for the language is infectious.

PowerShell Dispose-All

With all the talk of Dispose safety in object model code, it’s easy to forget that those same objects need to be cleaned up in PowerShell as well. Sure, SharePoint will clean everything up when the thread ends, but my PowerShell doesn’t get closed all that often, leaving open SPRequests for extended periods of time. To combat that, I wrote a quick function that will clean up all my IDisposable variables for me:

function Dispose-All {
    Get-Variable -exclude Runspace |
        Where-Object {
            $_.Value -is [System.IDisposable]
        } |
        Foreach-Object {
            $_.Value.Dispose()
            Remove-Variable $_.Name
        }
}

For some reason, I don’t think it would be a good idea to Dispose() the current Runspace… (Edit: Apparently I define $Runspace in a long-forgotten part of my profile script, so don’t be worried if you don’t have that defined.)

Of course this isn’t foolproof, as variables can be overwritten and not all IDisposables will have been assigned to variables in the first place, but you could certainly adapt your shell behavior to make the most out of a catch-all cleanup like this.

Site Collection-Safe CQWP XSL Links

A common complaint about styling the ContentByQueryWebPart is that the XslLink properties, used to reference custom XSL files that can be deployed via features (the best practice), don’t support site collection-relative links. This means that one would need to provide a separate .webpart file for each site collection that doesn’t live at the server root. Other common workarounds are to “get creative” with relative URLs or add the Web Part with code and assign a server-relative URL. Fortunately, it’s relatively easy to extend CQWP to adjust any server-relative URL to accommodate non-root site collections:

public class SiteSafeContentByQueryWebPart : ContentByQueryWebPart
{
    protected override void OnLoad(EventArgs e)
    {
        MainXslLink = MakeServerRelativeUrl(MainXslLink);
        HeaderXslLink = MakeServerRelativeUrl(HeaderXslLink);
        ItemXslLink = MakeServerRelativeUrl(ItemXslLink);
    }

    private string MakeServerRelativeUrl(string url)
    {
        if (string.IsNullOrEmpty(url) || url[0] != '/')
            return url;

        var u = SPUtility.SiteRelativeUrlPrefix + url.TrimStart('/');
        return SPUtility.GetServerRelativeUrlFromPrefixedUrl(u);
    }
}

There are already several examples online of how to deploy and use an extended CQWP, so I won’t bother to repeat the process here. As long as the XslLink starts with “/”, it will be translated for the current site collection:

<properties>
  <property name="Title" type="string">SiteSafeContentByQueryWebPart</property>
  <property name="MainXslLink" type="string">/Style Library/CustomContentQueryMain.xsl</property>
  <property name="HeaderXslLink" type="string">/Style Library/CustomHeader.xsl</property>
  <property name="ItemXslLink" type="string">/Style Library/CustomItemStyle.xsl</property>
</properties>

If there’s demand for a barebones SCSCQWP, I can post one, but I would rather see some of the more advanced versions just integrate this code.

Implementing LINQ Select for SPWebCollection

If I’m going to suggest that others use yield return, I suppose I should take my own advice. In particular, my original implementation of SPWebCollection.Select can be improved using the same techniques I used for Where:

public static IEnumerable<TResult> Select<TResult>(this SPWebCollection webs,
                                                   Func<SPWeb, TResult> selector)
{
    foreach (SPWeb web in webs)
    {
        TResult ret = selector(web);
        web.Dispose();
        yield return ret;
    }
}

And we might as well support a filtered Select as well:

public static IEnumerable<TResult> Select<TResult>(this SPWebCollection webs,
                                                   Func<SPWeb, TResult> selector,
                                                   Func<SPWeb, bool> predicate)
{
    foreach (SPWeb web in webs.Where(predicate))
    {
        TResult ret = selector(web);
        web.Dispose();
        yield return ret;
    }
}

Implementations using Cast<T>() are left as an exercise for the reader.

The function itself isn’t particularly interesting, but I did stumble on something I found rather surprising. When I first wrote up my function, I typed the following:

public static IEnumerable<TResult> Select<TResult>(this SPWebCollection webs,
                                                   Func<SPWeb, TResult> selector)
{
    foreach (SPWeb web in webs)
    {
        yield return selector(web);
        web.Dispose();
    }
}

I’m really not sure why I typed it that way…obviously you can’t keep going after you return, right? Well it turns out you can. The generated class just waits to call Dispose() until the next call to MoveNext(), effectively picking up where it left off. Here’s what that looks like in Reflector:

switch (this.<>1__state)
{
    case 0:
        this.<>1__state = -1;
        this.<>7__wrap1a = this.webs.GetEnumerator();
        this.<>1__state = 1;
        while (this.<>7__wrap1a.MoveNext())
        {
            this.<web>5__19 = (SPWeb) this.<>7__wrap1a.Current;
            this.<>2__current = this.selector(this.<web>5__19);
            this.<>1__state = 2;
            return true;
        Label_0080:
            this.<>1__state = 1;
            this.<web>5__19.Dispose();
        }
        this.<>m__Finally1c();
        break;

    case 2:
        goto Label_0080;
}
return false;

As Select will almost always be used in a foreach, with back-to-back calls of MoveNext(), the distinction is mostly academic. Still, I prefer to know that the web will be disposed immediately after the selector is done with it.

Implementing LINQ Where for SharePoint

First, a huge thanks to Waldek Mastykarz for running with my suggestion to run some performance tests on list item filtering. In short, CAML wins by a factor of 300, which I expect would be even more pronounced on larger lists and under load.

In his test, Waldek implements Where() as follows:

public static IEnumerable<SPListItem> Where(this SPListItemCollection items,
                                            Func<SPListItem, bool> predicate)
{
    List<SPListItem> list = new List<SPListItem>();
    foreach (SPListItem item in items)
        if (predicate(item))
            list.Add(item);
    return list;
}

This works as expected, but allocates a secondary data structure to store the filtered items. The preferred approach is to use the yield return syntax:

public static IEnumerable<SPListItem> Where(this SPListItemCollection items,
                                            Func<SPListItem, bool> predicate)
{
    foreach (SPListItem item in items)
        if (predicate(item))
            yield return item;
}

The actual IL this generates is too complex to go into here, but I highly suggest checking it out in Reflector. In short, the compiler creates a private class that provides a filtered enumerator without actually building an intermediate data structure, instead filtering in MoveNext(). Using yield also defers execution until the collection is actually enumerated, though I can’t think of a SharePoint example where this would actually matter.

Another alternative, which also defers execution, is to leverage LINQ’s Cast<T>() operator and LINQ’s IEnumerable<T>.Where():

public static IEnumerable<SPListItem> Where(this SPListItemCollection items,
                                            Func<SPListItem, bool> predicate)
{
    return items.Cast<SPListItem>().Where(predicate);
}

I imagine the compiler would optimize the yield-generated class in much the same way it optimizes LINQ’s internal implementation, but I will leave that research for a later date. It would also be interesting to compare the performance between the different implementations, though in a SharePoint context I expect the difference would be insignificant compared with the more expensive operations needed to retrieve the data.

The Problem with IDisposable

In my previous post, I suggested a Dispose-safe implementation of SPWebCollection.ForEach(), which Waldek leveraged for his Where implementation. Presumably because he was concerned about leaking SPWebs, his Where() implementation just returns a list of the web IDs. While avoiding leaks is smart, an ID isn’t nearly as useful as the full SPWeb and opening a new SPWeb from the ID is an expensive operation. What if I wanted a filtered enumeration of the SPWeb objects?

Well if we use one of the patterns described above, we should be safe if we call Dispose() for each when we’re done, right? I probably wouldn’t bother asking if there weren’t a catch, so I’ll answer my question with another question: When would Dispose() be called on the webs for which the predicate is false? It wouldn’t! To prevent these leaks, we need to be a bit more sophisticated:

public static IEnumerable<SPWeb> Where(this SPWebCollection webs,
                                       Func<SPWeb, bool> predicate)
{
    foreach (SPWeb web in webs)
        if (predicate(web))
            yield return web;
        else
            web.Dispose();
}

Or using Cast<T>():

public static IEnumerable<SPWeb> Where(this SPWebCollection webs,
                                        Func<SPWeb, bool> predicate)
{
    return webs.Cast<SPWeb>().Where(w =>
    {
        bool r = predicate(w);
        if (!r)
            w.Dispose();
        return r;
    });
}

Again, a detailed IL investigation would likely prove one preferable to the other, but the principle is the same.

Finally, since caller-dependent disposal is unreliable and delegates are fun, I figure we could use a Dispose-safe filtered ForEach:

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

Which would let us do something like this to print all publishing sites in a collection:

site.AllWebs.ForEach(
    w => { Console.WriteLine(w.Title); },
    w => { return w.Features.Contains(FeatureIds.OfficePublishingWeb); }
);

That is, if we define yet another useful, if simple, extension method:

public static bool Contains(this SPFeatureCollection features, Guid featureID)
{
    return features[featureID] != null;
}

And a final note: why did the LINQ team use Func<T, bool> instead of Predicate<T>, which has existed since .NET 2.0?

STSDev Theme Solution Generator Update

It took a bit longer than expected, but I have finally revisited my STSDEV Theme Solution Generator. I just posted a new release on CodePlex—STSDEV 1.3b with Theme Generator—and the full source is now available in my CodePlex repository. The big theme-related addition in this version is an option to automatically apply Heather Solomon’s @import technique, as a simple checkbox for now. There’s a lot more it could do for us (move other theme assets into LAYOUTS, fix image references in the CSS, etc), but it’s a start. I also fixed a TemplateFiles bug in STSDEV that has annoyed me for a while.

I’m curious what the community thinks I should do with the Generator. If STSDev were more mature, I would just release a Generator DLL for a specific release, but for now it’s still relatively volatile and the APIs I’m using will probably (hopefully, in some cases) change. There’s also the issue of STSDev vs. STSDev 2008. And maybe more importantly, is this even a tool worth maintaining as more than a proof-of-concept? I’m open to your thoughts/suggestions.

Posted in Branding, SharePoint, Tools. Tags: , , . Comments Off on STSDev Theme Solution Generator Update