Purclot
Purclot

Reputation: 559

PowerShell understand Get-Member

I'm having a problem understanding the "Get-Member" Definition column. I'm doing:

$Array = "ans", "zwei","drei"

$Array.GetType() gives back - as expected - BaseType=System.Array

Then:

$Array | gm 

says nothing about a method Add() - which is correct since $Array is an array.

But:

gm -inputobject $Array 

is showing me an Add()-Method, with the definition

int IList.Add(System.Object value).

Of course: $Array.Add("vier") doesn't work.
I know: IList is an interface etc. but it's here completely wrong since we're talking of a variable of an Array-type?

To make the chaos perfect: the intellisense of my ISE (PS Version: 4) is showing the Add()-Method as well.

What a mess. How to understand correctly the "definition" column using Get-Member?

Upvotes: 2

Views: 3029

Answers (3)

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174515

The type of this object:

$Array = "ans","zwei","drei"

if not System.Array, but Object[] - a type of collection that directly inherits from System.Array, but is not the same as.

If you dig a little deeper down the rabbithole, you'll find that the type of $Array does in fact implement IList:

PS C:\> $Array.GetType().ImplementedInterfaces
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    ICloneable
True     False    IList
True     False    ICollection
True     False    IEnumerable
True     False    IStructuralComparable
True     False    IStructuralEquatable
True     False    IList`1
True     False    ICollection`1
True     False    IEnumerable`1
True     False    IReadOnlyList`1
True     False    IReadOnlyCollection`1

The reason $Array | gm doesn't show an Add() method is that when you pipe $Array to gm, the pipeline tries to enumerate the contents of $Array, and what gm shows you is really the members of the contained type - a System.String:

enter image description here

To get the members of the array object itself instead of those of the array elements you must pass it to Get-Member via the -InputObject method:

PS C:\> Get-Member -InputObject $Array

   TypeName: System.Object[]

Name           MemberType            Definition
----           ----------            ----------
Count          AliasProperty         Count = Length
Add            Method                int IList.Add(System.Object value)
Address        Method                System.Object&, mscorlib, Version=4.0.0.0, C...
Clear          Method                void IList.Clear()
Clone          Method                System.Object Clone(), System.Object IClonea...
CompareTo      Method                int IStructuralComparable.CompareTo(System.O...
Contains       Method                bool IList.Contains(System.Object value)
CopyTo         Method                void CopyTo(array array, int index), void Co...
Equals         Method                bool Equals(System.Object obj), bool IStruct...
...

Upvotes: 6

user4003407
user4003407

Reputation: 22122

First: collections are enumerated, when passed by pipeline, so in this case $Array | gm Get-Member cmdlet does not see an array, but individual items of it, so it report members of items not members of array.

Second: arrays implement IList interface, and single dimensions zero-based arrays also implement generic version of that interface. Some interface methods are implemented explicitly, like Add or Contains.

Consider this code:

Add-Type -TypeDefinition @'
    using System;
    public interface IInterface {
        void Method();
    }
    public class Class1:IInterface {
        void IInterface.Method() {
            throw new NotSupportedException();
        }
    }
    public class Class2:IInterface {
        void IInterface.Method() {
            //Do something.
        }
    }
'@
(New-Object Class1).Method()
(New-Object Class2).Method()
([IInterface](New-Object Class1)).Method()
([IInterface](New-Object Class2)).Method()

Try run it in PowerShell v2. All Method calls failed with error Method invocation failed because [Class] doesn't contain a method named 'Method'. It is true, nor Class1 nor Class2 have Method method. So the problem: how to call interface method if class implement it explicitly?

In PowerShell v3 that changed. All interface methods now also reported as members of object, so call (New-Object Class2).Method() works. Problem is that PowerShell does not have a way to know why interface method was implemented explicitly, because it not supported or because it intended to call thru interface, so it have to report Method as member for Class1 and Class2, even if Class1 does not actually support than member.

For example IList.Contains is valid call for array but this code:

$a=1..10
$a.Contains(5)

fails in PowerShell v2:

function f{
    param(
        [Collections.IList]$List,
        [Object]$Item
    )
    $List.Contains($Item)
}
$Array=1..10
$List=New-Object Collections.Generic.List[Object] (,$Array)
f $Array 5 #This will fail in PowerShell v2
f $List 5

Upvotes: 3

Vesper
Vesper

Reputation: 18747

The trick is that $array | get-member returns a System.String as a type in this case, that string does not have a "Add" method. If your array would be equal to, say, @(1,2), you'll get an output of System.Int32 as "its" type. The reason on this is that you're using pipelines that are designed to unfurl arrays into cmdlets that normally are designed to perform one action per input member. So, the get-member -InputObject $array is the proper way of querying arrays for members, and not querying its members for their members.

The trick with $array.Add('vier') is a tad different - I've just tested and the exception does not say "illegal call", it says "size is fixed", and it is indeed so, $array.IsFixedSize returns true. The probable reason for this is to not expose the actual IList alteration mechanics and just declare them "fixed size" and only override this limit in operators or methods designed to accept arrays.

UPDATE: As @Mathias says in his answer, the actual type of $array is System.Object[] which I missed, and by the list of its interfaces, it was designed to declare itself as "read only" to .NET methods on interfaces, so it should only be modified by Powershell code, not .NET code.

Upvotes: 4

Related Questions