Source/Private/ClassTools.ps1
function Use-ClassAccessors { <# .SYNOPSIS Implements class getter and setter accessors. .DESCRIPTION The [Use-ClassAccessors][1] cmdlet updates script property of a class from the getter and setter methods. Which are also known as [accessors or mutator methods][2]. The getter and setter methods should use the following syntax: ### getter syntax [<type>] get_<property name>() { return <variable> } or: [Object] get_<property name>() { return ,[<Type>]<variable> } ### setter syntax set_<property name>(<variable>) { <code> } > [!NOTE] > A **setter** accessor requires a **getter** accessor to implement the related property. > [!NOTE] > In most cases, you might want to hide the getter and setter methods using the [`hidden` keyword][3] > on the getter and setter methods. .EXAMPLE # Using class accessors The following example defines a getter and setter for a `value` property and a _readonly_ property for the type of the type of the contained value. Install-Script -Name Use-ClassAccessors Class ExampleClass { hidden $_Value hidden [Object] get_Value() { return $this._Value } hidden set_Value($Value) { $this._Value = $Value } hidden [Type]get_Type() { if ($Null -eq $this.Value) { return $Null } else { return $this._Value.GetType() } } hidden static ExampleClass() { Use-ClassAccessors } } $Example = [ExampleClass]::new() $Example.Value = 42 # Set value to 42 $Example.Value # Returns 42 $Example.Type # Returns [Int] type info $Example.Type = 'Something' # Throws readonly error .PARAMETER Class Specifies the class from which the accessor need to be initialized. Default: The class from which this function is invoked (by its static initializer). .PARAMETER Property Filters the property that requires to be (re)initialized. Default: All properties in the given class .PARAMETER Force Indicates that the cmdlet reloads the specified accessors, even if the accessors already have been defined for the concerned class. .LINK [1]: https://github.com/iRon7/Use-ClassAccessors "Online Help" [2]: https://en.wikipedia.org/wiki/Mutator_method "Mutator method" [3]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_classes#hidden-keyword "Hidden keyword in classes" #> param( [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [string[]]$Class, [Parameter(ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [string]$Property, [switch]$Force ) process { $ClassNames = if ($Class) { $Class } else { $Caller = (Get-PSCallStack)[1] if ($Caller.FunctionName -ne '<ScriptBlock>') { $Caller.FunctionName } elseif ($Caller.ScriptName) { $Ast = [System.Management.Automation.Language.Parser]::ParseFile($Caller.ScriptName, [ref]$Null, [ref]$Null) $Ast.EndBlock.Statements.where{ $_.IsClass }.Name } } foreach ($ClassName in $ClassNames) { $TargetType = $ClassName -as [Type] if (-not $TargetType) { Write-Warning "Class not found: $ClassName" } $TypeData = Get-TypeData -TypeName $ClassName $Members = if ($TypeData -and $TypeData.Members) { $TypeData.Members.get_Keys() } else { @() } $Methods = if ($Property) { $TargetType.GetMethod("get_$Property") $TargetType.GetMethod("set_$Property") } else { $targetType.GetMethods().where{ ($_.Name -Like 'get_*' -or $_.Name -Like 'set_*') -and $_.Name -NotLike '???__*' } } $Accessors = @{} foreach ($Method in $Methods) { $Member = $Method.Name.SubString(4) if (-not $Force -and $Member -in $Members) { continue } $Parameters = $Method.GetParameters() if ($Method.Name -Like 'get_*') { if ($Parameters.Count -eq 0) { if ($Method.ReturnType.IsArray) { $Expression = @" `$TargetType = '$ClassName' -as [Type] `$Method = `$TargetType.GetMethod('$($Method.Name)') `$Invoke = `$Method.Invoke(`$this, `$Null) `$Output = `$Invoke -as '$($Method.ReturnType.FullName)' if (@(`$Invoke).Count -gt 1) { `$Output } else { ,`$Output } "@ } else { $Expression = @" `$TargetType = '$ClassName' -as [Type] `$Method = `$TargetType.GetMethod('$($Method.Name)') `$Method.Invoke(`$this, `$Null) -as '$($Method.ReturnType.FullName)' "@ } if (-not $Accessors.Contains($Member)) { $Accessors[$Member] = @{} } $Accessors[$Member].Value = [ScriptBlock]::Create($Expression) } else { Write-Warning "The getter '$($Method.Name)' is skipped as it is not parameter-less." } } elseif ($Method.Name -Like 'set_*') { if ($Parameters.Count -eq 1) { $Expression = @" `$TargetType = '$ClassName' -as [Type] `$Method = `$TargetType.GetMethod('$($Method.Name)') `$Method.Invoke(`$this, `$Args) "@ if (-not $Accessors.Contains($Member)) { $Accessors[$Member] = @{} } $Accessors[$Member].SecondValue = [ScriptBlock]::Create($Expression) } else { Write-Warning "The setter '$($Method.Name)' is skipped as it does not have a single parameter" } } } foreach ($MemberName in $Accessors.get_Keys()) { $TypeData = $Accessors[$MemberName] if ($TypeData.Contains('Value')) { $TypeData.TypeName = $ClassName $TypeData.MemberType = 'ScriptProperty' $TypeData.MemberName = $MemberName $TypeData.Force = $Force Update-TypeData @TypeData } else { Write-Warning "A 'set_$MemberName()' accessor requires a 'get_$MemberName()' accessor." } } } } } function Set-View { <# .SYNOPSIS Sets the default view output .LINK https://stackoverflow.com/questions/77752014/how-to-type-convert-a-derived-class #> param( [Parameter(ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [string[]]$Class, [Parameter(Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [ScriptBlock]$ScriptBlock ) process { $ClassNames = if ($Class) { $Class } else { $Caller = (Get-PSCallStack)[1] if ($Caller.FunctionName -ne '<ScriptBlock>') { $Caller.FunctionName } elseif ($Caller.ScriptName) { $Ast = [System.Management.Automation.Language.Parser]::ParseFile($Caller.ScriptName, [ref]$Null, [ref]$Null) $Ast.EndBlock.Statements.where{ $_.IsClass }.Name } } foreach ($ClassName in $ClassNames) { $FormatData = @" <Configuration> <ViewDefinitions> <View> <Name>$ClassName</Name> <OutOfBand /> <ViewSelectedBy> <TypeName>$ClassName</TypeName> </ViewSelectedBy> <CustomControl> <CustomEntries> <CustomEntry> <CustomItem> <ExpressionBinding> <ScriptBlock> <![CDATA[$($ScriptBlock.ToString())]]> </ScriptBlock> </ExpressionBinding> </CustomItem> </CustomEntry> </CustomEntries> </CustomControl> </View> </ViewDefinitions> </Configuration> "@ $TempFile = [IO.Path]::GetTempPath() + "$ClassName.ps1xml" Out-File -InputObject $FormatData -LiteralPath $TempFile -Encoding ASCII Update-FormatData -PrependPath $TempFile } } } |