【发布时间】:2021-10-02 02:47:21
【问题描述】:
Powershell 以两种不同的方式通过引用支持参数。任何变量都可以使用[Ref] 作为类型通过引用传递,或者基本上我猜想转换为特殊的[Ref] 类型。或者,像[Hashtable] 这样的复杂数据类型总是通过引用传递。前一种方法需要在更改值或将变量传递给另一个类方法或函数时使用.value。简而言之,这就是实践中的样子。
# implicit By Reference
class One_i {
static [Void] Act ([System.Collections.Generic.List[string]] $data) {
$data.Add("One_i::Act(): $(Get-Date)")
[Two_i]::Act($data)
}
}
class Two_i {
static [Void] Act ([System.Collections.Generic.List[string]] $data) {
$data.Add("Two_i::Act(): $(Get-Date)")
}
}
# explicit By Reference
class One_e {
static [Void] Act ([Ref] $data) {
$data.value.Add("One_e::Act(): $(Get-Date)")
[Two_e]::Act($data.value)
}
}
class Two_e {
static [Void] Act ([Ref] $data) {
$data.value.Add("Two_e::Act(): $(Get-Date)")
}
}
CLS
$data_i = [System.Collections.Generic.List[string]]::new()
$data_i.Add("Init_i: $(Get-Date)")
[One_i]::Act($data_i)
$data_i.Add("Finalize_i: $(Get-Date)")
foreach ($item in $data_i) {
Write-Host "$item"
}
Write-Host
$data_e = [System.Collections.Generic.List[string]]::new()
$data_e.Add("Init_e: $(Get-Date)")
[One_e]::Act($data_e)
$data_e.Add("Finalize_e: $(Get-Date)")
foreach ($item in $data_e) {
Write-Host "$item"
}
Write-Host
我对我更喜欢哪种方法感到矛盾。一方面[Ref] 明确了哪些变量是By Ref,这很有用。然而,对.value 的需求使代码更难阅读,增加了工作量,并创造了忘记和引发错误的机会。我是这样测试性能的...
(measure-Command {
$data_i = [System.Collections.Generic.List[string]]::new()
$data_i.Add("Init_i: $(Get-Date)")
foreach ($i in 1..1000) {
[One_i]::Act($data_i)
}
$data_i.Add("Finalize_i: $(Get-Date)")
}).TotalSeconds
(measure-Command {
$data_e = [System.Collections.Generic.List[string]]::new()
$data_e.Add("Init_e: $(Get-Date)")
foreach ($e in 1..1000) {
[One_e]::Act($data_e)
}
$data_e.Add("Finalize_e: $(Get-Date)")
}).TotalSeconds
并且隐式似乎要快得多,但不足以仅根据性能来为隐式进行论证。当我试图决定采用哪种方法时,这导致我提出了许多问题,或者可能意识到每种方法在某些情况下都是更好的选择,并且在单个程序中使用这两种方法实际上是有好处的。
1:为什么复杂类型与简单类型的处理方式不同?不一致的行为对我来说似乎很奇怪,我怀疑如果我了解行为不同的原因,我会学到一些基础知识。
2:除了是否需要使用.value之外,隐式和显式引用之间还有其他区别吗?尤其是其中一种方法是否存在任何潜在问题?
3:是否有强制复杂类型按值的机制?那会有什么用例?
4:我找到了this thread,有一条评论说最好避免引用(但没有解释)。但是,我还发现对依赖注入的引用优于全局变量和/或单例模式,并且按引用是 DI 的基础。是否有烦人的重要事项我需要在这里注意,特别是因为它与 PowerShell/类/依赖注入有关,因为 PowerShell 类没有像其他语言那样完全实现。我在 PS 5.1 中工作并且不会很快迁移到 Core,但很高兴知道 Core 和前 Core 实现之间是否存在根本差异。
编辑:基于@mclayton 的回答和我对“改变价值”与“突变”的理解,我尝试“打破”这样的参考
# implicit By Reference
class One_i {
static [Void] Act ([System.Collections.Generic.List[string]] $data) {
$data.Add("One_i::Act(): $(Get-Date)")
[Two_i]::Act($data)
$data = [System.Collections.Generic.List[string]]::new()
}
}
# explicit By Reference
class One_e {
static [Void] Act ([Ref] $data) {
$data.value.Add("One_e::Act(): $(Get-Date)")
[Two_e]::Act($data.value)
$data.value = [System.Collections.Generic.List[string]]::new()
}
}
我在“显式”示例中尝试了$data.value = [System.Collections.Generic.List[string]]::new() 和$data = [System.Collections.Generic.List[string]]::new(),但实际上似乎没有一个主题会导致问题。在所有三种情况下,控制台输出都是“正确的”。这表明我根本不明白改变价值和突变之间的区别。
也就是说,由于它与我提出问题的最初原因有关,即决定我要使用两种方法中的哪一种,我倾向于我所说的“隐式”,因为它确实使代码更简单一些通过消除对.value 的需要,并且变量的引用性质不明显并不会改变我必须理解它是引用的事实,无论我做什么。我真的希望我能找到一个讨论这两种方法的相对优点的线程,但到目前为止,我的搜索未能找到任何有用的东西。
Edit2:所以,我发现this mklement0 说这两种方法在功能上确实没有区别。我突然想到,我可以通过使用[Ref] 然后将$ref.value 分配给局部变量来获得两全其美的效果,以简化方法或函数中的事情。暂时的感觉是最好的答案。它在每个函数或方法的顶部添加了一行,但使调用在行为上更加明显,并简化了在方法或函数体中的使用。
# explicit By Reference with sugar
class One_x {
static [Void] Act ([Ref] $data) {
$localData = $data.Value
$localData.Add("One_x::Act(): $(Get-Date)")
[Two_e]::Act(([Ref]$localData))
}
}
class Two_x {
static [Void] Act ([Ref] $data) {
$localData = $data.Value
$localData.Add("Two_x::Act(): $(Get-Date)")
}
}
【问题讨论】:
标签: powershell pass-by-reference pass-by-value