英文:
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
逐一处理
[...]
当您将多个对象传递给命令时,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
这将生成以下输出。
(注意 BeginBlock
和 ProcessBlock
是空的,您的代码出现在 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...
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论