13

In PowerShell v3.0 PSCustomObject was introduced. It's like PSObject, but better. Among other improvements (e.g. property order being preserved), creating object from hashtable is simplified:

[PSCustomObject]@{one=1; two=2;}

Now it seems obvious that this statement:

[System.Management.Automation.PSCustomObject]@{one=1; two=2;}

would work the same way, because PSCustomObject is an "alias" for full namespace + class name. Instead I get an error:

Cannot convert the "System.Collections.Hashtable" value of type "System.Collections.Hashtable" to type "System.Management.Automation.PSCustomObject".

I listed accelerators for both types of objects:

[accelerators]::get.GetEnumerator() | where key -Like ps*object

    Key            Value
    ---            -----
    psobject       System.Management.Automation.PSObject
    pscustomobject System.Management.Automation.PSObject

and discovered that both reference the same PSObject class - this has to mean that using accelerators can do a bunch of other stuff than just making the code shorter.

My questions regarding this issue are:

  1. Do you have some interesting examaples of differences between using an accelerator vs using full type name?
  2. Should using full type name be avoided whenever an accelerator is available as a general best practice?
  3. How to check, maybe using reflection, if an accelerator does other stuff than just pointing to underlying class?
AdamL
  • 10,453
  • 5
  • 46
  • 68
  • 5
    If you decompile `System.Management.Automation.Language.Compiler.VisitConvertExpression`, then you can see that there are special handling for three type names: `ordered`, `PSCustomObject` and `ref`. – user4003407 Mar 09 '16 at 15:22

2 Answers2

6

Looking at the static methods:

PS C:\> [PSCustomObject] | gm -Static -MemberType Method



   TypeName: System.Management.Automation.PSObject

Name            MemberType Definition                                                        
----            ---------- ----------                                                        
AsPSObject      Method     static psobject AsPSObject(System.Object obj)                     
Equals          Method     static bool Equals(System.Object objA, System.Object objB)        
new             Method     psobject new(), psobject new(System.Object obj)                   
ReferenceEquals Method     static bool ReferenceEquals(System.Object objA, System.Object o...



PS C:\> [System.Management.Automation.PSCustomObject] | gm -Static -MemberType Method



   TypeName: System.Management.Automation.PSCustomObject

Name            MemberType Definition                                                        
----            ---------- ----------                                                        
Equals          Method     static bool Equals(System.Object objA, System.Object objB)        
ReferenceEquals Method     static bool ReferenceEquals(System.Object objA, System.Object o...

The type accelerator has a couple of new static methods added. I suspect it's using one of those as the constructor.

mjolinor
  • 59,504
  • 6
  • 99
  • 125
2

[PSObject] and [PSCustomObject] are aliases for the same type - System.Management.Automation.PSObject. I can't say there's a good reason for it, but it is at least suggestive of two different purposes, and maybe that's reason enough.

System.Management.Automation.PSObject is used to wrap objects. It was introduced to provide a common reflection api over any object that PowerShell wraps - .Net, WMI, COM, ADSI, or simple property bags.

System.Management.Automation.PSCustomObject is just an implementation detail. When you create a PSObject, the PSObject must wrap something. For property bags, the object wrapped is System.Management.Automation.PSCustomObject.SelfInstance (an internal member.) This instance is hidden from normal use of PowerShell, the only way to observe it is with reflection.

Property bags are created in multiple ways in PowerShell:

$o1 = [pscustomobject]@{Prop1 = 42}
$o2 = new-object psobject -Property @{Prop1 = 42 }

Both $o1 and $o2 above will be an instance of PSObject, and the PSObject will wrap PSCustomObject.SelfInstance. PSCustomObject.SelfInstance is used internally in PowerShell to tell the difference between a simple property bag and wrapping any other object.

Jason Shirk
  • 6,874
  • 19
  • 28
  • 1
    Hi Jason. Both `Get-Member` and `.GetType()` report both $o1 and $o2 as instances of PSCustomObject rather than PSObject. – Burt_Harris Sep 15 '16 at 23:05
  • 1
    If, however I create a third object as `$o3 = [psobject]@{Prop1=42}`, that object is different. – Burt_Harris Sep 15 '16 at 23:08
  • 1
    My point being that the answer above is confusing, because in powershell `[psobject]` and `[pscustomobject]` sure don't act the same. – Burt_Harris Sep 15 '16 at 23:24
  • 1
    Yeah, it's a little confusing, part my fault, partly the design.. I missed calling GetType() as a way of seeing PSCustomObject. Casting to [pscustomobject] is treated specially in the parser starting in V3, before that, it would have been equivalent to casting to [psobject]. The bottom line though - the type (and singleton instance) System.Management.Automation.PSCustomObject is an implementation detail. The cast [pscustomobject] is useful if the operand is a hash literal, otherwise it's equivalent to [psobject] cast. – Jason Shirk Sep 16 '16 at 17:49
  • @JasonShirk if memory serves, using the `[pscustomobject]` cast (n v3 or higher) can fail in certain restricted language modes, whereas some other methods (`New-Object`? `Select-Object`?) would work in the same context. I don't remember the details now because I found out (the hard way) a few years ago. – briantist Jan 03 '18 at 01:42