2

PowerShell 的类型扩展工具很简洁,但我还没有找到扩展索引器的方法——如果存在的话。我尝试为索引器属性(在 System.String 的情况下为 Chars)添加一个 ScriptProperty,为 getter(get_Chars)添加一个 ScriptMethod,但这两种方法似乎都没有成效。有可能吗,还是我在浪费时间?:)

[编辑] 显然,正确的成员类型是 ParameterizedProperty,但是当我尝试这样做时,我得到:

Add-Member : Cannot add a member with type "ParameterizedProperty". Specify a different 
type for the MemberTypes parameter.

At line:1 char:11
+ Add-Member <<<<  -MemberType ParameterizedProperty -Name Item -InputObject $string { "x" }
+ CategoryInfo          : InvalidOperation: (:) [Add-Member], InvalidOperationException
+ FullyQualifiedErrorId : CannotAddMemberType,Microsoft.PowerShell.Commands.AddMemberCommand
4

2 回答 2

1

我将得出结论,我收到的错误消息是这件事的最终决定。此外,经过进一步思考,很明显,我希望的那种扩展无论如何都不受这种机制的支持。:-)

于 2010-05-28T19:31:21.303 回答
1

您不能直接在 Powershell 中创建 ParameterizedProperty 属性,但您可以通过允许 Powershell 将 PSObject 包装在具有访问器属性的对象周围来间接创建它们。然后,将此 PSObject 设置为要向其添加属性的对象的 NoteProperty。在 C# 中,我们谈论的是this[]访问器。我编写了一个 Powershell 脚本,它创建了一个具有this[]访问器的最小 .NET 对象。为了使其尽可能通用,我尝试复制 ScriptProperty 成员的功能,并添加了两个类型的属性ScriptBlock- 一个用于Get块,另一个用于Set块。所以本质上,当用户设置this[]访问器时,它调用Set块,当用户从this[]访问器,它调用Get块。

以下模块,我称之为PSObjectWrappers.psm1

<#
    .SUMMARY
    Creates a new ParameterizedPropertyAccessor object.

    .DESCRIPTION
    Instantiates and returns an object compiled on the fly which provides some plumbing which allows a user to call a new Parameterized
    Property, which looks as if it is created on the parent object. In fact, a NoteProperty is created on the parent object which retrieves
    an instance of ParameterizedPropertyAccessor, which has a this[] accessor which Powershell wraps in a ParameterizedProperty object.
    When the this[] accessor is retrieved, it tries to retrieve a value via a Get script block. When the this[] accessor is updated, this 
    triggers a Set script block.

    .NOTES
    No actual variable value state is stored by this object.
    The C# code is conditionally compiled to take advantage of new functionality in Powershell 4. Before this version, the first parameter
    in the Set and Get script blocks must be "[PSObject] $this". From this version, the $this parameter is automatically created for the user.
#>
Function New-ParameterizedPropertyAccessor
{
    Param(
        # Contains the object on which the "ParameterizedProperty" will be added.
        [Parameter(Mandatory = $true, Position = 0)]
        [PSObject] $Parent,
        # The name of the parameterized property.
        [Parameter(Mandatory = $true, Position = 1)]
        [string] $Name,
        # Script block which will be called when the property is retrieved.
        # First parameter must be $this. Second parameter must be $key.
        [Parameter(Mandatory = $true, Position = 2)]
        [scriptblock] $Get,
        # Script block which will be called when the property is set.
        # First parameter must be $this. Second parameter must be $key. Third parameter must be $value.
        [Parameter(Mandatory = $true, Position = 3)]
        [scriptblock] $Set
    );

    # Note. You *MUST* ensure the next line starts at position 1 on the line. Likewise, the last line of the code *MUST*
    # start at position 1 on the line.

$csharpCode = @'
    using System;
    using System.Collections.Generic;
    using System.Management.Automation;

    public class ParameterizedPropertyAccessor
    {
        private PSObject _parentPsObject;
        private ScriptBlock _getBlock;
        private ScriptBlock _setBlock;

        public ParameterizedPropertyAccessor(PSObject parentPsObject, string propertyName, ScriptBlock getBlock, ScriptBlock setBlock)
        {
            _parentPsObject = parentPsObject;

            PSVariable psVariable = new PSVariable(propertyName, this, ScopedItemOptions.ReadOnly);
            PSVariableProperty psVariableProperty = new PSVariableProperty(psVariable);
            _parentPsObject.Properties.Add(psVariableProperty);

            _getBlock = getBlock;
            _setBlock = setBlock;
        }

        public object this[object key]
        {
            get
            {
#if WITH_CONTEXT
                return _getBlock.InvokeWithContext(null, new List<PSVariable> { new PSVariable("this", _parentPsObject) }, new object[] { key });
#else
                return _getBlock.Invoke(new object[] { _parentPsObject, key });
#endif
            }
            set
            {
#if WITH_CONTEXT
                _setBlock.InvokeWithContext(null, new List<PSVariable> { new PSVariable("this", _parentPsObject) }, new object[] { key, value });
#else
                _setBlock.Invoke(new object[] { _parentPsObject, key, value });
#endif
            }
        }
    }
'@;

    <#
    The version of the ScriptBlock object in Powershell 4 and above allows us to create automatically declared
    context variables. In this case, we are providing a $this object, like you would get if we were using a
    ScriptMethod or ScriptProperty member script. If we are using this version, then set the WITH_CONTEXT symbol 
    to conditionally compile a version of the C# code above which takes advantage of this.
    #>
    If ($PSVersionTable.PSVersion.Major -ge 4)
    {
        $compilerParameters = New-Object System.CodeDom.Compiler.CompilerParameters;
        $compilerParameters.CompilerOptions = "/define:WITH_CONTEXT";
        $compilerParameters.ReferencedAssemblies.Add( "System.dll" );
        $compilerParameters.ReferencedAssemblies.Add( "System.Core.dll" );
        $compilerParameters.ReferencedAssemblies.Add( ([PSObject].Assembly.Location) );
    }
    # Compiles the C# code in-memory and allows us to instantiate it.
    Add-Type -TypeDefinition $csharpCode -CompilerParameters $compilerParameters;

    # Instantiates the object.
    New-Object ParameterizedPropertyAccessor -ArgumentList $Parent,$Name,$Get,$Set;
}

请注意,我已经在 C# 代码中进行了条件编译,以使代码的行为类似于 Powershell 4 及更高版本中的正确 ScriptBlock,因此$this会自动提供一个变量。否则,您必须确保调用每个脚本块中的第一个参数$this

以下是我的测试脚本Test-PPA.ps1

<#
    .SYNOPSIS
    Test script for the ParameterizedPropertyAccessor object.
#>



<#
    .SYNOPSIS
    Create a new PSCustomObject which will contain a NoteProperty called Item accessed like a ParameterizedProperty.
#>
Function New-TestPPA
{
    # Instantiate our test object.
    $testPPA = New-Object -TypeName PSCustomObject;

    # Create a new instance of our PPA object, added to our test object, providing it Get and Set script blocks.
    # Note that currently the scripts are set up for Powershell 4 and above. If you are using a version of Powershell
    # previous to this, comment out the current Param() values, and uncomment the alternate Param() values.
    $ppa = New-ParameterizedPropertyAccessor -Parent $testPPA -Name Item -Get `
    {
        Param(
            <#
            [Parameter(Mandatory = $true, Position = 0)]
            [PSObject] $this,
            [Parameter(Mandatory = $true, Position = 1)]
            [string] $Key
            #>
            [Parameter(Mandatory = $true, Position = 0)]
            [string] $Key
        )
        $this._ht[$Key];
    } -Set {
        Param(
            <#
            [Parameter(Mandatory = $true, Position = 0)]
            [PSObject] $this,
            [Parameter(Mandatory = $true, Position = 1)]
            [string] $Key,
            [Parameter(Mandatory = $true, Position = 2)]
            [string] $Value
            #>
            [Parameter(Mandatory = $true, Position = 0)]
            [string] $Key,
            [Parameter(Mandatory = $true, Position = 1)]
            [string] $Value
        )
        $this._ht[$Key] = $Value;
    };

    # Add a HashTable <_ht> used as our backing store. Note that this could be any keyed collection type object.
    $testPPA | Add-Member -MemberType NoteProperty -Name _ht -Value @{} -PassThru;
}


[string] $scriptDir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent;
Import-Module $scriptDir\PSObjectWrappers.psm1;

# Create test object.
$testPPA = New-TestPPA;

# Note that "Item" property is actually a NoteProperty of type ParameterizedPropertyAccessor.
Write-Host "Type '`$testPPA | gm' to see Item NoteProperty.";

# Note that it is the ParameterizedPropertyAccessor object retrieved that has a ParameterizedProperty.
# Also note that Powershell has named this property "Item".
Write-Host "Type '`$testPPA.Item | gm' to see Item ParameterizedProperty";

# Step through what happens when we "set" the "parameterized" Item property.
# Note that this is actually retrieving the Item NoteProperty, and then setting its default accessor, which calls
# the 'Set' ScriptBlock.

Write-Host "";
Write-Host "Setting Name value";
Write-Host "... to 'Mark'."
$testPPA.Item["Name"] = "Mark";

# Step through what happens when we "get" the "parameterized" Item property.
# Note that this is actually retrieving the Item NoteProperty, and then retrieving its default accessor, which calls
# the 'Get' ScriptBlock.

Write-Host "";
Write-Host "Retrieving Name value:";
$temp = $testPPA.Item["Name"];
Write-Host $temp;

请注意,如果您使用的是 Powershell 4 之前的版本,则必须按照指示更改脚本块。

于 2016-04-14T11:47:13.863 回答