字符串连接在使用 Class 的成员时添加 space 而在使用变量时不添加

String concatenation adds space when using a member of a Class that is not added when using a variable instead

我从 Microsoft 找到了一些使用字符串连接的 PowerShell 代码,例如 $variable".Name"。我将此代码改编为在 class 的方法中使用,并发现了一个奇怪的行为,如这段代码所示。

Class A
{
    [string]$a = "A"
    [void]Test()
    {
        $b = $this.a
        Write-Host $b"B"
        Write-Host $this.a"B"
    }
}

$c = [A]::new()
$c.Test()

第一个表达式 returns A 和 B 之间没有 space 的字符串,第二个表达式在它们之间有一个 space。

AB
A B

我使用“$($this.a)B”解决了这个问题,但我很想知道造成这种差异的原因。

经过一些研究,我认为这是 PowerShell 如何解析 $a"bbb"$b.a"bbb" ValueFromRemainingArguments 属性的组合 属性 在 Write-Host

-Output 参数上

基本上:

  • write-host $a"bbb" 等价于 write-host "$($a)bbb" 输出为 aaabbb.

  • write-host $b.a"bbb" 等价于 write-host @( $b.a, "bbb" ) 输出为 aaa bbb.

长版

如果您检查为以下脚本块创建的 Ast,您可以看到 PowerShell 如何解析表达式的差异:

示例 1

$s1 = {
    $a = "aaa";
    write-host $a"bbb";
}
$s1.Ast.EndBlock.Statements[1].PipelineElements[0].CommandElements

给出此输出:

StringConstantType : BareWord
Value              : write-host
StaticType         : System.String
Extent             : write-host
Parent             : write-host $a"bbb"

Value              : $abbb
StringConstantType : BareWord
NestedExpressions  : {$a}
StaticType         : System.String
Extent             : $a"bbb"
Parent             : write-host $a"bbb"

这会将 write-host $a"bbb" 视为等同于 write-host "$($a)bbb"(请参阅 NestedExpressions),如果您调用它,最终会将 aaabbb 写入控制台。

示例 2

$s2 = {
    $b = [pscustomobject] @{ "a" = "aaa" };
    write-host $b.a"bbb";
}
$s2.Ast.EndBlock.Statements[1].PipelineElements[0].CommandElements

给出输出:

StringConstantType : BareWord
Value              : write-host
StaticType         : System.String
Extent             : write-host
Parent             : write-host $b.a"bbb"

Expression      : $b
Member          : a
Static          : False
NullConditional : False
StaticType      : System.Object
Extent          : $b.a
Parent          : write-host $b.a"bbb"

StringConstantType : DoubleQuoted
Value              : bbb
StaticType         : System.String
Extent             : "bbb"
Parent             : write-host $b.a"bbb"

表示write-host $b.a"bbb"等价于write-host $b.a "bbb",输出aaa bbb

但是,我们还没有完全完成...

Get-Help Write-Host

如果你调用 Get-Help Write-Host 你会看到这样的输出:

Write-Host [[-Object] <Object>] ...

但如果我们在自定义函数中镜像该签名:

function x
{
    param
    (
        [Object] $Object
    )
    write-host $Object.GetType().FullName
    write-host $Object
}

"Example 1"
x $a"bbb"

"Example 2"
x $b.a"bbb"

我们得到这个输出:

Example 1
System.String
aaabbb
Example 2
System.String
aaa

在第二个示例中,"bbb" 在函数调用中被丢弃,因为没有要绑定的第二个值的参数,因此 Write-Host 一定有其他事情发生嗯...

Get-Help Write-Host-满

如果我们调用 Get-Help Write-Host -Full,我们将看到有关 cmdlet 参数的一些附加信息,它向我们展示了 -Output 参数用 FromRemainingArguments 属性:

   -Object <Object>

        Required?                    false
        Position?                    0
        Accept pipeline input?       true (ByValue, FromRemainingArguments)
        Parameter set name           (All)
        Aliases                      Msg, Message
        Dynamic?                     false
        Accept wildcard characters?  false

因此,如果我们将其复制到我们自己的函数中,我们将得到:

function x
{
    param
    (
        [Parameter(ValueFromRemainingArguments=$true)]
        [Object] $Object
    )
    write-host $Object.GetType().FullName
    write-host $Object.Count
    write-host ($Object | fl * | out-string)
    write-host $Object
}

现在任何未绑定的参数都捆绑到我们的 $Object:

"Example 1"
x $aaa"bbb"
"Example 2"
x $a.b"bbb"

给出:

Example 1
System.Collections.Generic.List`1[[System.Object, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
1
aaabbb
aaabbb

System.Collections.Generic.List`1[[System.Object, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
2
aaa
bbb
aaa bbb

我们可以看到第一个示例传递了一个包含单个字符串的通用列表,write-host 简单地逐字回显,而第二个示例将任何未绑定的值捆绑到 -Output 中,它给出了一个列表其中有两个值,PowerShell 中可枚举类型的默认序列化将项目与 space 作为分隔符。