我有一些非常奇怪的行为,我想这与点源有关,但我无法理解
包含两个函数和一个类的脚本sourced.ps1
:
class MyData {
[string] $Name
}
function withClass() {
$initialData = @{
Name1 = "1";
Name2 = "2";
}
$list = New-Object Collections.Generic.List[MyData]
foreach ($item in $initialData.Keys) {
$d = [MyData]::new()
$d.Name = $item
$list.Add($d)
}
}
function withString() {
$initialData = @{
Name1 = "1";
Name2 = "2";
}
$list = New-Object Collections.Generic.List[string]
foreach ($item in $initialData.Keys) {
$list.Add($item)
}
}
我还有一个脚本caller.ps1
,它点源于上面的脚本并调用函数:
$ErrorActionPreference = 'Stop'
. ".sourced.ps1"
withClass
然后,我通过在shell中执行.caller.ps1
(带PS Core的Win终端(来调用caller.ps1
。
以下是我无法解释的行为:如果我再次调用.caller.ps1
、.sourced.ps1
和caller.ps1
,我会得到错误:
Line |
14 | $list.Add($d)
| ~~~~~~~~~~~~~
| Cannot find an overload for "Add" and the argument count: "1".
但是,如果我将caller.ps1
改为调用withString
函数,那么无论我调用caller.ps1
和sourced.ps1
多少次,一切都会正常工作。
此外,如果我首先用withString
调用caller.ps1
,然后将其更改为withClass
,则没有任何错误。
我想使用模块会更正确,但我首先感兴趣的是这种奇怪行为的原因。
编写于PowerShell 7.2.1
-
一个给定的脚本文件是点源和直接执行的(按任意顺序,无论频率如何(会在其中创建
class
定义的连续版本-这些都是不同的.NET类型,即使它们的结构相同。可以说,没有充分的理由这样做,行为可能是一个错误。 -
这些版本具有相同的全名(在脚本的顶级作用域中创建的PowerShell
class
定义只有名称,没有命名空间(,但位于不同的动态(内存(程序集中,这些程序集的版本号的最后一个组件不同,彼此之间存在阴影,哪个是效果取决于上下文:- 点源于此类脚本的其他脚本始终可以看到新版本
- 在脚本本身内部,无论它本身是直接执行还是点源执行:
- 在PowerShell代码中,原始版本保持有效
- 在二进制cmdlet(特别是
New-Object
(中,新版本生效 - 如果混合这两种方法来访问脚本中的类,则可能会发生类型不匹配,这就是您的情况-请参阅下面的示例代码
-
虽然您可以通过一致地使用
::new()
或New-Object
引用类来从技术上避免此类错误,但最好避免对包含class
定义的脚本文件执行直接执行和点源
示例代码:
-
将代码保存到脚本文件中,例如
demo.ps1
-
执行两次。
- 首先,通过直接执行:
.demo.ps1
- 然后,通过点源:
. .demo.ps1
- 首先,通过直接执行:
-
您看到的类型不匹配错误将在第二次执行期间发生。
- 注意:错误消息
Cannot find an overload for "Add" and the argument count: "1"
有点模糊;它试图表达的是,.Add()
方法不能用给定类型的参数调用,因为它需要[MyData]
的新版本的实例,而::new()
创建了原始版的实例
- 注意:错误消息
# demo.ps1
# Define a class
class MyData { }
# Use New-Object to instantiate a generic list based on that class.
# This passes the type name as a *string*, and instantiation of the
# type happens *inside the cmdlet*.
# On the second execution, this will use the *new* [MyData] version.
Write-Verbose -Verbose 'Constructing list via New-Object'
$list = New-Object System.Collections.Generic.List[MyData]
# Use ::new() to create an instance of [MyData]
# Even on the second execution this will use the *original* [MyData] version
$myDataInstance = [MyData]::new()
# Try to add the instance to the list.
# On the second execution this will *fail*, because the [MyData] used
# by the list and the one that $myDataInstance is an instance of differ.
$list.Add($myDataInstance)
请注意,如果使用$myDataInstance = New-Object MyData
,则类型不匹配将消失。
类似地,如果您坚持使用::new()
并使用它实例化列表:$list = [Collections.Generic.List[MyData]]::new()
,它也会消失