GeezerVison is showing github.com — A ValueFromRemainingArguments parameter by default invariably receives a [List[object]] collection rather than a scalar / PowerShell array ([object[]]), as appropriate · Issue #4625 · PowerShell/PowerShell
A ValueFromRemainingArguments parameter by default invariably receives a [List[object]] collection rather than a scalar / PowerShell array ([object[]]), as appropriate #4625DescriptionRelated: #3773 and #3154
Declaring a (at most 1) parameter with attribute ValueFromRemainingArguments allows you to collect any remaining arguments - those not bound to other parameters - in a single parameter.
Given that there may be 1 or more remaining arguments, that would usually imply:
- bind a scalar to the parameter if there's only one remaining argument
- bind a regular PowerShell array (
[object[]]), if there are multiple remaining arguments.
By contrast, a [List[object]] instance is currently bound, irrespective of whether 1 or more remaining arguments are bound.
Steps to reproduce
Create script t.ps1 in the current dir. with the following content:
[CmdletBinding()]
param(
[Parameter(ValueFromRemainingArguments)]
$Rest
)
$Rest.Count
$Rest.GetType().FullName
Then call:
Expected behavior
1
System.String
2
System.Object[]
Actual behavior
1
System.Collections.Generic.List`1[[System.Object, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
2
System.Collections.Generic.List`1[[System.Object, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
Environment data
PowerShell Core v6.0.0-beta.5 on macOS 10.12.6
PowerShell Core v6.0.0-beta.5 on Ubuntu 16.04.3 LTS
PowerShell Core v6.0.0-beta.5 on Microsoft Windows 10 Pro (64-bit; v10.0.15063)
Windows PowerShell v5.1.15063.483 on Microsoft Windows 10 Pro (64-bit; v10.0.15063) Activitychanged the title [-]ValueFromRemainingArguments parameters by default invariably receive a [List[object]] collection rather than a scalar / PowerShell array ([object[]]), as appropriate[/-] [+]A ValueFromRemainingArguments parameter by default invariably receives a [List[object]] collection rather than a scalar / PowerShell array ([object[]]), as appropriate[/+] on Aug 20, 2017lzybkr commented on Aug 21, 2017 Parameters that use ValueFromRemainingArguments usually specify an array type, but if the type is omitted, isn't it more useful to pass a collection? The command probably expects a collection anyway.
This would also be a breaking change. mklement0 commented on Aug 21, 2017 ContributorAuthor More actions
isn't it more useful to pass a collection?
It is useful in the same way that cmdlets (e.g., Get-ChildItem) return collections - but, by virtue of going through the pipeline, they output:
- either a
[System.Object[]] array
- or, if the collection happens to contain a single item only, a scalar.
I get the backward compatibility concern, but what good reason is there for ValueFromRemainingArguments parameters and common parameters -OutVariable, -ErrorVariable, -WarningVariable, and -InformationVariable do deviate from these fundamental behaviors? mklement0 commented on Aug 21, 2017 ContributorAuthor More actions P.S.:
Given that we're already willing to fix the issue for -OutVariable, even though it is a breaking change - #3154 - this strikes me as a great opportunity to fix the issue fundamentally, given that the other cases are probably less common than -OutVariable.
Also, while technically a breaking change, I wouldn't expect it to be a problem in practice, given that, if a scalar is returned in the future for single-item collections, things should continue to work, thanks to being able to use .Count and indexing on scalars too.
I suppose there could be code out there that relies on the specific collection types returned ([List[object]] for ValueFromRemainingArguments, and [System.Collections.ArrayList] for the common parameters), but that strikes me as unlikely. lzybkr commented on Aug 21, 2017 Comparing this issue to -OutVariable is interesting. I think the primary use case for OutVariable is the scalar case, which is why we were open to a change.
ValueFromRemainingArguments has the opposite expectation - the primary case being a collection.
mklement0 commented on Aug 21, 2017 ContributorAuthor More actions
ValueFromRemainingArguments has the opposite expectation
Absolutely - but the larger point is that all these features should be familiar PS citizens, which means:
mklement0 commented on Aug 22, 2017 ContributorAuthor More actions Just ran into another case where the above expectations are confounded: #4642 mklement0 commented on Nov 16, 2020 ContributorAuthor More actions Revisiting this I agree that it makes more sense to always create a collection for a (non-type-constrained) ValueFromRemainingArguments parameter variable, given that the point of such a parameter is to expect a variable number of arguments, and that the script/function author has to be prepared to handle multiple arguments.
This reduces the issue at hand to suggesting use of object[] instead of List`1 - but, unlike in the #3154 case, this technical difference is unlikely to matter, so I'm closing this. surfingoldelephant commented on Jan 28, 2025 It's worth noting there's asymmetry between positional input passed as:
- A single scalar argument or multiple arguments vs...
- A single collection argument
This affects untyped or [object]/[psobject]-typed parameters.
$a = [string[]] ('Foo', 'Bar')
$untyped = {
param ([Parameter(ValueFromRemainingArguments)] $P1)
process {
[pscustomobject] @{
Type = $P1.GetType().Name
Count = $P1.Count
Value = $P1
IsRef = [object]::ReferenceEquals($P1, $a)
}
}
}
& $untyped Foo
& $untyped Foo Bar
& $untyped $a
# Type Count Value IsRef
# ---- ----- ----- -----
# List`1 1 {Foo} False
# List`1 2 {Foo, Bar} False
# String[] 2 {Foo, Bar} True
For completeness, when the parameter is typed:
$a = [string[]] ('Foo', 'Bar')
$typed = {
param ([Parameter(ValueFromRemainingArguments)] [string[]] $P1)
process {
[pscustomobject] @{
Type = $P1.GetType().Name
Count = $P1.Count
Value = $P1
IsRef = [object]::ReferenceEquals($P1, $a)
}
}
}
& $typed Foo
& $typed Foo Bar
& $typed $a
# Type Count Value IsRef
# ---- ----- ----- -----
# String[] 1 {Foo} False
# String[] 2 {Foo, Bar} False
# String[] 2 {Foo, Bar} True
Summary:
- If the parameter is untyped or
[object]/[psobject]-typed:
- If a single scalar argument is passed,
$P1 is a new List<T> instance.
- If multiple arguments are passed,
$P1 is a new List<T> instance.
- If a single collection argument is passed,
$P1 is a reference to that collection.
- If the parameter is typed as anything other than
[object]/[psobject], $P1 is of whatever that type is.
In older versions, there was no asymmetry in scenario 1. The result was invariably a new List<T> instance (for 1.3, a single-element list containing the collection).
I believe this is a by-product of #2035.
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|