执行点源脚本后运行该脚本时出错



我有一些非常奇怪的行为,我想这与点源有关,但我无法理解

包含两个函数和一个类的脚本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.ps1caller.ps1,我会得到错误:

Line |
14 |          $list.Add($d)
|          ~~~~~~~~~~~~~
| Cannot find an overload for "Add" and the argument count: "1".

但是,如果我将caller.ps1改为调用withString函数,那么无论我调用caller.ps1sourced.ps1多少次,一切都会正常工作。

此外,如果我首先用withString调用caller.ps1,然后将其更改为withClass,则没有任何错误。

我想使用模块会更正确,但我首先感兴趣的是这种奇怪行为的原因。

编写于PowerShell 7.2.1

  • 一个给定的脚本文件是点源和直接执行的(按任意顺序,无论频率如何(会在其中创建class定义的连续版本-这些都是不同的.NET类型,即使它们的结构相同。可以说,没有充分的理由这样做,行为可能是一个错误

  • 这些版本具有相同的全名(在脚本的顶级作用域中创建的PowerShellclass定义只有名称,没有命名空间(,但位于不同的动态(内存(程序集中,这些程序集的版本号的最后一个组件不同,彼此之间存在阴影,哪个是效果取决于上下文

    • 点源于此类脚本的其他脚本始终可以看到版本
    • 在脚本本身内部,无论它本身是直接执行还是点源执行:
      • 在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(),它也会消失

相关内容

  • 没有找到相关文章

最新更新