2010-05-18 2 views
2

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

[Изменить] Видимо соответствующий тип член 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 

ответ

0

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

0

Вы не можете создавать свойства ParameterizedProperty непосредственно в Powershell, но вы можете косвенно создавать их, разрешая Powershell обтекать объект PSO вокруг объекта, обладающего свойством 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.

Ниже мой тестовый скрипт, Тест-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.

Смежные вопросы