Grouping into more groups in one iteration
public static IDictionary&lt;string, Dictionary&lt;object, HashSet&lt;T&gt;&gt;&gt; MultiGroupBy&lt;T&gt;(this IEnumerable&lt;T&gt; source, params (string Label, Func&lt;T, object&gt; Getter)[] groupers)



I don't understand the mixture of interfaces (IDictionary) and implementations (Dictionary, HashSet), nor the mixture of generics (&lt;T&gt;) and non-generics (object). Why is it not

public static IDictionary&lt;string, IDictionary&lt;K, ISet&lt;T&gt;&gt;&gt; MultiGroupBy&lt;T, K&gt;(this IEnumerable&lt;T&gt; source, params (string Label, Func&lt;T, K&gt; Getter)[] groupers)


?




  IDictionary&lt;string, Dictionary&lt;object, HashSet&lt;T&gt;&gt;&gt; results = new Dictionary&lt;string, Dictionary&lt;object, HashSet&lt;T&gt;&gt;&gt;();

  using (var enumer = source.GetEnumerator())
  {
    while (enumer.MoveNext())
    {
      foreach ((var label, var func) in groupers)
      {
        if (!results.TryGetValue(label, out var dict))
        {
          dict = new Dictionary&lt;object, HashSet&lt;T&gt;&gt;();
          results[label] = dict;
        }

        ...



I'm not quite sure why you want to return an empty dictionary if the source is empty. As a caller of your library, I'd probably rather get a dictionary mapping the grouper names to empty dictionaries.

That also simplifies the initialisation:

  var results = groupers.ToDictionary(grouper =&gt; grouper.Item1, _ =&gt; new Dictionary&lt;object, HashSet&lt;T&gt;&gt;());





  using (var enumer = source.GetEnumerator())
  {
    while (enumer.MoveNext())
    {
       ...
    }

  }



KISS. foreach is much kinder on the maintenance programmer, who doesn't have to check for correct usage patterns of the unsugared API. Using MoveNext() / Current for speed is the epitome of premature optimisation unless benchmarking shows that it's a bottleneck, in which case there should be a comment explaining the bottleneck to justify the more complex code.

Moreover, if this is a bottleneck then it seems likely that the dictionary lookups in results for every single element in the source will be slower than the overhead of foreach, so you could start by replacing results with a List&lt;(string Label, Func&lt;T, K&gt; Getter, IDictionary&lt;K, ISet&lt;T&gt;&gt; Groups)&gt; and just convert it to a dictionary after the loop.




      foreach ((var label, var func) in groupers)



var (label, func) saves the repetition.



After my proposed refactors and some minor tidying of whitespace, I get

public static IDictionary&lt;string, IDictionary&lt;K, ISet&lt;T&gt;&gt;&gt; MultiGroupBy&lt;T, K&gt;(this IEnumerable&lt;T&gt; source, params (string Label, Func&lt;T, K&gt; Getter)[] groupers)
{
  if (source == null) throw new ArgumentNullException(nameof(source));
  if (groupers == null) throw new ArgumentNullException(nameof(groupers));

  var results = groupers.ToDictionary(grouper =&gt; grouper.Item1, _ =&gt; (IDictionary&lt;K, ISet&lt;T&gt;&gt;)new Dictionary&lt;K, ISet&lt;T&gt;&gt;());

  foreach (var elt in source)
  {
    foreach (var (label, func) in groupers)
    {
      var dict = results[label];
      var key = func(elt);
      if (!dict.TryGetValue(key, out var set))
      {
        set = new HashSet&lt;T&gt;();
        dict[key] = set;
      }

      set.Add(elt);
    }
  }

  return results;
}