使用第二个参数$null从 PowerShell 调用时,使用'HTML Agility Pack'的哪个版本的 GetAttributeValue?



我正在编写一个PowerShell脚本,以便在Windows 10中工作。我使用的是"HTML敏捷包"库1.11.43版本。

在这个库中,有四个版本的HTML元素节点的GetAttributeValue方法:

  1. public string GetAttributeValue(string name, string def)
  2. public int GetAttributeValue(string name, int def)
  3. public bool GetAttributeValue(string name, bool def)
  4. public T GetAttributeValue<T>(string name, T def)

我已经在PowerShell上为这些方法编写了一个测试脚本:

$libPath = "HtmlAgilityPack.1.11.43libnetstandard2.0HtmlAgilityPack.dll"
Add-Type -Path $libPath
$dom = New-Object -TypeName "HtmlAgilityPack.HtmlDocument"
$dom.Load("test.html", [System.Text.Encoding]::UTF8)
foreach ($node in $dom.DocumentNode.DescendantNodes()) {
if ("#text" -ne $node.Name) {
$node.OuterHTML
"    " + $node.GetAttributeValue("class", "")
"    " + $node.GetAttributeValue("class", 0)
"    " + $node.GetAttributeValue("class", $true)
"    " + $node.GetAttributeValue("class", $false)
"    " + $node.GetAttributeValue("class", $null)
}
}

文件"test.html":

<p class="true"></p>
<p class="false"></p>
<p></p>
<p class="any other text"></p>

测试脚本执行结果:

<p class="true"></p>
true
0
True
True
True
<p class="false"></p>
false
0
False
False
False
<p></p>
0
True
False
False
<p class="any other text"></p>
any other text
0
True
False
False

我知道要获得HTML元素的属性值,还可以使用表达式$node.Attributes["class"]。我也了解多态性和方法重载是什么。我也知道什么是泛型方法。我不需要解释。

我有三个问题:

  1. 从PowerShell脚本调用$node.GetAttributeValue("class", $null)时,GetAttributeValue方法的四个变体中的哪一个有效?

  2. 我认为第四种选择是可行的(通用方法)。那么,为什么具有第二参数$null的调用与具有第二个参数$false的调用工作完全相同呢?

  3. 在C#源代码中,第四个选项需要以下条件才能工作

#if !(METRO || NETSTANDARD1_3 || NETSTANDARD1_6)

我尝试了NETSTANDARD1_6NETSTANDARD2_0的库版本。测试脚本的工作方式相同。但对于NETSTANDARD1_6,第四个选项应该不可用,对吧?那么当NETSTANDARD1_6时,方法GetAttributeValue的哪个版本与第二参数$null一起工作?

tl;dr

要实现您使用
$node.GetAttributeValue("class", $null)未成功的尝试,即将属性值返回为[string],如果没有,则默认为$null,请使用:

$node.GetAttributeValue("class", [string] [NullString]::Value)

[string] $null也起作用,但将""(空字符串)而不是$null作为默认值


虽然您看到的过载解决方案令人惊讶,但您可以在PowerShell的方法过载解决过程中使用cast:来解决歧义

$dom = [HtmlAgilityPack.HtmlDocument]::new()
$dom.LoadHtml(@'
<p class="true"></p>
<p class=42></p>
<p></p>
<p class="any other text"></p>
'@)
$nodes = $dom.DocumentNode.SelectNodes('p')
# Note the use of explicit casts (e.g., [string]) to guide overload resolution.
$nodes[0].GetAttributeValue('class', [bool] $false)
$nodes[1].GetAttributeValue('class', [int] 0)
$nodes[2].GetAttributeValue('class', [string] 'default')
$nodes[3].GetAttributeValue('class', [string] [NullString]::Value)

输出:

True
42
default
any other text

或者,在PowerShell(Core)7.3+[1]中,您现在可以使用显式类型参数调用泛型方法

# PS 7.3+
# Note the generic type argument directly after the method  name.
# Calls the one and only generic overload, with various types substituted for T:
#   public T GetAttributeValue<T>(string name, T def)
# Note how the 2nd argument doesn't need a cast anymore.
$nodes[0].GetAttributeValue[bool]('class',  $false)
$nodes[1].GetAttributeValue[int]('class', 0)
$nodes[2].GetAttributeValue[string]('class', 'default')
$nodes[3].GetAttributeValue[string]('class', [NullString]::Value)

注:

  • 当您将$null传递给[string]类型的参数(在cmdlet和.NET方法中)时,PowerShell实际上会悄悄地将其转换为""(空字符串)。[NullString]::Value告诉PowerShell传递一个真正的null,并且主要用于调用.NET方法,在这些方法中,传递null""可能会导致行为差异。

  • 因此,如果要调用$nodes[3].GetAttributeValue('class', [string] $null),或者在PS 7.3+中调用$nodes[3].GetAttributeValue[string]('class', $null),那么如果属性class不存在,则会得到""(空字符串)作为默认值。

  • 相比之下,在上面的命令中使用的[NullString]::Value会在属性不存在的情况下返回真正的$null值;您可以使用$null -eq ...进行测试。


至于您的问题

一般来说,PowerShell的过载解决方案很复杂,为了获得最终的真相,您必须查阅源代码。以下内容基于PowerShell 7.2.6的实际行为,以及关于可以应用的逻辑的思考。

从PowerShell脚本调用$node.GetAttributeValue("class", $null)时,GetAttributeValue方法的四种变体中的哪一种有效?

在实践中,选择public bool GetAttributeValue(string name, bool def)过载;具体来说,为什么在可用的重载中选择它最终是无关紧要的,因为根本问题是对于PowerShell,$null提供了关于它可能是的替代类型的信息不足,因此,通常不能期望它选择特定的过载(对于后者,您需要铸造,如顶部所示):

  • C#中,将null传递给非泛型调用中的第二个参数明确地暗示了string类型的def参数的重载,因为在非泛型重载中,作为def参数类型的string是唯一的.NET引用类型,因此也是唯一可以直接接受null参数的类型。

  • 这在PowerShell中是而不是真的,它有更灵活的隐式类型转换规则:从PowerShell的角度来看,$null可以绑定到def参数中的任何类型,因为它允许$null转换为这些类型;具体地说,[bool] $null产生$false[int] $null产生0,并且——也许令人惊讶的是,如上所述——[string] $null产生""(空字符串)。

    • 因此,PowerShell在这种情况下选择任何一个非通用重载是合理的,它选择的重载应被视为实现细节

然而,奇怪的是,即使使用[NullString]::Value也没有什么区别,即使PowerShell应该知道这个特殊值代表string参数的$null值-请参阅GitHub问题#18072


我认为第四个选项有效(通用方法)。那么,为什么第二个参数为$null的调用与第二个变量为$false的调用工作原理完全相同呢?

使用v7.3+中提供的通用调用语法,通用重载肯定有效-作为默认值参数的$null将转换为指定为类型参数的类型(假设PowerShell允许这样的转换;例如,它不适用于[datetime],因为[datetime] $null会导致错误)。

即使使用非通用语法,PowerShell也会通过推理选择通用重载,如下例所示,但仅当您传递实际对象而不是$null:时

# Try to retrieve a non-existent attribute and provide a [double]
# default value.
# The fact that a [double] instance is returned implies that the
# generic overload was chosen.
#  -> 'System.Double'
$nodes[0].GetAttributeValue('nosuch', [double] $null).GetType().FullName

在C#源代码中,第四个选项需要以下条件才能工作〔…〕

当您传递$null时,不考虑泛型重载,并且在没有类型信息的情况下不能考虑,所以这没有什么区别。


[1]截至本文撰写之时,v7.3尚未发布,但预览版本可用-请参阅回购

相关内容

  • 没有找到相关文章

最新更新