In ScriptBlock.InvokeAsDelegateHelper() we pass the result of the execution through GetRawResult() which results in a scalar value if the return pipe contains one object. The type conversion on the return value can't deal with the scalar in some case resulting in a run time error. Making this work is important for methods like [Linq.Enumerable]::SelectMany() which takes a delegate [System.Func[TSource,int,System.Collections.Generic.IEnumerable[TResult]]]
([func[system.collections.generic.ienumerable[object]]] { 1 }).Invoke()
1
Exception calling "Invoke" with "0" argument(s): "Cannot convert the "1" value of type "System.Int32" to type "System.Collections.Generic.IEnumerable`1[System.Object]"."
At line:1 char:1
+ ([func[system.collections.generic.ienumerable[object]]] { 1 }).Invoke ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : PSInvalidCastException
PSCore (1:102) > $PSVersionTable
Name Value
---- -----
PSVersion 6.1.0-preview.2
PSEdition Core
GitCommitId v6.1.0-preview.2
OS Microsoft Windows 10.0.17134
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
I think part of the problem is there's no conversion path for T to IEnumerable<T>. I've had similar issues inheriting classes with abstract methods that return an IEnumerable<>.
A shorter repro is that this doesn't work:
[System.Collections.Generic.IEnumerable[object]]1
But this does
[System.Collections.Generic.IEnumerable[object]][object[]]1
In classes the workaround is to force the return value to something already inheriting IEnumerable<> like above, but that doesn't seem to work here.
@SeeminglyScience In this specific case, the code returns a collection, checks to see if it's length 1 then turns it into a scalar. If the cast worked, we'd then take that scalar and turn it back into a collection of 1 element which is not desirable. More generally, you can't cast a scalar to IEnumerable[T] because converting from a scalar to a collection requires creating an instance of the collection and you can't create an instance of IEnumerable[T], only instances of concrete types that implement IEnumerable[T}. Now we could add a special case (hack but one of many) in the type converter logic that picked a specific concrete type (probably array of T) to do the conversion but I'm not sure it's desirable. Thoughts?
@BrucePay I think the pattern is common enough that it makes sense. Maybe even the other collection-like interfaces that T[] would implement like IList<T>, ICollection<T>, etc.
I don't think it would be desirable to always force the result to be an array (you wouldn't want to cast a LINQ method result unnecessarily) but if the target object is not already of the specified cast type, then forcing to T[] makes a lot of sense to me.
Isn't this... expected behavior?
([func[system.collections.generic.ienumerable[object]]] { Write-Output @(1) -NoEnumerate }).Invoke() works fine
@IISResetMe it isn't surprising behavior, but I'd argue that it's not desired behavior.
The example given only works because the default array return type object[] is already IEnumerable<object> so it doesn't need to do anything extra.
For example:
# Fails
([func[system.collections.generic.ienumerable[int]]] { Write-Output @(1) -NoEnumerate }).Invoke()
# Works
([func[int[]]] { Write-Output @(1) -NoEnumerate }).Invoke()
# Fails
([func[System.Collections.Generic.IEnumerable[object]]] { ,[int[]](1) }).Invoke()
There's a few interfaces that are often used as return types in abstract classes or delegates. Some extra conversion paths would be a nice bit of quality of life.
Most helpful comment
@IISResetMe it isn't surprising behavior, but I'd argue that it's not desired behavior.
The example given only works because the default array return type
object[]is alreadyIEnumerable<object>so it doesn't need to do anything extra.For example:
There's a few interfaces that are often used as return types in abstract classes or delegates. Some extra conversion paths would be a nice bit of quality of life.