Use-ClassAccessors.ps1
<#PSScriptInfo
.Version 0.1.3 .Guid 19631007-aef4-42ec-9be2-1cc2854222cc .Author Ronald Bode (iRon) .CompanyName .Copyright .Tags Accessors Getter Setter Class get_ set_ TypeData .License https://github.com/iRon7/Use-ClassAccessors/LICENSE.txt .ProjectUri https://github.com/iRon7/Use-ClassAccessors .Icon https://raw.githubusercontent.com/iRon7/Use-ClassAccessors/master/Use-ClassAccessors.png .ExternalModuleDependencies .RequiredScripts .ExternalScriptDependencies .ReleaseNotes .PrivateData #> <# .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> } > [!NOTE] > Any (single) item array will be unrolled if the `[Object]` type is used for the getter method. ### 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() } $Methods = if ($Property) { $TargetType.GetMethod("get_$Property") $TargetType.GetMethod("set_$Property") } else { $NativeProperties = $TargetType.GetProperties() $NativeNames = if ($NativeProperties) { $NativeProperties.Name } $TargetType.GetMethods().where{ -not $_.IsStatic -and ($_.Name -Like 'get_*' -or $_.Name -Like 'set_*') -and $_.Name -NotLike '???__*' -and $_.Name.SubString(4) -notin $NativeNames } } $Accessors = [Ordered]@{} 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." } } } } |