PowerShell 函数参数 - 按引用还是按值? [英] PowerShell Function parameters - by reference or by value?

查看:16
本文介绍了PowerShell 函数参数 - 按引用还是按值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

因此,我尝试查找此问题的答案,发现普遍可用的答案是 PowerShell 按值传递参数.这些普遍接受的解决方案都贴出示例代码来证明他们的断言,类似于以下内容:

So, I tried looking up the answer to this question, and found the generally available answer is that PowerShell passes parameters by value. These generally accepted solutions all post sample code to prove their assertions, similar to the following:

Function add1 ($parameter)
{
    Write-Host "    In Function: `$parameter = $parameter"
    Write-Host "    In Function: `$parameter += 1"
    $parameter += 1
    Write-Host "    In Function: `$parameter = $parameter"
}

cls
$a = 1
Write-Host "Before function: `$a = $a"
add1 $a
Write-Host " After function: `$a = $a"

结果如下:

Before function: Run Command: $a = 1
    In Function: $parameter: 1
    In Function: Run Command: $parameter += 1
    In Function: $parameter: 2
 After function: $a: 1

从而证明参数是按值传递的,对吗?好吧,我花了很多时间来对我正在编写的函数进行故障排除.该函数向我传递给函数的 PSCustomObject 添加了几个额外的 NoteProperty 项,并且我的程序会抛出各种错误,指出 NoteProperty 已经存在,即使我没有修改父作用域中的原始对象,只有函数内部.

Thus proving that parameters are passed by value, right? Well, I was having a heck of a time troubleshooting a function I was writing. The function added a couple of additional NoteProperty items to a PSCustomObject I pass in to the function, and my program would throw all sorts of errors saying that the NoteProperty already existed, even though I had not modified the original object in the parent scope, only inside the function.

因此,我设置了上述代码的一个版本来使用 [PSCustomObject] 类型的参数进行测试,如下所示:

So, I set up a version of the above code to test using parameter of type [PSCustomObject], like so:

Function F1($Obj)
{
    'Function F1: Run command: $Obj.FirstValue = 11'
    $Obj.FirstValue = 11
    "             `$Obj.Name: $($StartObject.Name)"
    "             `$Obj.FirstValue: $($StartObject.FirstValue)"
    "             `$Obj.SecondValue: $($StartObject.SecondValue)"
}

Function F2($Obj)
{
    'Function F2: Run command: $Obj | Add-Member -MemberType NoteProperty -Name SecondValue -Value 33'
    $obj | Add-Member -MemberType NoteProperty -Name SecondValue -Value 33
    "             `$Obj.Name: $($StartObject.Name)"
    "             `$Obj.FirstValue: $($StartObject.FirstValue)"
    "             `$Obj.SecondValue: $($StartObject.SecondValue)"
}

cls
Remove-Variable StartObject
"Main script: Run command: `$StartObject = [PSCustomObject]@{Name='Original';FirstValue=22}"
$StartObject = [PSCustomObject]@{Name='Original';FirstValue=22}
"             `$StartObject.Name: $($StartObject.Name)"
"             `$StartObject.FirstValue: $($StartObject.FirstValue)"
"             `$StartObject.SecondValue: $($StartObject.SecondValue)"
'Run command: F1 $StartObject'
" "
F1 $StartObject
" "
"Main script: `$StartObject.Name: $($StartObject.Name)"
"             `$StartObject.FirstValue: $($StartObject.FirstValue)"
"             `$StartObject.SecondValue: $($StartObject.SecondValue)"
"Run command: F2 $StartObject"
" "
F2 $StartObject
" "
"Main script: `$StartObject.Name = $($StartObject.Name)"
"             `$StartObject.FirstValue = $($StartObject.FirstValue)"
"             `$StartObject.SecondValue = $($StartObject.SecondValue)"

这个凌乱的程序产生以下输出:

This messy piece of programming produces the following output:

Main script: Run command: $StartObject = [PSCustomObject]@{Name='Original';FirstValue=22}
             $StartObject.Name: Original
             $StartObject.FirstValue: 22
             $StartObject.SecondValue: 
Run command: F1 $StartObject

Function F1: Run command: $Obj.FirstValue = 11
             $Obj.Name: Original
             $Obj.FirstValue: 11
             $Obj.SecondValue: 

Main script: $StartObject.Name: Original
             $StartObject.FirstValue: 11
             $StartObject.SecondValue: 
Run command: F2 @{Name=Original; FirstValue=11}

Function F2: Run command: $Obj | Add-Member -MemberType NoteProperty -Name SecondValue -Value 33
             $Obj.Name: Original
             $Obj.FirstValue: 11
             $Obj.SecondValue: 33

Main script: $StartObject.Name = Original
             $StartObject.FirstValue = 11
             $StartObject.SecondValue = 33

这些结果清楚地表明,当使用 [PSCustomObject] 参数时,函数内的任何修改都发生在传递的对象上,因此通过引用传递.无论将我的参数定义为 [PSCustomObject]$Obj 还是不输入它们,都会发生这种行为.这本身并不是一个大问题,但问题是我无法在我浏览的任何文档中找到这个小小的信息宝石.我查看了一些教程网站和微软自己关于函数参数的文档,但没有看到这个异常.

These results clearly show that when [PSCustomObject] parameters are used, any modifications within the function take place on the passed object, thus pass by reference. This behavior happens regardless of defining my parameters as [PSCustomObject]$Obj, or leaving them untyped. This is not a huge problem in and of itself, but the problem is that I was unable to find this little gem of information in any of the documentation I looked through. I checked a few tutorial sites and Microsoft's own documentation on Function Parameters, but did not see this exception.

所以,我的问题归结为:有没有人找到任何文档来支持我的理论,即虽然大多数参数默认按值传递,但在涉及对象时它们是按引用传递的?

So, my question boils down to this: Has anyone found any documentation to support my theory that while most parameters default to passing by value, they are passed by reference when objects are concerned?

我完全愿意相信我在某处遗漏了一些文档,所以请...指出并告诉我我的方法错误!:)

I am perfectly willing to believe that I missed some documentation somewhere, so please...point it out and show me the error of my ways! :)

非常感谢

推荐答案

注意:以下也适用将一个变量赋值给另一个变量:$b = $a ...
* 使 $b 引用 $a 相同的对象 如果 $a 的值是一个实例引用类型,
* 使$b 接收$a 值的独立副本,如果后者是值类型的实例em>.

Note: The following also applies to assigning one variable to another: $b = $a ...
* makes $b reference the very same object that $a does if $a's value is an instance of a reference type,
* makes $b receive an independent copy of $a's value if the latter is an instance of a value type.

  • PowerShell 默认使用 by-(variable)-value 传递;也就是说,传递的是变量的内容,而不是对变量本身的引用.

  • PowerShell uses by-(variable)-value passing by default; that is, the content of a variable is passed, not a reference to the variable itself.

  • 如果您想通过-(变量)-引用 传递,即如果您想传递对变量本身的引用,则需要额外的努力,允许被调用者获取变量的内容并分配新内容;在最简单的形式中,您可以使用 [ref] 类型的参数(类似于 C# 中的 ref 参数).但是,请注意,在 PowerShell 中很少需要这种技术.
  • Extra effort is needed if you want by-(variable)-reference passing, i.e. if you want to pass a reference to a variable itself, allowing the callee to both get the variable's content and to assign new content; in the simplest form, you can use a [ref]-typed parameter (akin to ref parameters in C#). However, note that this technique is rarely necessary in PowerShell.

内容是调用者所见内容的副本还是对相同对象的引用取决于数据类型 内容:

Whether that content is a copy of what the caller sees or a reference to the very same object depends on the data type of the content:

  • 如果内容恰好是 .NET reference type - 正如 [pscustomobject] 是 - content 是一个对象引用,并且被调用者因此可以通过看到与调用者完全相同的对象来修改该对象.

  • If the content happens to be an instance of a .NET reference type - as [pscustomobject] is - that content is an object reference, and the callee can therefore potentially modify that object, by virtue of seeing the very same object as the caller.

  • 如果您想传递引用类型实例的副本(克隆),请注意没有创建通用机制一:
    • 您可以创建类型实例的副本如果它们实现了 System.ICloneable 接口通过调用它们的 .Clone() 方法,但注意是否执行取决于实现类型克隆[1];正是出于这个原因,不鼓励使用此接口;实际上,实现它的类型通常执行克隆,特别是数组、数组列表 (System.Collections.ArrayList) 和哈希表(但请注意 [ordered] 哈希表 (System.Collections.Specialized.OrderedDictionary) 根本没有实现 ICloneable.
    • 此外,在 PowerShell 中,您可以对 [pscustomobject] 类型的实例调用 .psobject.Copy() 以创建副本.(不要在任何其他类型的对象上使用这个方法,因为它实际上是一个无操作的对象.)类似地,单个 .NET 类型可以实现自定义克隆方法.
    • If you want to pass a copy (clone) of a reference-type instance, note that there is no universal mechanism for creating one:
      • You can create copies of instances of types if they implement the System.ICloneable interface by calling their .Clone() method, but note that it is up to the implementing type whether to perform shallow or deep cloning[1]; it is for that reason that use of this interface is discouraged; in practice, types that do implement it typically perform shallow cloning, notably arrays, array lists (System.Collections.ArrayList) and hashtables (but note that an [ordered] hashtable (System.Collections.Specialized.OrderedDictionary) doesn't implement ICloneable at all.
      • Additionally, in PowerShell, you can call .psobject.Copy() on instances of type [pscustomobject] to create a shallow copy. (Do not use this method on objects of any other type, where it will effectively be a no-op.) Similarly, individual .NET types may implement custom cloning methods.

      相比之下,如果该内容是 .NET 值类型 - 例如,[int] - 或一个string[2],传递该实例的独立副本.

      If, by contrast, that content is an instance of a .NET value type - e.g., [int] - or a string[2], an independent copy of that instance is passed.

      这种区别是 .NET 的基础,而不是特定于 PowerShell;例如,这也是在 C# 中传递参数的方式.

      This distinction is fundamental to .NET, not specific to PowerShell; it is also how arguments are passed in C#, for instance.

      要确定给定变量的值是值类型的实例还是引用类型的实例,请使用以下内容:

      To determine whether a given variable's value is an instance of a value type or a reference type, use something like the following:

      1, (Get-Date), (Get-Item /) |  # sample values
        foreach {
          '{0} is of type {1}; is it a value type? {2}' -f $_, 
                                                           $_.GetType(),
                                                           $_.GetType().IsValueType
        }
      

      您会看到类似的内容:

      1 is of type System.Int32; is it a value type? True
      4/30/2020 12:37:01 PM is of type System.DateTime; is it a value type? True
      / is of type System.IO.DirectoryInfo; is it a value type? False
      

      如果您查找给定 .NET 类型的文档,请说 System.DateTime,继承信息以Object开头->ValueType 用于值类型;在 C# 术语中,值类型是 structenum,而引用类型是 class.

      If you look up the documentation for a given .NET type, say System.DateTime, the inheritance information will start with Object -> ValueType for value types; in C# terms, a value type is either a struct or an enum, whereas a reference type is a class.

      这里有两个不相关的概念,而且它们都使用术语(by-)value(by-)reference 可能会让人感到困惑:

      There are two unrelated concepts at play here, and the fact that they both use the terms (by-)value and (by-)reference can get confusing:

      • By-(variable)-value vs. by-(variable)-reference parameter-passingdata-holder(变量)概念:

      • By-(variable)-value vs. by-(variable)-reference parameter-passing is a data-holder (variable) concept:

      • 它描述了在参数传递时,是传递一个变量的(按值)还是对变量本身的引用[3](参考).
      • It describes whether, on parameter passing, a variable's value is passed (by value) or a reference to the variable itself[3] (by reference).

      引用类型与值类型纯粹是一个数据概念:

      Reference types vs. value types is purely a data concept:

      • 也就是说,出于技术原因,.NET 中的任何对象要么是值类型的实例(存储在 堆栈 上)要么是引用类型(存储在 ).前者的实例直接存储在变量中,而后者则通过引用存储.因此,复制一个变量——例如,在按值参数传递的上下文中——意味着:
        • 要么:制作一个值类型实例本身的副本,从而产生一个独立的数据副本.
        • 或:制作引用类型实例的副本reference引用的副本仍然指向同一个对象,然而,这就是为什么即使是按变量值传递的引用类型实例也能被被调用者直接看到(通过他们的参考副本的方式).
        • That is, for technical reasons, any object in .NET is either an instance of a value type (stored on the stack) or a reference type (stored on the heap). Instances of the former are directly stored in variables, whereas the latter are stored by way of a reference. Therefore, copying a variable value - e.g., in the context of by-value parameter-passing - means:
          • either: making a copy of a value-type instance itself, resulting in an independent data copy.
          • or: making a copy of a reference-type instance reference; a copy of a reference still points to the same object, however, which is why even by-variable-value passed reference-type instances are directly seen by the callee (by way of their reference copy).

          [1] 克隆意味着作为引用类型实例的属性值按原样复制 - 作为引用 - 这意味着克隆的属性值再次引用相同的对象作为原件.深度克隆意味着这些属性值被递归地克隆.深度克隆成本高昂,而且并非总是可行.

          [1] Shallow cloning means that property values that are reference-type instances are copied as-is - as references - which means that the clone's property value again references the very same object as the original. Deep cloning means that such property values are cloned themselves, recursively. Deep cloning is expensive and isn't always possible.

          [2] 字符串 ([string]) 技术上也是 reference 类型的实例,但是,如一个异常,它被视为值类型;请参阅此答案了解此异常背后的基本原理.

          [2] A string ([string]) is technically also an instance of a reference type, but, as an exception, it is treated like a value type; see this answer for the rationale behind this exception.

          [3] 另一种思考方式:传递指向变量存储其值的位置的引用(指针).这允许被调用者不仅可以访问变量的值,还可以分配一个(新)值.

          [3] Another way of thinking about it: a reference (pointer) to the location where the variable stores its value is passed. This allows the callee to not only access the variable's value, but also to assign a (new) value.

          这篇关于PowerShell 函数参数 - 按引用还是按值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆