グループ化してセット演算子を適用する。 2

再考。さっきよりは良い感じ。
Distinct は IEnumerable<IGrouping> を受け取って、他のセット演算は IEnumerable<IGrouping> を返すのがポイント。

■グループ化セット演算

public static class LinqExtensions
{
  // グループ化したキーで重複除去 (sourceはグループ化済)
  public static IEnumerable<T> DistinctByKey<T, TKey>(this IEnumerable<IGrouping<TKey, T>> source)
  {
    var lookup = source.SelectMany(x => x.Select(y => new { key = x.Key, value = y })).ToLookup(x => x.key, x => x.value);
    return lookup.Select(x => x.First());
  }

  // グループ化したキーで和
  public static IEnumerable<IGrouping<TKey, T>> GroupUnion<T, TKey>(this IEnumerable<T> first, Func<T, TKey> keySelector, IEnumerable<T> second)
  {
    var g1 = first.GroupBy(keySelector);
    var g2 = second.GroupBy(keySelector);
    var unionedKeys = g1.Select(x => x.Key).Union(g2.Select(x => x.Key));
    return g1.Union(g2).Where(x => unionedKeys.Contains(x.Key));
  }

  // グループ化したキーで積
  public static IEnumerable<IGrouping<TKey, T>> GroupIntersect<T, TKey>(this IEnumerable<T> first, Func<T, TKey> keySelector, IEnumerable<T> second, bool onlyFirstSource = false)
  {
    var g1 = first.GroupBy(keySelector);
    var g2 = second.GroupBy(keySelector);
    var intersectedKeys = g1.Select(x => x.Key).Intersect(g2.Select(x => x.Key));
    var srcGroup = onlyFirstSource ? g1 : g1.Union(g2);
    return srcGroup.Where(x => intersectedKeys.Contains(x.Key));
  }

  // グループ化したキーで差
  public static IEnumerable<IGrouping<TKey, T>> GroupExcept<T, TKey>(this IEnumerable<T> first, Func<T, TKey> keySelector, IEnumerable<T> second)
  {
    var g1 = first.GroupBy(keySelector);
    var g2 = second.GroupBy(keySelector);
    var exceptedKeys = g1.Select(x => x.Key).Except(g2.Select(x => x.Key));
    return g1.Where(x => exceptedKeys.Contains(x.Key));
  }
}

■使い方

// a, b のシーケンスがある
var distincted = a.GroupBy(x => x.Item).DistinctByKey();
var excepted = a.GroupExcept(x => x.Item, b).DistinctByKey();