2

Why does the last statement error when all of the others work?

using namespace System.Collections.Generic;

$a = [List[int]](1, 2)    # list creates correctly
$b = [List[int]](@(1, 2)) # list creates correctly
$c = [List[int]](1)       # list creates correctly
$d = [List[int]](@(1))    # error: Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.Int32".

Note that the issue only appears to happen in PowerShell 5.1. In PowerShell Core it doesn't have any issues.

Polshgiant
  • 3,595
  • 1
  • 22
  • 25
  • 1
    This is indeed a bug in the older version of windows PowerShell, you might workaround this with commands like: `$d = [List[int]]1` or: `$d = [List[int]](@(1) |% { $_ })` or `$d = [List[int]]([Int[]]@(1))` – iRon Aug 15 '23 at 16:11
  • @iRon That is a helpful workaround, thanks. Do you have any info as to what the actual bug is, by chance? Like is it PS unrolling behavior, explicit casting, implicit casting, something else? – Polshgiant Aug 15 '23 at 16:11
  • I think @mklement0 filed an issue for this particular bug earlier this year (in the context of class `return` parameters in think), I can't find it now though :-/ – Mathias R. Jessen Aug 15 '23 at 16:25
  • 2
    @MathiasR.Jesse; [`#2208` v5 Regression: Casting object to more narrow generic collection fails with a single element, but not with two](https://github.com/PowerShell/PowerShell/issues/2208) – iRon Aug 15 '23 at 16:59
  • 1
    Additional workaround: `[List[int]]$($a)` – iRon Aug 15 '23 at 17:03

1 Answers1

1

iRon has provided the crucial pointer:

  • You're seeing a bug in Windows PowerShell that has since been fixed in PowerShell (Core) 7+, and because Windows PowerShell is no longer actively developed and receives security-critical fixes only, this won't be fixed.

  • Specifically, casting from a single-element array whose type isn't the same as the target list fails, and @(...), the array-subexpression operator, invariably creates [object[]] arrays.

    # UNEXPECTEDLY FAILS in Windows PowerShell:
    # Casting from a single-element array that isn't strongly typed
    # with the list's element type.
    [System.Collections.Generic.List[int]] @(1)
    
    • Note that other generic list types, such as System.Collections.ObjectModel.Collection`1, are not affected and neither is the non-generic System.Collections.ArrayList class, though the latter suffers from the inverse bug - see below.
  • Workarounds:

    # Use a scalar
    [System.Collections.Generic.List[int]] 1
    
    # Use a strongly typed array
    [System.Collections.Generic.List[int]] [int[] @(1)
    
    # Also use a strongly typed array
    # if you're given an array $arr and don't know how many elements it has.
    [System.Collections.Generic.List[int]] [int[] $arr
    
    # Syntactically simpler, but slower alternative, using
    # $(...), the subexpression operator:
    [System.Collections.Generic.List[int]] $($arr)
    
    • The reason that $(...), the subexpression operator is slower (though it may not matter in practice) is that it enumerates $arr and then collects the results, in a new ([object[]) array, if $arr has two or more elements, otherwise a single-element array's only element is collected as-is (it is the latter behavior that makes $(...) effective as a workaround).

Note that there's a related bug that also affects PowerShell (Core), up to at least v7.3.6 (current as of this writing):

  • The System.Collections.ArrayList class - which is no longer recommended - has the inverse problem, reported in GitHub issue #11749:

    • Casting from a scalar unexpectedly breaks.

      # UNEXPECTEDLY FAILS up to at least PowerShell 7.3.6:
      # Casting from a scalar.
      [System.Collections.ArrayList] 1
      
  • Workaround:

    # Use @(...)
    [System.Collections.ArrayList] @(1)
    

Note:

  • That casting a scalar to a list type (e.g. [System.Collections.Generic[int] 42 and [int[]] 42) works at all may be surprising.

  • It is a testament to PowerShell's flexibility not just with respect to its automatic type conversions for scalars, but also its unified handling of scalars and collections - see this answer - which generally also extends to casts - see this answer.

mklement0
  • 382,024
  • 64
  • 607
  • 775