Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Every array of enum implements IEnumerable<EveryOtherEnum>. How do I work around this?

Tags:

c#

.net

types

enums

It appears that, in .NET, "array of enum" is not a strongly-typed concept. MyEnum[] is considered to implement not just IEnumerable<MyEnum>, but also IEnumerable<YourEnum>. (I didn't believe it at first either.)

// Returns true:
typeof(IEnumerable<DayOfWeek>).IsAssignableFrom(typeof(AttributeTargets[]))

// Outputs "3":
var listOfLists = new List<object> {
    new[] { AttributeTargets.All },
    new[] { ConsoleColor.Blue },
    new[] { PlatformID.Xbox, PlatformID.MacOSX }
};
Console.WriteLine(listOfLists.OfType<IEnumerable<DayOfWeek>>().Count());

So when I go looking through a list for everything that implements IEnumerable<T>, I'm getting the T[]s and the List<T>s and the iterator-generated IEnumerable<T>s, but I'm also getting the SomethingElse[]s that I do not want.

What's the easiest way to find out whether a given Type (as with IsAssignableFrom above) or a given instance (as with OfType<T> above) really and truly implements, say, IEnumerable<DayOfWeek>?


I gave Mr. Skeet the green checkmark, but once I got his code into Visual Studio, ReSharper suggested a more concise version. Use whichever version you prefer.

public static IEnumerable<IEnumerable<T>> OfSequenceType<T>
    (this IEnumerable source) where T : struct
{
    return from sequence in source.OfType<IEnumerable<T>>()
           let type = sequence.GetType()
           where !type.IsArray || type.GetElementType() == typeof (T)
           select sequence;
}
like image 295
Joe White Avatar asked Oct 19 '11 22:10

Joe White


2 Answers

I believe this is basically due to section 8.7 of ECMA 335.

Basically we're looking at the assignable-to relationship between the two array-of-enum types. As far as I can tell, 8.7.2 is applicable:

A location type T is compatible-with a location type U if and only if one of the following holds.

  1. T and U are not managed pointer types and T is compatible-with U according to the definition in §8.7.1.

So we look to 8.7.1 and find bullet 5:

T is a zero-based rank-1 array V[], and U is a zero-based rank-1 array W[], and V is array-element-compatible-with W.

So now we're interested in whether the two enum types have an array-element-compatible-with relationship... That then leads to:

A signature type T is array-element-compatible-with a signature type U if and only if T has underlying type V and U has underlying type W and either:

  1. V is compatible-with W; or
  2. V and W have the same reduced type.

Now the underlying type of an enum is defined by this:

The underlying type of a type T is the following:

  1. If T is an enumeration type, then its underlying type is the underlying type declared in the enumeration‘s definition.
  2. [Irrelevant to us]

So in the case of, say:

enum Foo : int {}
enum Bar : int {}

the underlying types of both are int.

Now we can go back to our array-element-compatible-with definition, and see that both V and W are int. A type is compatible-with itself due to the first bullet of 8.7.1:

A signature type T is compatible-with a signature type U if and only if at least one of the following holds.

  1. T is identical to U.

Therefore, the arrays are compatible.

They're also compatible with an array of the underlying type itself:

enum Foo {}
...
object x = new int[10];
Foo[] y = (Foo[]) x; // No exception

Note that x has to be declared as object here to persuade the C# compiler that this might be legal - otherwise it follows the rules of the C# language, which aren't quite so forgiving...


Now as for your second question:

What's the easiest way to find out whether a given Type (as with IsAssignableFrom above) or a given instance (as with OfType above) really and truly implements, say, IEnumerable?

You could just special case arrays, as they behave a little oddly. Frankly that's probably the easiest approach. I tried using Type.GetInterfaceMap but that gives a problem too:

Unhandled Exception: System.ArgumentException: Interface maps for generic interf aces on arrays cannot be retrived.

(Yes, the typo at the end is really in the error message. Can't be bothered to raise a Connect issue for that though...)

I strongly suspect that special-casing is the way forward... for example, assuming that you know you're dealing with a value type (covariance of reference type arrays is a separate matter...)

public static IEnumerable<IEnumerable<T>> OfSequenceType<T>
    (this IEnumerable source) where T : struct
{
    // Nullity check elided...
    foreach (object x in source)
    {
        IEnumerable<T> sequence = x as IEnumerable<T>;
        if (sequence == null)
        {
            continue;
        }
        // Work around odd value type array variance
        Type type = sequence.GetType();
        if (type.IsArray && type.GetElementType() != typeof(T))
        {
            continue;
        }
        yield return sequence;
    }
}
like image 173
Jon Skeet Avatar answered Sep 28 '22 01:09

Jon Skeet


I tried this:

var listOfLists = new List<object> {
    new[] { AttributeTargets.All },
    new[] { ConsoleColor.Blue },
    new[] { PlatformID.Xbox, PlatformID.MacOSX },
    new[] { DayOfWeek.Friday, DayOfWeek.Saturday }
};
Console.WriteLine(listOfLists.Where(obj => obj.GetType() == typeof(DayOfWeek[])).Count());

I got 1 as a result. I'm still searching to see how you can use IEnumerable< DayOfWeek > in the query

like image 21
GianT971 Avatar answered Sep 28 '22 02:09

GianT971