`.GetType()` 方法对于相同的变量返回不同的值。

huangapple go评论64阅读模式
英文:

Method .GetType() returns different values for the same variable

问题

通过使用.GetType()方法练习,我发现当我编写一个返回'Name'和'BaseType'的函数时,与不编写该函数时,结果不同。

示例:

function Get-BaseType_Name {
    param(
        [Parameter(Mandatory, ValueFromPipeline = $True)]
        [System.Object]$var   
    )
    $var.GetType() | Select -Property Name, BaseType;
} 
$var = Get-Service
$var | Get-BaseType_Name

我得到:

Name              BaseType                       
----              --------                       
ServiceController System.ComponentModel.Component

然而,如果我执行以下句子:

$var = Get-Service
$var.GetType() | Select -Property Name, BaseType;

我得到:

Name              BaseType                       
----              --------                       
Object[]          System.Array 

为什么会这样?

我期望在两种情况下都得到相同的结果。

英文:

Practicing with the . GetType() method, I have found that if I write a function that returns 'Name' and 'BaseType' I get a different result than expected when I do not write the function.

Example:

function Get-BaseType_Name {
    param(
        [Parameter(Mandatory,ValuefromPipeline=$True)]
        [System.Object]$var   
    )
    $var.GetType() | Select -Property Name, BaseType;
} 
$var = Get-Service
$var | Get-BaseType_Name

I get:

Name              BaseType                       
----              --------                       
ServiceController System.ComponentModel.Component

However, if I execute the sentences:

$var = Get-Service
$var.GetType() | Select -Property Name, BaseType;

I get:

Name              BaseType                       
----              --------                       
Object[]          System.Array 

Why is that?

I expected the same result in both cases

答案1

得分: 1

about_Pipelines帮助主题

逐一处理

[...]

当您将多个对象传递给命令时,PowerShell 逐个将对象发送给命令。当您使用命令参数时,对象将作为单个数组对象发送。这个细微的差异具有重要的影响。

在执行管道时,**PowerShell 会自动枚举实现IEnumerable接口的任何类型,并将成员逐个通过管道发送。唯一的例外是[hashtable],它需要调用GetEnumerator()方法。

换句话说:这是有意设计的 - PowerShell检测到$var包含一个数组(一种实现IEnumerable接口的类型),然后开始逐一枚举其中的项。

您可以通过使用Write-Output -NoEnumerate来防止自动枚举:

PS ~> Write-Output $var -NoEnumerate | Get-BaseType_Name

Name              BaseType                       
----              --------                       
Object[]          System.Array 
英文:

From the about_Pipelines help topic:

> ## One-at-a-time processing
> [...]
>
> When you pipe multiple objects to a command, PowerShell sends the objects to the command one at a time. When you use a command parameter, the objects are sent as a single array object. This minor difference has significant consequences.
>
> When executing a pipeline, PowerShell automatically enumerates any type that implements the IEnumerable interface and sends the members through the pipeline one at a time. The exception is [hashtable], which requires a call to the GetEnumerator() method.

In other words: this is by design - PowerShell sees that $var contains an array (a type that implements the IEnumerable interface), and starts enumerating the items in it one-by-one

You can prevent automatic enumeration by using Write-Output -NoEnumerate:

PS ~> Write-Output $var -NoEnumerate | Get-BaseType_Name

Name              BaseType                       
----              --------                       
Object[]          System.Array 

答案2

得分: 1

根据 @MatthiasRJessen 的回答,您通过管道一个接一个地将服务对象传递给函数。

但还有一点需要注意 - 您只看到一个输出结果的原因是因为您的函数等效于以下内容:

function Get-BaseType_Name {
    param(
        [Parameter(Mandatory,ValuefromPipeline=$True)]
        [System.Object]$var
    )
    END {
    #^^^^ END BLOCK
        $var.GetType() | Select -Property Name, BaseType;
    }
}

也就是说,您的函数主体被视为一个 end 块,您可以通过查看 PowerShell 为您的代码生成的AST来确认这一点:

${function:Get-BaseType_Name}.Ast.Body

这将生成以下输出。

(注意 BeginBlockProcessBlock 是空的,您的代码出现在 EndBlock 中)。

Attributes         : {}
UsingStatements    : {}
ParamBlock         : param(
                             [Parameter(Mandatory,ValuefromPipeline=$True)]
                             [System.Object]$var
                         )
BeginBlock         :
^^^^^^^^^^ no definition

ProcessBlock       :
^^^^^^^^^^^^ no definition

EndBlock           : param(
                             [Parameter(Mandatory,ValuefromPipeline=$True)]
                             [System.Object]$var
                         )
                         $var.GetType() | Select -Property Name, BaseType
^^^^^^^^ your code is in this block

DynamicParamBlock  :
ScriptRequirements :
Extent             : {
                         param(
                             [Parameter(Mandatory,ValuefromPipeline=$True)]
                             [System.Object]$var
                         )
                         $var.GetType() | Select -Property Name, BaseType;
                     }
Parent             : function Get-BaseType_Name {
                         param(
                             [Parameter(Mandatory,ValuefromPipeline=$True)]
                             [System.Object]$var
                         )
                         $var.GetType() | Select -Property Name, BaseType;
                     }

如果您查看 关于函数高级方法 中的 end 部分,它说:

end

用于为函数提供可选的一次性后处理。

发生的情况是,PowerShell 将每个 Get-Service 的结果一个接一个地传递给 Get-BaseType_Name 函数,但只有在处理 最后 项目时,您的代码才实际执行任何操作,而在这种情况下,$var 包含对最后一个项目的引用。如果您运行以下代码,可以看到:

function Get-BaseType_Name {
    param(
        [Parameter(Mandatory,ValuefromPipeline=$True)]
        [System.Object]$var
    )
    write-host $var.Name
    #^^^^^^^^^^^^^^^^^^^ -- 将服务的名称写入控制台
    $var.GetType() | Select -Property Name, BaseType;
}

Get-Service | Get-BaseType_Name

在我的机器上,我看到以下输出:

XboxNetApiSvc

Name              BaseType
----              --------
ServiceController System.ComponentModel.Component

其中 XboxNetApiSvc 是由 Get-Service 返回的最后一个服务的名称。

如果要查看每个项目的输出,您可以将您的代码放在 process 块中:

function Get-BaseType_Name {
    param(
        [Parameter(Mandatory,ValuefromPipeline=$True)]
        [System.Object]$var
    )
    PROCESS {
    #^^^^^^^^ PROCESS BLOCK
        $var.GetType() | Select -Property Name, BaseType;
    }
}

Get-Service | Get-BaseType_Name

然后您的输出将如下所示:

Name              BaseType
----              --------
ServiceController System.ComponentModel.Component
ServiceController System.ComponentModel.Component
ServiceController System.ComponentModel.Component
... 等等 ...

总之,您通过管道发送了一个服务集合(根据 @MathiasRJessen 的回答),但您只返回该集合中最后一个项目的值,因为PowerShell隐式地将您的函数主体视为end块。

英文:

Per @MatthiasRJessen's answer, you're passing service objects into your function one-at-a-time via the pipeline.

There's a little bit more to it though - the reason you're only seeing one output result is because your function is equivalent this:

function Get-BaseType_Name {
    param(
        [Parameter(Mandatory,ValuefromPipeline=$True)]
        [System.Object]$var
    )
    END {
    #^^^^ END BLOCK
        $var.GetType() | Select -Property Name, BaseType;
    }
}

That is, your function body is being treated as an end block, which you can confirm if you look at the AST that PowerShell generates for your code:

${function:Get-BaseType_Name}.Ast.Body

which produces the following output.

(Note that the BeginBlock and ProcessBlock are empty and your code appears in the EndBlock).

Attributes         : {}
UsingStatements    : {}
ParamBlock         : param(
                             [Parameter(Mandatory,ValuefromPipeline=$True)]
                             [System.Object]$var
                         )
BeginBlock         :
^^^^^^^^^^ no definition

ProcessBlock       :
^^^^^^^^^^^^ no definition

EndBlock           : param(
                             [Parameter(Mandatory,ValuefromPipeline=$True)]
                             [System.Object]$var
                         )
                         $var.GetType() | Select -Property Name, BaseType
^^^^^^^^ your code is in this block

DynamicParamBlock  :
ScriptRequirements :
Extent             : {
                         param(
                             [Parameter(Mandatory,ValuefromPipeline=$True)]
                             [System.Object]$var
                         )
                         $var.GetType() | Select -Property Name, BaseType;
                     }
Parent             : function Get-BaseType_Name {
                         param(
                             [Parameter(Mandatory,ValuefromPipeline=$True)]
                             [System.Object]$var
                         )
                         $var.GetType() | Select -Property Name, BaseType;
                     }

If you have a look at the end section in about_Functions_Advanced_Methods it says:

> end
>
> This block is used to provide optional one-time post-processing for the function.

What's happening is PowerShell is feeding each of the results from Get-Service into the Get-BaseType_Name function one-at-a-time, but your code is only actually doing anything once the last item has been processed, and in that case $var holds a reference to the last item, which you can see if you run this instead:

function Get-BaseType_Name {
    param(
        [Parameter(Mandatory,ValuefromPipeline=$True)]
        [System.Object]$var
    )
    write-host $var.Name
    #^^^^^^^^^^^^^^^^^^^ -- write the name of the service to the console
    $var.GetType() | Select -Property Name, BaseType;
}

Get-Service | Get-BaseType_Name

On my machine I see this output:

XboxNetApiSvc

Name              BaseType
----              --------
ServiceController System.ComponentModel.Component

where XboxNetApiSvc is the name of the last service returned by Get-Service.

If you want to see the output for every item you can put your code in the process block instead:

function Get-BaseType_Name {
    param(
        [Parameter(Mandatory,ValuefromPipeline=$True)]
        [System.Object]$var
    )
    PROCESS {
    #^^^^^^^^ PROCESS BLOCK
        $var.GetType() | Select -Property Name, BaseType;
    }
}

Get-Service | Get-BaseType_Name

and then your optput will look like this:

Name              BaseType
----              --------
ServiceController System.ComponentModel.Component
ServiceController System.ComponentModel.Component
ServiceController System.ComponentModel.Component
... etc ...

In summary, you're sending a collection of service through the pipeline (per @MathiasRJessen's answer), but you're only returning values for the last item in that collection because PowerShell is implicitly treating your function body as an end block...

huangapple
  • 本文由 发表于 2023年2月16日 19:41:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/75471754.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定