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;
}
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.
- 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:
- V is compatible-with W; or
- 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:
- If T is an enumeration type, then its underlying type is the underlying type declared in the enumeration‘s definition.
- [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.
- 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;
}
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With