Можно ли расширить индексаторы в PowerShell? - PullRequest
2 голосов
/ 18 мая 2010

Средство расширения типов PowerShell прекрасно, но я еще не нашел способ - если таковой существует - расширить индексатор. Я пытался добавить ScriptProperty для свойства indexer (Chars в случае System.String) и ScriptMethod для getter (get_Chars), но ни один из подходов не кажется плодотворным. Это вообще возможно, или я трачу свое время? :)

[Edit] Видимо, правильный тип элемента - 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 ]

0 голосов
/ 14 апреля 2016

Вы не можете создавать свойства ParameterizedProperty непосредственно в Powershell, но вы можете косвенно создавать их, позволяя Powershell обернуть объект PSObject вокруг объекта, обладающего свойством accessor. Затем вы делаете этот объект PSObject объектом NoteProperty для объекта, к которому хотите добавить свойство. В C # мы говорим о this[] аксессоре. Я написал скрипт Powershell, который создает минимальный объект .NET, имеющий this[] аксессор. Чтобы сделать это как можно более универсальным, я попытался скопировать то, что делает член 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 #, чтобы код работал как правильный ScriptBlock в Powershell 4 и выше, поэтому автоматически предоставляется переменная $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.

0 голосов
/ 28 мая 2010

Я собираюсь сделать вывод, что полученное сообщение об ошибке - последнее слово по этому вопросу. Кроме того, при дальнейшем рассмотрении стало очевидно, что то расширение, на которое я надеялся, так или иначе не поддерживается этим механизмом. : -)

...