Description
The ArgumentConverter.CreateEnumerable method calls Type.IsAssignableFrom here:
These IsAssignable calls appear to be written the wrong way around, but the code works in practice anyway.
For example, if typeof(IEnumerable<>).IsAssignableFrom(enumerable)
returns true, then CreateEnumerable calls CreateArray. For that, enumerable.IsAssignableFrom(typeof(IEnumerable<>))
would seem more appropriate, because if the application defines some interface IDerivedEnumerable<out T> : IEnumerable<T>
and attempts to use Argument<IDerivedEnumerable<string>>
, then IEnumerable<string>
is assignable from IDerivedEnumerable<string>
, but IDerivedEnumerable<string>
is not assignable from string[]
, and thus CreateEnumerable should not call CreateArray.
However, CreateEnumerable indeed does not call CreateArray in this scenario, because the target of the Type.IsAssignableFrom call is typeof(IEnumerable<>)
rather than typeof(IEnumerable<string>)
, and that apparently causes Type.IsAssignableFrom to return false unless the types match exactly:
Microsoft (R) Roslyn C# Compiler version 2.10.0.0
Loading context from 'CSharpInteractive.rsp'.
Type "#help" for more information.
> typeof(IEnumerable<>).IsAssignableFrom(typeof(IList<>))
false
> typeof(IList<>).IsAssignableFrom(typeof(IEnumerable<>))
false
> typeof(IEnumerable<object>).IsAssignableFrom(typeof(IList<object>))
true
> typeof(IList<object>).IsAssignableFrom(typeof(IEnumerable<object>))
false
> typeof(IEnumerable<object>).IsAssignableFrom(typeof(IList<>))
false
> typeof(IEnumerable<>).IsAssignableFrom(typeof(IList<object>))
false
> typeof(System.Collections.IEnumerable).IsAssignableFrom(typeof(IList<>))
true
> typeof(IEnumerable<>).IsAssignableFrom(typeof(IEnumerable<>))
true
I think, if the comparison were changed to enumerable == typeof(IEnumerable<>)
, and likewise for IList<>
and ICollection<>
, then that would have the same result as the current comparison, but would be easier for me to understand.