Powershell 5.# add type accelerator and use in class in same file

Viewed 227

I have been running into trouble adding new type accelerators in PS 5.1. I found this, and a few other similar references, so my understanding is that this is the case for all 5.# builds of PowerShell, not just the referenced preview. To that end, I have this

CLS
$accelerators = [PowerShell].Assembly.GetType("System.Management.Automation.TypeAccelerators")
$accelerators::Add("pxList",[System.Collections.Generic.List[string]])
$accelerators::Add("pxHashList",[System.Collections.Generic.List[hashtable]])
$builtinTypeAccelerators = $accelerators.GetField("builtinTypeAccelerators", [System.Reflection.BindingFlags]"Static,NonPublic")
$builtinTypeAccelerators.SetValue($builtinTypeAccelerators, $accelerators::Get)

class pxT_DefinitionsMigrater {
    # Properties
    $XML = [xml.xmlDocument]::new()
    $Errors = [pxList]::new()
    $initiations = [pxHashList]::new()

    # Constructor
    pxT_DefinitionsMigrater ([string]$xmlPath) {
    }
}

[pxT_DefinitionsMigrater]::new('String')

And I am still getting Unable to find type errors. Where am I going wrong? Interestingly, this DOES work.

CLS
$accelerators = [PowerShell].Assembly.GetType("System.Management.Automation.TypeAccelerators")
$accelerators::Add("pxList",[System.Collections.Generic.List[string]])
$accelerators::Add("pxHashList",[System.Collections.Generic.List[hashtable]])
$builtinTypeAccelerators = $accelerators.GetField("builtinTypeAccelerators", [System.Reflection.BindingFlags]"Static,NonPublic")
$builtinTypeAccelerators.SetValue($builtinTypeAccelerators, $accelerators::Get)

([pxHashList]::new()).GetType().FullName

It seems that custom type accelerators don't work (the same way) with classes. So perhaps more importantly, is adding our own type accelerators still considered good practice, or is this change since PS5 an indication that we really shouldn't be doing it? Or at least not doing it in conjunction with classes?

Also, as an aside, but something that certainly doesn't warrant an actual question, I notice that the new method is not capitalized. When I get my intellisence options in the ISE for my new type Equals and ReferenceEquals are both capitalized, but new isn't. This is true for every new I have seen. Seems like there must be some useful information packaged in that fact.

EDIT: Very interesting discovery, just after my initial post. This also works

CLS
$accelerators = [PowerShell].Assembly.GetType("System.Management.Automation.TypeAccelerators")
$accelerators::Add("pxList",[System.Collections.Generic.List[string]])
$accelerators::Add("pxHashList",[System.Collections.Generic.List[hashtable]])
$builtinTypeAccelerators = $accelerators.GetField("builtinTypeAccelerators", [System.Reflection.BindingFlags]"Static,NonPublic")
$builtinTypeAccelerators.SetValue($builtinTypeAccelerators, $accelerators::Get)

class pxT_DefinitionsMigrater {
    # Properties
    $XML = [xml.xmlDocument]::new()
    $Errors = $null
    $initiations = $null

    # Constructor
    pxT_DefinitionsMigrater ([string]$xmlPath) {
        $this.Errors = [pxList]::new()
        $this.initiations = [pxHashList]::new()
    }
}

[pxT_DefinitionsMigrater]::new('String')

So, the XML property can be assigned an actual value in the properties section, of an empty [xml.document], but I can't create new empty versions of my new types there, I need to do it in the ctor. Makes me wonder if there is an argument for always setting properties to $null at declaration, and then assigning a value in the constructor. So...

CLS
$accelerators = [PowerShell].Assembly.GetType("System.Management.Automation.TypeAccelerators")
$accelerators::Add("pxList",[System.Collections.Generic.List[string]])
$accelerators::Add("pxHashList",[System.Collections.Generic.List[hashtable]])
$builtinTypeAccelerators = $accelerators.GetField("builtinTypeAccelerators", [System.Reflection.BindingFlags]"Static,NonPublic")
$builtinTypeAccelerators.SetValue($builtinTypeAccelerators, $accelerators::Get)

class pxT_DefinitionsMigrater {
    # Properties
    [xml.xmlDocument]$XML = $null
    [pxList]$Errors = $null
    [pxHashList]$initiations = $null

    # Constructor
    pxT_DefinitionsMigrater ([string]$xmlPath) {
        $this.XML = [xml.xmlDocument]::new()
        $this.Errors = [pxList]::new()
        $this.initiations = [pxHashList]::new()
    }
}

[pxT_DefinitionsMigrater]::new('String')

If you have a number of ctor variations this seems a bit wasteful, but perhaps is best practice?

EDIT: Indeed, it seems it is second run that allows things to work, not where the accelerator is used. Given that this will eventually be a script and not run in the ISE, an answer that works in both places would be ideal.

$accelerators = [PowerShell].Assembly.GetType("System.Management.Automation.TypeAccelerators")
$accelerators::Add("pxList",[System.Collections.Generic.List[string]])
$accelerators::Add("pxHashList",[System.Collections.Generic.List[hashtable]])
$builtinTypeAccelerators = $accelerators.GetField("builtinTypeAccelerators", [System.Reflection.BindingFlags]"Static,NonPublic")
$builtinTypeAccelerators.SetValue($builtinTypeAccelerators, $accelerators::Get)

class pxT_DefinitionsMigrater {
    # Properties
    [xml.xmlDocument]$XML
    [pxList]$Errors
    [pxHashList]$initiations

    # Constructor
    [Void]initializeProperties () {
        $this.XML = [xml.xmlDocument]::new()
        $this.Errors = [pxList]::new()
        $this.initiations = [pxHashList]::new()
    }
    pxT_DefinitionsMigrater ([string]$xmlPath) {
        $this.initializeProperties()
    }
}

[pxT_DefinitionsMigrater]::new('String')
1 Answers

A Powershell Code containing class definitions first evaluates them.

# works!
[myClass]::new()
Start-Sleep -Seconds 3
class myClass{}          # this line runs first
# throws!
f               # this line runs first
function f {}

f: The term 'f' is not recognized as the name of a cmdlet, function, ...

The problem is that the class pxT_DefinitionsMigrater is try to use a type that's not defined yet.

The solution is

  1. Define types and classes in separate files.
  2. Load a type definitions file first, then class definitions.

Update

It's possible to define types and classes in a single file base on this answer

function GetPxLists {
    if (("pxList" -as [type]) -eq $null){
        $accelerators = [PowerShell].Assembly.GetType("System.Management.Automation.TypeAccelerators")
        $accelerators::Add("pxList",[System.Collections.Generic.List[string]])
        $accelerators::Add("pxHashList",[System.Collections.Generic.List[hashtable]])
        $builtinTypeAccelerators = $accelerators.GetField("builtinTypeAccelerators", [System.Reflection.BindingFlags]"Static,NonPublic")
        $builtinTypeAccelerators.SetValue($builtinTypeAccelerators, $accelerators::Get)
    }

    [pxList]::new(), [pxHashList]::new()
}


# and inside class
    # Constructor
    pxT_DefinitionsMigrater ([string]$xmlPath) {
        $PxLists = GetPxLists
        $this.Errors = $PxLists[0]
        $this.initiations = $PxLists[1]
    }

Related