Reputation: 559
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
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
:
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
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
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