Skip to content

A ValueFromRemainingArguments parameter by default invariably receives a [List[object]] collection rather than a scalar / PowerShell array ([object[]]), as appropriate #4625

Closed

Description

Related: #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:

./t foo
./t foo bar

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)

Activity

  1. changed 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, 2017
  2. lzybkr commented on Aug 21, 2017

    @lzybkr
    Contributor

    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.

  3. mklement0 commented on Aug 21, 2017

    @mklement0
    ContributorAuthor

    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?

  4. added
    WG-Languageparser, language semantics
    Issue-Discussionthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassif
    on Aug 21, 2017
  5. mklement0 commented on Aug 21, 2017

    @mklement0
    ContributorAuthor

    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.

  6. lzybkr commented on Aug 21, 2017

    @lzybkr
    Contributor

    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.

  7. mklement0 commented on Aug 21, 2017

    @mklement0
    ContributorAuthor

    ValueFromRemainingArguments has the opposite expectation

    Absolutely - but the larger point is that all these features should be familiar PS citizens, which means:

    • use [object[]] arrays rather than some collection type not otherwise used publicly in PowerShell.

    • output a scalar if a collection happens to contain 1 item only.

  8. mklement0 commented on Aug 22, 2017

    @mklement0
    ContributorAuthor

    Just ran into another case where the above expectations are confounded: #4642

  9. mklement0 commented on Nov 16, 2020

    @mklement0
    ContributorAuthor

    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.

  10. surfingoldelephant commented on Jan 28, 2025

    @surfingoldelephant

    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:

    1. If the parameter is untyped or [object]/[psobject]-typed:
      1. If a single scalar argument is passed, $P1 is a new List<T> instance.
      2. If multiple arguments are passed, $P1 is a new List<T> instance.
      3. If a single collection argument is passed, $P1 is a reference to that collection.
    2. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Discussionthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassifWG-Languageparser, language semantics

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions