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()
)
)
}
}