【问题标题】:Controlling the type returned by a Function in PowerShell在 PowerShell 中控制函数返回的类型
【发布时间】:2020-06-19 18:13:30
【问题描述】:

所以,我正在尝试使用 WPF 表单为搜索应用程序创建 GUI。我的表单有一个 TextBox 和 ComboBox 用于输入,每次更改事件触发时,内容都会作为过滤器应用。 $var_SearchRegion 是我的 ComboBox,$var_SearchFor 是我的 TextBox,$var_DGDisplay 是我用于返回数据的 DataGrid。

为了设置过滤器,我创建了一个基本函数。我知道有比 If Then ElseIf 树更干净的方法来做到这一点,但这很快而且有效。函数如下所示:

Function GetFilteredItems
{
    $RSelect = $var_SearchRegion.SelectedValue.Content
    $PF = $var_SearchFor.Text
    $RF = If ($RSelect -eq 'All Regions') {''} Else {$RSelect}
    If ($PF -eq '' -and $RF -eq '')
    {
        $DPST
    }
    ElseIf ($PF -eq '')
    {
        $DPST | ? {$_.Region -eq $RF}
    }
    ElseIf ($RF -eq '')
    {
        $DPST | ? {$_.FilePath -like "*$PF*"}
    }
    Else
    {
        $DPST | ? {$_.Region -eq $RF -and $_.FilePath -like "*$PF*"}
    }
}

当过滤器将数据集减少到一个条目并将其返回时,就会出现此问题。我之前在使用 .NET 方法的 PowerShell 中遇到过这个问题,我假设类型从“某物”的 [Array] 更改为“某物”,这会导致 WPF 对象出现问题。

这是一个事件处理程序代码的示例,当过滤器将我的返回设置为 1 项时会导致问题:

$var_SearchFor.Add_TextChanged({
    $var_DGDisplay.ItemsSource = GetFilteredItems
    $var_DGDisplay.Refresh
})

GetFilteredItems 返回 1 项时,我收到此错误:

Exception setting "ItemsSource": "Cannot convert the "@{Region=SW; 
FilePath=\\server\share\folder\file.ext}" value of type 
"Deserialized.System.Management.Automation.PSCustomObject" to type "System.Collections.IEnumerable"."
At C:\Users\Matthew\GUITest.ps1:90 char:5
+     $var_DGDisplay.ItemsSource = GetFilteredItems
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], SetValueInvocationException
    + FullyQualifiedErrorId : ExceptionWhenSetting

我可以通过包装函数调用并显式转换为 [array] 类型来轻松“修复”这个问题,如下所示:

$var_SearchFor.Add_TextChanged({
    $var_DGDisplay.ItemsSource = [array](GetFilteredItems)
    $var_DGDisplay.Refresh
})

但是不得不这样做感觉倒退了。我应该能够以某种方式在函数内部解决这个问题。但是,用相同的[array]() 包装函数的输出行(在 4 个位置)并没有成功。我找到了有关 PowerShell 高级函数的一些信息,我可以在其中使用语法 [OutputType([Array])] 来指定输出类型,但即使我使用 (Get-Command GetFilteredItems).OutputType 验证了我的输出类型是 System.Array,它仍然会失败同样的错误。

TL;DR - 有没有办法避免使用 [array]() 包装我的函数调用并仍然使其与我的 WPF DataGrid 控件的 ItemSource 属性一起使用?

【问题讨论】:

  • 我一直这样做是因为 PowerShell 在幕后经常使用赋值来创建返回对象,例如:$return = Cmd.如果命令的结果是一个数组,它会这样返回。如果它只是一个项目,它将作为类型变量返回,无论对象是什么。为了解决这个问题,我总是使用: $return = @(Cmd)
  • @thepip3r,虽然您在 净效应 方面是正确的,但这并不是它在幕后工作的方式 - 请参阅 stackoverflow.com/a/60020105/45375

标签: arrays .net wpf powershell return


【解决方案1】:
  • PowerShell 默认枚举从函数输出的集合(无论是隐式输出还是通过return 语句输出)

    • 为防止这种情况发生,请使用Write-Output -NoEnumerate,或者更简单有效(但更隐蔽),将输出集合包装在单元素辅助中。数组 (, $collection) - 请参阅 this answer 了解更多信息。
  • 在您的情况下,您还需要确保您所包装的本身始终是一个集合,您可以使用@() 或转换为[array]

Function GetFilteredItems
{
    $RSelect = $var_SearchRegion.SelectedValue.Content
    $PF = $var_SearchFor.Text
    $RF = If ($RSelect -eq 'All Regions') {''} Else {$RSelect}

    # Collect your command's output in an array-typed variable ([object[]])
    [array] $result = 
      If ($PF -eq '' -and $RF -eq '')
      {
        $DPST
      }
      ElseIf ($PF -eq '')
      {
        $DPST | ? {$_.Region -eq $RF}
      }
      ElseIf ($RF -eq '')
      {
        $DPST | ? {$_.FilePath -like "*$PF*"}
      }
      Else
      {
        $DPST | ? {$_.Region -eq $RF -and $_.FilePath -like "*$PF*"}
      }

    # Output the array $result as-is, via an aux. wrapper array.
    , $result

}

注意:您并不严格需要立即的 $result 变量;你可以直接使用, @(if ...)

但是,请注意,在这两种情况下,命令的输出都不会(在创建对象时发出对象),因为整个输出必须首先收集,然后辅助。可以围绕它构建数组。

【讨论】:

  • @Matthew,是的,它基本上是一样的,因为函数输出到管道,这使得输出的内容默认受到枚举。要将单个集合作为函数作为一个整体的单个对象输出,您必须使用End块。
  • @Matthew :) 请注意,everything 是 .NET 中的一个对象,包括 collections(恰好是包含多个其他对象的对象) .
  • 很容易得到关于 PowerShell 的不完全正确的答案,就像对于 C++ 或 C# 的初学者来说很难得到足够简单的答案......不是很多人们真正了解 PowerShell 幕后发生的事情……甚至关心它。我非常感谢您回答的详细和精确。您将 pip3r 指向的答案(关于我上面的问题)非常好,并且澄清了一大堆关于为什么使用 [array]$var=RHS 而不是 $var=@(RHS) 的问题。非常感谢您的专业知识。
  • 我很高兴听到这个消息,@Matthew,我非常感谢您的反馈。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多