运行 点源脚本执行后出错

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.ps1sourced.ps1 多少次,一切都正常。

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

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

从 PowerShell 7.2.1 开始编写

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

  • 这些版本,它们具有相同的全名(在脚本的顶级范围内创建的 PowerShell class 定义只有 name, no namespace) 但它们位于不同的动态(内存中)程序集中,它们的版本号的最后一个组件不同,shadow 互为影响,哪个是效果取决于上下文:

    • 点源此类脚本的其他脚本始终看到 版本。
    • 在脚本本身内部,不管它本身是直接执行的还是点源的:
      • 在 PowerShell 代码中,原始版本仍然有效。
      • binary cmdlets 中,特别是 New-Objectnew 版本生效。
      • 如果你混合使用这两种方式访问​​脚本里面的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()