PowerShell 5.1 为什么这两个函数返回不同的类型

Rod*_*Rod 3 powershell

function Main {
    $result1 = DoWork1
    $result1.GetType()

    $result2 = DoWork2
    $result2.GetType()
}

function DoWork1 {
    $result1 = Invoke-Sqlcmd -Query "select top 1 * from customer" -ServerInstance "(localdb)\MSSQLLocalDB" -Database "Database1" -OutputAs DataTables
    #assign to variable then return
    return $result1
}

function DoWork2 {
    #return results without assigning to variable
    return Invoke-Sqlcmd -Query "select top 1 * from customer" -ServerInstance "(localdb)\MSSQLLocalDB" -Database "Database1" -OutputAs DataTables
}

Main
Run Code Online (Sandbox Code Playgroud)

这是意外的输出:

IsPublic IsSerial Name                                     BaseType                                                                                    
-------- -------- ----                                     --------                                                                                    
True     False    DataRow                                  System.Object                                                                               
True     True     DataTable                                System.ComponentModel.MarshalByValueComponent   

                                        
Run Code Online (Sandbox Code Playgroud)

San*_*zon 5

使用之前的问答中的类似示例来重现相同的行为:

function Invoke-SqlExample {
    $dtt = [System.Data.DataTable]::new()
    [void] $dtt.Columns.Add('Name')
    $row = $dtt.NewRow()
    $row.Name  = "Hello"
    $dtt.Rows.Add($row)
    , $dtt
}

function Main {
    $result1 = DoWork1
    $result1.GetType()

    $result2 = DoWork2
    $result2.GetType()
}

function DoWork1 {
    $result1 = Invoke-SqlExample
    [pscustomobject]@{
        Function = $MyInvocation.MyCommand.Name
        Type     = $result1.GetType().Name
    } | Out-Host
    return $result1

    # Immediate fixes:
    #     return , $result1
    #     Write-Output $result1 -NoEnumerate
    #     $PSCmdlet.WriteObject($result1, $false) !! Only if Advanced Function
}

function DoWork2 {
     return Invoke-SqlExample
}

Main
Run Code Online (Sandbox Code Playgroud)

您将从中得到的输出是:

Function Type
-------- ----
DoWork1  DataTable


IsPublic IsSerial Name            BaseType
-------- -------- ----            --------
True     False    DataRow         System.Object
True     True     DataTable       System.ComponentModel.MarshalByValueComponent
Run Code Online (Sandbox Code Playgroud)

我们可以看到,只有在先前分配给变量时才会展开 the DataTable,即使该变量($result1inDoWork1仍然是类型DataTable)。

这可以解释为,DoWork2发生在单个管道中而不是DoWork1发生在两个管道中,首先Invoke-SqlExample将输出收集在变量中,然后作为输出发出(这是触发展开的地方)。这是基于假设,可能不完全正确。

正如iRon 在先前答案中的有用评论中建议的那样,立即修复以不受影响地DoWork1返回DataTable实例(展开),我们可以使用逗号运算符,,它将DataTable实例包装在一个数组中,然后在枚举期间丢失该实例(函数的输出) ),因为它是一个只有一个元素的数组。另一种选择是使用Write-Output -NeEnumerate. 作为最后一种选择,我们也可以仅在该功能是高级功能时使用。$PSCmdlet.WriteObject(..., $false)


添加一个演示相同行为的类似示例,该示例由mclayton 在他的有用评论中提供:

function test {
    , [Collections.Generic.List[string]]@(
        'hello'
        'world'
    )
}

(& { test }).GetType()          # => List`1
(& { $a = test; $a }).GetType() # => Object[]
Run Code Online (Sandbox Code Playgroud)

  • FWIW,我认为 ```return``` 语句对于变量与直接表达式的行为不同 - 如果您尝试这个 ```(get-command DoWork1).ScriptBlock.Ast.Body.EndBlock.Statements[1] .Pipeline.PipelineElements[0].GetType()``` 你会看到 ```return $result1``` 中的 ```$result1``` 是一个 ```CommandExpressionAst```,但是 `` `(get-command DoWork2).ScriptBlock.Ast.Body.EndBlock.Statements[0].Pipeline.PipelineElements[0].GetType()``` 为 ```Invoke-SqlCmd 提供 ```CommandAst``` ...``` 在 ```return Invoke-SqlCmd ...``` 中。我怀疑“CommandExpressionAst”被展开...... (2认同)