考虑以下示例:

$无效

foreach ($n in $无效) {'This is a $无效 test'}
(no output)

$无效 | ForEach-Object {'This is a $无效 test'}
This is a $无效 test

$无效 -in $无效
True

$无效 -contains $无效
True

 

[INT]1

foreach ($n in [INT]1) {'Test'}
Test

[INT]1 | ForEach-Object {'Test'}
Test

[INT]1 -in [INT]1
True

[INT]1 -contains [INT]1
True


Since $无效 is a scalar value that contains nothing, in the second $无效 example, $无效 will send a single instance of 'nothing' down the pipeline, which explains the output.

为什么即使$无效 -in $无效返回True,$无效也不能作为一个集合进行迭代?

  • 对求值为空的对象/值进行not次迭代的性能yield 似乎很明显,但为什么PowerShell将标量变量/值视为集合?
  • 这种行为是否会在整个.NET和.NET框架中持续存在?
  • 是否存在类型强制,或者我对集合/列表的 struct 或行为有根本的误解?

推荐答案

tl;dr

您观察到的不对称性肯定是不幸的--但不太可能得到解决,以免 destruct 向后兼容性.


Both
<commandOrExpression> | ... (pipeline) and
foreach ($var in <commandOrExpression>) { ... }
are enumeration contexts:

  • 如果<commandOrExpression>的计算结果为enumerable,则[1]将自动枚举值为the enumerated elements are processed one by one的对象.

  • 如果不是,则结果被处理为itself;在某种程度上:它被视为like a single-element enumerable.


  • 100,对应于C#中的null,是一个特殊的something,恰好发生在represent的"标量(单一)无"中.

  • 这与enumerable null形成对比--从技术上讲,enumerable null[System.Management.Automation.Internal.AutomationNull]::Value个单例,也就是"Automation Null"--它是当命令生成no output时得到的.

    • 它是enumerable的PS特定表示,即nothing in itselfhas no elements:它的目的是发出信号"我什么都不表示-我自己不是对象(除非我被迫在expressions中充当对象(作为标量),在这种情况下,我将假装是$null),枚举我产生no个元素".
  • 顺便说一句:在PowerShell 7.4.x中,要确定给定的存储值是实际的$null还是可枚举的NULL(这一区别与实际的does有关),仍然不是一件容易的事:

    # Only $true if $someValue contains the enumerable null 
    $null -eq $someValue -and @($someValue).Count -eq 0
    
    • 以下future 的增强功能已获得批准,但尚未实施-请参阅GitHub issue #13465:

      # !! Future enhancement - doesn't work as of v7.4.x
      $someValue -is [System.Management.Automation.AutomationNull]
      

因此,无论是foreach循环还是管道:

  • 幸运的是,如果enumerable null被提供为输入,则not执行枚举,假定其目的是用信号通知存在nothing to enumerate.

    # Capture the output of a command that produces NO output.
    $noOutput = Get-Item *NoSuchFile*
    
    # No output, because the loop body is not executed.
    foreach ($obj in $noOutput) { 'This does not print.' }
    
    # Ditto.
    $noOutput | ForEach-Object { 'This does not print' }
    
  • should$null处理为itself,因此导致以$null作为要处理的(非)对象的单个处理迭代:

    • 幸运的是,这个ispipeline中是如何工作的,正如您在问题中对ForEach-Object cmdlet的样例调用所证明的那样.

    • 这是foreachlanguage statement的行为方式,这出乎意料地处理了$null,正如你也观察到的.


至于这种不对称的reason:

  • 直到Windows PowerShell的版本2,将生成no个输出的命令的输出赋给variable隐式地将可枚举空(表示缺少输出)转换为$null;在v3+(包括PowerShell (Core) 7+中)中,幸运的是现在存储可枚举空.

  • 为了使将输出存储在中间变量中相当于direct处理具有foreach的命令的输出,决定忽略$null作为输入,从版本3:[2]开始

    • 例如,如果$null weren'tforeach语句忽略,则以下命令的实际上等效公式将是等效的not,并且实际上是were NOT equivalent up to v2:

       # DIRECT processing of command output.
       foreach ($obj in Get-Item *NoSuchFile*) { 'This does not print' }
      
       # INDIRECT processing of command output, via an intermediate variable.
       $output = Get-Item *NoSuchFile*
       # In v2, $output was $null, not the enumerable null,
       # causing the loop body to execute (once, with $obj containing $null)
       foreach ($obj in $output) { 'This does not print' }
      
  • Presumably,假设如果使用pipeline,则不需要中间变量,在这种情况下不会出现问题.

  • However, the problem (still) does arise if non-existent variables or non-existent properties of existing objects are referenced, because they evaluate to $null, and therefore cause $null to be sent through the pipeline.
    Also, passing the enumerable null as an argument to a function or script's untyped parameter causes quiet conversion to $null.


从上面得出的结论是,如果对向后兼容性的影响最小,则potential solution应如下所示:

  • Make references to non-existent variables or properties default to the enumerable null rather than $null, which would implicitly prevent enumeration in both the pipeline and in foreach statements.
    Additionally, preserve the enumerable null as function / script arguments when binding to untyped parameters.

    • expression个上下文中,可枚举空的行为类似于$null,因此类似$null -eq $noSuchVariable的内容将继续工作.
  • 就像流水线已经做的那样,将进程actual $null的值设为foreach,这将消除不对称性.


具体地说,实现IEnumerable接口或其泛型对应项的类型会自动枚举,但也有几个例外:字符串、字典(实现IDictionary或其泛型对应项的类型)和System.Xml.XmlNode实例.此外,System.Data.DataTable也通过其.Rows属性被枚举,尽管它本身没有实现IEnumerable. 有关详情,请参阅this answer.

[2]v3中的这一行为更改是在2012年PowerShell团队博客上宣布的:New V3 Language Features节,"ForEach语句不迭代超过$NULL",

.net相关问答推荐

升级到.NET8后,SignalR(在坞站容器上)网关损坏

等待时 Blazor 服务器按钮刷新

通过 System.Net.Mail 的 VB SMTP 停止工作

无法通过构建目标访问 dotnet 的环境变量

如何确定计时器是否正在运行?

既然 .NET 有一个垃圾收集器,为什么我们需要终结器/析构器/dispose-pattern?

为什么这个多态 C# 代码会打印它的功能?

在 .NET 反射中使用 GetProperties() 和 BindingFlags.DeclaredOnly

string.Format 如何处理空值?

C# 的部分类是糟糕的设计吗?

.NET 的 `Array.Sort()` 方法使用的排序算法是稳定的算法吗?

如何将自定义 UserControl 显示为对话框?

隐式与显式接口实现

Mono 是树莓派

在 C# 中使用委托

关闭 Visual Studio 中所有选项卡但当前选项卡的键盘快捷键?

如果选中,则更改列表框项的 WPF DataTemplate

可以从 C# 调用 C++ 代码吗?

实体框架太慢了.我有哪些 Select ?

LINQ 可以与 IEnumerable 一起使用吗?