我正在编写一个PowerShell脚本,以便在Windows 10中工作。我使用的是"HTML敏捷包"库1.11.43版本。
在这个库中,有四个版本的HTML元素节点的GetAttributeValue
方法:
public string GetAttributeValue(string name, string def)
public int GetAttributeValue(string name, int def)
public bool GetAttributeValue(string name, bool def)
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"]
。我也了解多态性和方法重载是什么。我也知道什么是泛型方法。我不需要解释。
我有三个问题:
从PowerShell脚本调用
$node.GetAttributeValue("class", $null)
时,GetAttributeValue
方法的四个变体中的哪一个有效?我认为第四种选择是可行的(通用方法)。那么,为什么具有第二参数
$null
的调用与具有第二个参数$false
的调用工作完全相同呢?在C#源代码中,第四个选项需要以下条件才能工作
#if !(METRO || NETSTANDARD1_3 || NETSTANDARD1_6)
我尝试了NETSTANDARD1_6
和NETSTANDARD2_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尚未发布,但预览版本可用-请参阅回购