运行 点源脚本执行后出错
Error when running a dot-sourced script after executing it
我有一些非常奇怪的行为,我想这与点源不知何故有关,但我无法理解它。这是我拥有的:
包含两个函数和 class:
的脚本 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(具有 PS 核心的 Win 终端)中执行 .\caller.ps1
来调用 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
定义只有 name, no namespace) 但它们位于不同的动态(内存中)程序集中,它们的版本号的最后一个组件不同,shadow 互为影响,哪个是效果取决于上下文:
- 点源此类脚本的其他脚本始终看到 新 版本。
- 在脚本本身内部,不管它本身是直接执行的还是点源的:
- 在 PowerShell 代码中,原始版本仍然有效。
- 在 binary cmdlets 中,特别是
New-Object
,new 版本生效。
- 如果你混合使用这两种方式访问脚本里面的class,就会出现类型不匹配,也就是你的情况发生了什么 - 请参阅下面的示例代码。
虽然您可以通过 一致地 使用 ::new()
或 New-Object
引用 class,最好 避免同时执行包含 class
定义的脚本文件的直接执行和点源。
示例代码:
将代码保存到脚本文件中,例如,demo.ps1
执行两次.
- 首先,直接执行:
.\demo.ps1
- 然后,通过dot-sourcing:
. .\demo.ps1
您看到的类型不匹配错误将在第二次执行期间发生。
- 注意:错误消息
Cannot find an overload for "Add" and the argument count: "1"
有点晦涩;它试图表达的是 .Add()
方法不能用给定类型 的参数 调用,因为它需要 new 的实例[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,它也会消失:$list = [Collections.Generic.List[MyData]]::new()
我有一些非常奇怪的行为,我想这与点源不知何故有关,但我无法理解它。这是我拥有的:
包含两个函数和 class:
的脚本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(具有 PS 核心的 Win 终端)中执行 .\caller.ps1
来调用 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
定义只有 name, no namespace) 但它们位于不同的动态(内存中)程序集中,它们的版本号的最后一个组件不同,shadow 互为影响,哪个是效果取决于上下文:- 点源此类脚本的其他脚本始终看到 新 版本。
- 在脚本本身内部,不管它本身是直接执行的还是点源的:
- 在 PowerShell 代码中,原始版本仍然有效。
- 在 binary cmdlets 中,特别是
New-Object
,new 版本生效。 - 如果你混合使用这两种方式访问脚本里面的class,就会出现类型不匹配,也就是你的情况发生了什么 - 请参阅下面的示例代码。
虽然您可以通过 一致地 使用
::new()
或New-Object
引用 class,最好 避免同时执行包含class
定义的脚本文件的直接执行和点源。
示例代码:
将代码保存到脚本文件中,例如,
demo.ps1
执行两次.
- 首先,直接执行:
.\demo.ps1
- 然后,通过dot-sourcing:
. .\demo.ps1
- 首先,直接执行:
您看到的类型不匹配错误将在第二次执行期间发生。
- 注意:错误消息
Cannot find an overload for "Add" and the argument count: "1"
有点晦涩;它试图表达的是.Add()
方法不能用给定类型 的参数 调用,因为它需要 new 的实例[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,它也会消失:$list = [Collections.Generic.List[MyData]]::new()