Unable to Return [BindingList[RandomType]] from ScriptProperty

Advertisements

I have a complex class that dynamically adds members to itself based on a file loaded via Import-Clixml.

Boiling the class down to the problematic part leaves us with this (Take note of the commented line used to prove success up to that point):

class TestClass {
    [hashtable]$_data = @{}
    [void]DefineData([object]$data) {
        $this._data['Data'] = $data
        $this | Add-Member -MemberType ScriptProperty -Name 'ScriptProperty' -Value {
            #$this._data['Data'].GetType() | Out-Host
            return $this._data['Data']
        }
    }
}

In the following code, there are 4 statements for assigning a value to $OriginalValue. Leave 3 of these statements commented and uncomment the one you want to try. When executed, the code should result in $ReturnValue containing the same value as $OriginalValue, but in the case of assigning $OriginalValue an instance of [BindingList[RandomType]], $ReturnValue is $null.

$ClassVar = [TestClass]::new()

$OriginalValue = [System.ComponentModel.BindingList[string]]::new()
#$OriginalValue = @{}
#$OriginalValue = [PSCustomObject]@{ Name = 'Value' }
#$OriginalValue = "Test String"

$OriginalValue.GetType()
$ClassVar.DefineData($OriginalValue)

$ReturnValue = $ClassVar.ScriptProperty
$ReturnValue.GetType()

Yes, I can hack my way around the problem by storing instances of [BindingList[RandomType]] in a [hashtable], but could someone explain what is going on, or even better yet, how to fix the code for all data types?

>Solution :

As explained in comments, the problem is not the BindingList but the output from your Script Block being enumerated. Since your BindingList has no elements when you call .DefineData($OriginalValue) then enumerating a list with no elements via .ScriptProperty results in null value:

(& { [System.ComponentModel.BindingList[string]]::new() }).GetType()

# Errors with:
# InvalidOperation: You cannot call a method on a null-valued expression.

A simple workaround is to wrap the output in a single element array before outputting, for this you can use the comma operator ,.

(& { , [System.ComponentModel.BindingList[string]]::new() }).GetType()

# Output type is preserved and the wrapping array is lost due to enumeration

So, your class method could look as follows considering the hashtable property is not needed:

class TestClass {
    [void] DefineData([object] $data) {
        $this.PSObject.Properties.Add(
            [psscriptproperty]::new(
                'ScriptProperty',
                { , $data }.GetNewClosure()
            )
        )
    }
}

Leave a ReplyCancel reply