代码之家  ›  专栏  ›  技术社区  ›  Rytmis

是否可以在PowerShell中扩展索引器?

  •  2
  • Rytmis  · 技术社区  · 16 年前

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

    [编辑]显然正确的成员类型是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
    
    2 回复  |  直到 16 年前
        1
  •  1
  •   Rytmis    16 年前

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

        2
  •  1
  •   Mark Bertenshaw    10 年前

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

    下面的模块,我已经调用了 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代码中进行了条件编译,以使代码的行为类似于PowerShell4及更高版本中的适当脚本块,因此 $this 自动提供变量。否则,必须确保调用每个脚本块中的第一个参数 $这个 .

    下面是我的测试脚本, 试验-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;
    

    推荐文章