Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting if an IEnumerable<T> is Grouped, I'm getting some crazy results when checking the types

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
like image 492
Ben Lesh Avatar asked Dec 28 '22 06:12

Ben Lesh


1 Answers

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.

like image 179
Jon Skeet Avatar answered Apr 26 '23 14:04

Jon Skeet