SPWeb.AssociatedGroups.Contains Lies


While working on SPExLib (several months ago), I revisited this post, which presented a functional approach to a solution Adam describes here. Both posts include logic to add an SPWeb group association, which most simply could look something like this:

SPGroup group = web.SiteGroups[groupName];
if (!web.AssociatedGroups.Contains(group))
{
web.AssociatedGroups.Add(group);
web.Update();
}

While testing on a few groups, I noticed that the Contains() call lies, always returning false. This behavior can also be verified with PowerShell:

PS > $w.AssociatedGroups | ?{ $_.Name -eq 'Designers' } | select Name

Name
----
Designers

PS > $g = $w.SiteGroups['Designers']
PS > $w.AssociatedGroups.Contains($g)
False

Of course, it’s not actually lying—it just doesn’t do what we expect. Behind the scenes, AssociatedGroups&nbsp; is implemented as a simple List<SPGroup> that is populated with group objects retrieved by IDs stored in the SPWeb‘s vti_associategroups property. The problem is that List<T>.Contains() uses EqualityComparer<T>.Default to find a suitable match, which defaults to reference equality for reference types like SPGroup that don’t implement IEquatable<T> or override Equals().

To get around this, SPExLib provides a few extension methods to make group collections and SPWeb.AssociatedGroups easier to work with and more closely obey the Principle of Least Surprise:

public static bool NameEquals(this SPGroup group, string name)
{
return string.Equals(group.Name, name, StringComparison.OrdinalIgnoreCase);
}

public static bool Contains(this SPGroupCollection groups, string name)
{
return groups.Any<SPGroup>(group => group.NameEquals(name));
}

public static bool HasGroupAssociation(this SPWeb web, string name)
{
return web.AssociatedGroups.Contains(name);
}

public static bool HasGroupAssociation(this SPWeb web, SPGroup group)
{
if (group == null)
throw new ArgumentNullException("group");
return web.HasGroupAssociation(group.Name);
}

public static void EnsureGroupAssociation(this SPWeb web, SPGroup group)
{
if (web.HasGroupAssociation(group))
web.AssociatedGroups.Add(group);
}

The code should be pretty self-explanatory. The name comparison logic in NameEquals()

is written to align with how SharePoint compares group names

internally, though they use their own implementation of case

insensitivity because the framework’s isn’t good enough. Or something

like that.

There should be two lessons here:

  1. Don’t assume methods that have a notion of equality, like Contains(), will behave like you expect.
  2. Use SPExLib and contribute other extensions and helpers you find useful. 🙂
Extension Methods on Types You Own?