Why does C# allow matching a generic array against patterns of all types?

Viewed 106

I have an array of a generic type T, and I wanted to check whether the length was 0. However, I made a typo, and instead of Array.Length is 0 I typed Array is 0. When I noticed the typo, I wondered why it didn't give me a compile-time error, which does happen when I do it with an array of any concrete type like string, int, object, or even dynamic. Obviously, no matter what the underlying type of the array is, an array cannot be the int value zero, so this is questionable. I tried this with various other int literals, string literals, I even tried less-than and greater-than patterns and all of that worked. I tried changing the dimensions of array or the nullabillity (since my original example was a T[,]?), I even tried adding some constraints to the T generic type, but none of that changed anything.

Are there some special cases where those patterns could actually match (even if I can't imagine that since they're still just arrays), or is it a compiler bug? If it's the latter, what causes it?


Since somebody asked for the code, this is a simple test that gave me the same result: (assuming T is any generic type)

T[] array = new T[42];
// You can assign anything here
// Only thing that matters is that the variable is a T[] with any number of dimensions
if (array is 0 or "test") Console.WriteLine("This shouldn't even compile")
1 Answers

I think it's the result of the following specification change in C# 7.1:

The specification for the existing C# as operator permits there to be no conversion between the type of the operand and the specified type when either is an open type. However, in C# 7 the Type identifier pattern requires there be a conversion between the type of the input and the given type.

We propose to relax this and change expression is Type identifier, in addition to being permitted in the conditions when it is permitted in C# 7, to also be permitted when expression as Type would be allowed. Specifically, the new cases are cases where the type of the expression or the specified type is an open type.

Change itself:

Certain combinations of static type of the left-hand-side and the given type are considered incompatible and result in compile-time error. A value of static type E is said to be pattern compatible with the type T if there exists an identity conversion, an implicit reference conversion, a boxing conversion, an explicit reference conversion, or an unboxing conversion from E to T, or if either E or T is an open type. It is a compile-time error if an expression of type E is not pattern compatible with the type in a type pattern that it is matched with.

As a result of this change, if left side of is is open generic type (which is the case in your question) - then it's considered what they call "pattern compatible" with whatever is on the right.

However, specific generic type such as string[] violates the other restrictions mentioned in specification, as so is not "pattern compatible" with int, so it causes compile error.

T[] array = new T[10];
bool test = array is int; // that's fine with this proposal, since left sid e is open type

Now documentation about constant pattern you are using (is 0) says:

When the input value is not an open type, the constant expression is implicitly converted to the type of the matched expression; if the type of the input value is not pattern-compatible with the type of the constant expression, the pattern-matching operation is an error.

As we figured out, open-typed (T[] where T is not resolved) generic array is pattern compatible with int, so compiler is fine with your expression, while string[] (or any other closed generic array) is not pattern compatible and causes the error.

You can see Motivation section at the first link to see why they decided to change specification in this way.

Related