arrayspowershellmethodsmethod-callmember-access-enumeration

Why does GetType() return Object[] as the type on array, but a bad method call error says it is the type that is in the 0th element in the array?


So, I create an array, call GetType(), and I (reasonably) get the answer Object[]. If I, however, give it a bad method, I get the type of the 0th element in the error message.

Just trying to understand Powershell more -- I assume this is because Powershell is unwrapping the array in the case it doesn't find the method on the array, then also fails on the 0th array which throws the full error?

PS C:\Users\x> $i = @(1)
PS C:\Users\x> $i.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

PS C:\Users\x> $i.HerpDerp()
InvalidOperation: Method invocation failed because [System.Int32] does not contain a method named 'HerpDerp'.
PS C:\Users\x> $a = @("hi")
PS C:\Users\x> $a.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

PS C:\Users\x> $a.HerpDerp()
InvalidOperation: Method invocation failed because [System.String] does not contain a method named 'HerpDerp'.
PS C:\Users\x> $m = @("hi",1)
PS C:\Users\x> $m.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

PS C:\Users\x> $m.HerpDerp()
InvalidOperation: Method invocation failed because [System.String] does not contain a method named 'HerpDerp'.

Solution

  • The relevant information is in the comments, but I think your question deserves an answer post:

    What you're seeing are the effects of member-access enumeration, a convenient PowerShell feature where accessing a member (property or method) on an array (list-like collection) implicitly accesses that member on its elements, one by one, and returns the results as an array, except if there's only one result, which is returned as-is;[1] e.g.:

    # Even though .Year is applied to the *array*, PowerShell returns 
    # the .Year property values from the two [datetime] instances that
    # are the *elements* of that array; e.g., in 2023: 
    #   @( 2023, 2022 )
    @( (Get-Date), (Get-Date).AddYears(-1) ).Year
    

    This only works if the specified member doesn't also exist on the array itself, because the array's own member takes precedence; e.g.:

    # Returns 3, the *array's* .Length property value (count of elements), 
    # not the length of the strings stored in the array's elements.
    @( 'foo', 'bar', 'baz' ).Length
    

    This also explains why $i.GetType() returned the array's type.


    When member-access enumeration is applied, the (default) behavior differs depending on whether you access a property or a method:

    Note that the above implies that it isn't necessarily the first element that triggers the statement-terminating error and if it isn't, the results from the calls on the earlier elements are in effect discarded.
    This is somewhat problematic, given that method calls can have side effects (and possibly have no output), in which case you may need to determine after the fact what elements the method calls were successfully performed on.


    [1] In doing so, member-access enumeration mimics the behavior of PowerShell's pipeline, which may be surprising. See GitHub issue #6802 for a discussion.