4

So I'm trying to figure out if some any random IEnumerable passed to my method has been grouped. The idea being that I'm going to handle grouped data differently. My problem is the IGrouping<> is a generic interface that doesn't implement a non-generic interface, and for whatever reason I can't test for it with is statements.

To test this out here is my sample code:

        var dict = new Dictionary<int, string>() {
            {1, "Prime" },
            {2, "Prime" },
            {3, "Prime" },
            {4, "Not" },
            {5, "Prime" },
            {6, "Not" },
            {7, "Prime" },
            {8, "Not" },
            {9, "Not" },
            {10, "Not" },
            {11, "Prime" }
        };
        var grouped = from d in dict
                      group d by d.Value into gd
                      select gd;
        Debug.WriteLine("IEnumerable<IGrouping<string, KeyValuePair<int, string>>>: {0}", grouped is IEnumerable<IGrouping<string, KeyValuePair<int, string>>>);
        Debug.WriteLine("IEnumerable<IGrouping<object, KeyValuePair<int, string>>>: {0}", grouped is IEnumerable<IGrouping<object, KeyValuePair<int, string>>>);
        Debug.WriteLine("IEnumerable<IGrouping<object, KeyValuePair<object, object>>: {0}", grouped is IEnumerable<IGrouping<object, object>>);
        Debug.WriteLine("IEnumerable<IGrouping<object, object>: {0}", grouped is IEnumerable<IGrouping<object, object>>);
        Debug.WriteLine("IEnumerable<object>: {0}", grouped is IEnumerable<object>);

Output is as follows:

IEnumerable<IGrouping<string, KeyValuePair<int, string>>>: True
IEnumerable<IGrouping<object, KeyValuePair<int, string>>>: True
IEnumerable<IGrouping<object, KeyValuePair<object, object>>: False
IEnumerable<IGrouping<object, object>: False
IEnumerable<object>: True

So it seems that unless I know the type of the values being grouped, I can't test to see if it's grouped? Is this a bug in the .Net Framework?

How are the results above even possible?


EDIT: Jon Skeet's answer was dead on... As a courtesy to anyone else that find themselves here with the same difficulty I had, the following code did the job of checking to see if an IEnumerable was grouped or not (based off of Mr. Skeet's fancy-ass idea of using the dynamic keyword):

    public static bool IsGrouped (dynamic test) {
        return CheckGrouping(test);
    }
    static bool CheckGrouping(object o ) {
        return false;
    }
    static bool CheckGrouping<TKey, TValue>(IEnumerable<IGrouping<TKey, TValue>> grouping) {
        return true;
    }

Assuming the code is the same as above:

IsGrouped(dict); //false
IsGrouped(grouped); //true
Ben Lesh
  • 107,825
  • 47
  • 247
  • 232

1 Answers1

10

The results are not just possible but expected under .NET 4 - in .NET 3.5, the final result would be False due to the lack of generic variance.

While IGrouping<out TKey, out TValue> is covariant in both TKey and TValue, KeyValuePair<Tkey, TValue> is a struct, and therefore cannot be used in a covariant manner.

So an IGrouping<string, string> is an IGrouping<object, object>, but an IGrouping<string, KeyValuePair<int, string>> is not.

This is not a bug in the framework. As for how to find out whether something implements IGrouping<TKey, TValue> for some TKey, TValue pair, you could either work quite hard yourself, or using dynamic to try to help:

// Code as before...
dynamic dyn = grouped;
ShowGrouping(dyn);

private static void ShowGrouping<TKey, TValue>
   (IEnumerable<IGrouping<TKey, TValue>> grouping)
{
    Console.WriteLine("TKey = {0}, TValue = {1}", typeof(TKey), typeof(TValue));
}

private static void ShowGrouping(object notGrouped)
{
    Console.WriteLine("Not a grouping");
}

Note that this certainly isn't foolproof - but it may be good enough for what you need.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks! The first thing I attempted was actually an extension method for IEnumerable for grouped or not grouped, which didn't really work. I didn't think to use the dynamic keyword. I realized it was an issue with KVP, strings, and ints being structs while I was playing with it and talking with a coworker. – Ben Lesh Feb 02 '12 at 21:08
  • 1
    @blesh: `string` isn't a struct, it's a class. – Jon Skeet Feb 02 '12 at 21:14
  • Thank you again! I've edited my question to include how I solved my particular issue using your super-clever use of the dynamic keyword. It's honestly the first time I've had a reason to use it, oddly enough. – Ben Lesh Feb 02 '12 at 21:24
  • +1 Mr. Skeet... you're correct that String is not a struct. That was me typing in a hurry more than anything. – Ben Lesh Feb 02 '12 at 21:27
  • Is there any reason why you could not use `if (typeof(grouped).FullName.StartsWith("System.Linq.GroupedEnumerable")) {...}`? –  May 16 '14 at 03:19
  • 1
    @StephenMuecke: I assume you mean `grouped.GetType()`... but basically this is brittle: 1) it assumes there are no other types starting with that name; 2) it assumes an *implementation detail* that grouping will use `GroupedEnumerable`, which isn't even a public type. I'm generally wary of code that uses type names instead of the types themselves to determine anything. – Jon Skeet May 16 '14 at 05:45