Arraylist是否通过PowerShell中的引用传递给函数

时间:2014-01-25 03:18:56

标签: powershell arraylist

我今天发现当我从函数中的arraylist中删除一个值时,我传递给函数的arraylist会被更改。下面的代码似乎暗示传递是通过引用发生的。那为什么会这样?这是设计还是某种bug? (我在Win 8.1上使用v4)

function myfunction {
    param (
        [System.Collections.ArrayList]$local
    )
        "`$local: " + $local.count
        "removing 1 from `$local"
        $local.RemoveAt(0)     
        "`$local:" + $local.count       
}

[System.Collections.ArrayList]$names=(Get-Content c:\temp\names.txt)

"`$names: " + $names.count
 myfunction -local $names      
"`$names: " + $names.count

结果:

$names: 16
$local: 16
removing 1 from $local
$local:15
$names: 15

3 个答案:

答案 0 :(得分:6)

这是设计使然,不是错误。 ref传递数组,集合和哈希表。这与添加或从数组中删除的行为不同的原因是该操作在函数范围内创建了一个新数组。无论何时在函数内部创建新变量,它都将作用于该函数。 $ local.RemoveAt(0)不会创建新的$ local,它只调用父脚本中现有$ local的函数。如果你希望函数在它自己的$ local上运行,你需要在函数内部显式创建一个新函数。

因为它是由ref,这不起作用:

 $local = $local

您仍将在父作用域中引用$ local。 但您可以使用clone()方法创建它的新副本

  function testlocal {
   param ([collections.arraylist]$local)
   $local = $local.Clone()
   $local.RemoveAt(0)
   $local
 }

$local = [collections.arraylist](1,2,3)

'Testing function arraylist'    
testlocal $local
''
'Testing local arraylist'
$local


Testing function arraylist
2
3

Testing local arraylist
1
2
3

答案 1 :(得分:4)

mjolinor's helpful answer 提供关键指针:要让函数在输入ArrayList的副本上运行,它必须是首先通过.Clone() 克隆

不幸的是,解释提供了为什么需要不正确: [1]

没有特定于PowerShell的变量行为发挥作用; 行为是.NET框架本身的基础 ,这是PowerShell的基础:

变量在技术上通过值传递(默认情况下 [2] ),但是 意味着什么取决于变量的类型

  • 对于 value types ,对于哪些变量直接包含数据 实际数据的副本 已经成就。
  • 对于 reference types ,对于哪些变量只包含数据的引用引用的副本 ,导致 有效按引用传递

因此,在目前的情况下,因为[System.Collections.ArrayList]引用类型(使用-not [System.Collections.ArrayList].IsValueType验证),参数$local按设计点与调用范围中的变量$names完全相同的ArrayList实例

不幸的是, PowerShell可以通过某些操作通过在幕后克隆 对象来掩盖正在发生的事情:

  • 使用+=附加到数组([System.Object[]]):

     $a = 1, 2, 3  # creates an instance of reference type [Object[]]
     $b = $a       # $b and $a now point to the SAME array
     $a += 4       # creates a NEW instance; $a now points to a DIFFERENT array.
    
  • 使用+=附加到[System.Collections.ArrayList]个实例:

    • 如果是数组[System.Object[]),则必须创建新实例 - 因为数组是的定义已修复大小 - 在使用[System.Collections.ArrayList] 时,不幸的是,PowerShell会将+=实例静静地转换为数组,因此显然也会创建一个新对象,即使[System.Collections.ArrayList] 可以增长,也就是使用.Add()方法。

      $al = [Collections.ArrayList] @(1, 2, 3)  # creates an ArrayList
      $b = $al       # $b and $al now point to the SAME ArrayList
      $al += 4       # !! creates a NEW object of type [Object[]]
      # By contrast, this would NOT happen with: $al.Add(4)
      
  • 解构数组:

     $a = 1, 2, 3     # creates an instance of reference type [Object[]]
     $first, $a = $a  # creates a NEW instance
    

[1] mjolinor的误解是围绕从父(祖先)范围继承/遮蔽变量:参数声明隐式地 本地变量声明。也就是说,在输入testlocal() $local时,已经是一个包含作为参数传递的内容的局部变量 - 它永远不会看到同名的祖先变量。以下代码段演示了这一点:function foo([string] $local) { "`$local inside foo: $local" }; $local = 'hi'; "`$local in calling scope: $local"; foo; foo 'bar' - foo()永远不会看到调用范围的$local定义。

[2]请注意,某些.NET语言(例如,C#中的ref)甚至PowerShell本身([ref])也允许传递变量引用,因此本地参数实际上只是调用范围变量的别名,但此功能与值/引用类型二分法无关。

答案 2 :(得分:-1)

如果您传递myfunction -local $names,那么$ names = $ local,看起来您的函数是删除$ local @ position 1(影响$ names,请记住)。下次你读取它已被修改的变量时,修复就是将grep $ names变量加倍,将计数返回到16。

[System.Collections.ArrayList]$names=(Get-Content c:\temp\names.txt)

"`$names: " + $names.count
 myfunction -local $names      
# $names variable is altered now, so re-run grep.
[System.Collections.ArrayList]$names=(Get-Content c:\temp\names.txt)
"`$names: " + $names.count

或者重新编写/管道您的函数作为跳过而不是动态删除???

Select-Object -Skip 1

以下是一些进一步的证明 - 请注意,write-host命令将显示函数同时编辑的两个变量。

function myfunction {
    param (
        [System.Collections.ArrayList]$local
    )
Write-Host $names
Write-Host $local
        "`$local: " + $local.count
        "removing 1 from `$local"
        $local.RemoveAt(0)     
        "`$local: " + $local.count 
Write-Host $names
Write-Host $local      
}

[System.Collections.ArrayList]$names=(Get-Content c:\temp\names.txt)

"`$names: " + $names.count
 myfunction -local $names  
"`$names: minus 1 - bork bork bork " + $names.count
[System.Collections.ArrayList]$names=(Get-Content c:\temp\names.txt)    
"`$names: " + $names.count