为什么`-lt`对字符和字符串的表现不同?

时间:2016-03-19 00:15:50

标签: powershell operator-overloading operators

我最近answered a SO-question关于使用-lt-gt使用字符串。我的回答是基于something I've read earlier,它说-lt一次比较每个字符串中的一个字符,直到ASCII值不等于另一个字符串。此时结果(低/等/更大)决定。根据该逻辑,"Less" -lt "less"应返回True,因为L的ASCII字节值低于l,但它不会:

[System.Text.Encoding]::ASCII.GetBytes("Less".ToCharArray())
76
101
115
115

[System.Text.Encoding]::ASCII.GetBytes("less".ToCharArray())
108
101
115
115

"Less" -lt "less"
False

似乎我可能错过了一个关键部分:测试不区分大小写

#L has a lower ASCII-value than l. PS doesn't care. They're equal
"Less" -le "less"
True

#The last s has a lower ASCII-value than t. PS cares.
"Less" -lt "lest"
True

#T has a lower ASCII-value than t. PS doesn't care
"LesT" -lt "lest"
False

#Again PS doesn't care. They're equal
"LesT" -le "lest"
True

然后我尝试测试char vs single-character-string:

[int][char]"L"
76

[int][char]"l"
108


#Using string it's case-insensitive. L = l
"L" -lt "l"
False

"L" -le "l"
True

"L" -gt "l"
False

#Using chars it's case-sensitive! L < l
([char]"L") -lt ([char]"l")
True

([char]"L") -gt ([char]"l")
False

为了进行比较,我尝试使用区分大小写的less-than运算符,但是它表示L > l-lt为chars返回的内容相反。

"L" -clt "l"
False

"l" -clt "L"
True

比较是如何工作的,因为它显然不是使用ASCII值,为什么字符与字符串的行为不同?

2 个答案:

答案 0 :(得分:3)

非常感谢PetSerAl所有宝贵的意见。

<强> TL;医生

  • -lt-gt通过 Unicode codepoint 比较[char]个实例数字

    • 令人困惑的是,-ilt-clt-igt-cgt也是如此 - 尽管它们只对 string 操作数有意义,但是& #39; PowerShell语言本身的一个怪癖(见下)。
  • 相比之下,
  • -eq(及其别名-ieq)比较[char]个实例不区分大小写,这通常是不一定喜欢不区分大小写的字符串比较(-ceq再次比较严格数字)。

    • -eq / -ieq 最终还会比较数字,但首先使用不变文化将操作数转换为大写等值;因此,这种比较并不完全等同于PowerShell的字符串比较,后者还识别所谓的兼容序列(不同的字符或甚至被认为具有相同含义的序列;请参阅{{ 3}})同等。
    • 换句话说: PowerShell 特殊情况 -eq / -ieq[char]个操作数的行为,并且以几乎但不完全相同的方式执行此操作字符串比较
  • 这种区别会导致反直觉行为,例如[char] 'A' -eq [char] 'a'[char] 'A' -lt [char] 'a' 两者返回$true

  • 为了安全起见:

      如果您想要 numeric (Unicode代码点)比较,
    • 始终会转换为[int]
    • 如果您想要字符串比较,
    • 始终会转换为[string]

有关背景资料,请继续阅读。

PowerShell通常有助于操作员重载有时会很棘手。

请注意,在 数字上下文(无论是隐式还是显式)中,PowerShell会处理字符([char][System.Char])个实例 数字,通过 Unicode 代码点(不是 ASCII )。

[char] 'A' -eq 65  # $true, in the 'Basic Latin' Unicode range, which coincides with ASCII
[char] 'Ā' -eq 256 # $true; 0x100, in the 'Latin-1 Supplement' Unicode range

使 [char] 异常的原因是它的实例通过Unicode代码点数字地进行比较使用{{ 1}} / -eq

  • -ieqceq-lt通过Unicode代码点直接比较 ,并且 - 反直觉 - 所以-gt,{{ 1}},-ilt-clt
-igt
  • -cgt(及其别名[char] 'A' -lt [char] 'a' # $true; Unicode codepoint 65 ('A') is less than 97 ('a') 首先将字符转换为大写,然后比较生成的Unicode代码点:
-eq

值得反思的是这个佛教转向:这个:PowerShell世界中的,角色&#39; A&#39;小于等于&#39; a&#39;,具体取决于您的比较方式

此外,直接或间接 - 转换为大写后 - 比较Unicode代码点与将它们作为 strings 进行比较并不相同,因为PowerShell的字符串比较另外识别所谓的兼容序列,其中字符(或甚至字符序列)被认为是#34;相同的&#34;如果它们具有相同的含义(参见Unicode equivalence); e.g:

-ieq

请注意,使用前缀[char] 'A' -eq [char] 'a' # !! ALSO $true; equivalent of 65 -eq 65 # Distinct Unicode characters U+2126 (Ohm Sign) and U+03A9 Greek Capital Letter Omega) # ARE recognized as the "same thing" in a *string* comparison: "Ω" -ceq "Ω" # $true, despite having distinct Unicode codepoints # -eq/ieq: with [char], by only applying transformation to uppercase, the results # are still different codepoints, which - compared numerically - are NOT equal: [char] 'Ω' -eq [char] 'Ω' # $false: uppercased codepoints differ # -ceq always applies direct codepoint comparison. [char] 'Ω' -ceq [char] 'Ω' # $false: codepoints differ 显式指定大小写匹配行为不足以强制 string 比较,即使概念运算符,例如ic-ceq-ieq-clt,{{ 1}}只对字符串有意义。

有效的是,-ilt-cgt前缀在应用于-igti时只需忽略,同时{{1}操作数;事实证明(与我原先的想法不同),这是一个一般的PowerShell陷阱 - 请参阅下面的解释。

顺便说一下:字符串比较中的 c-lt逻辑不是数字,但基于整理订单 (一种独立于代码点/字节值的人类中心排序方式),在.NET术语中由 culture 控制(或者默认情况下,当前有效的方法,或通过将参数传递给方法) 正如@PetSerAl在评论中演示(与我最初声明的不同), PS字符串比较使用不变文化,而不是当前文化,所以他们的行为是一样的,不管现在的文化是什么。

在幕后:

正如@PetserAl在评论中解释的那样,PowerShell的解析并没有区分运算符的基本形式-gt - 前缀形式;例如,[char]-lt都转换为相同的-gt
因此, Powershell 无法i-lt-iltIlt实施不同的行为,... ,因为它在语法级别上对它们的处理方式相同。

这导致某些反直觉行为,因为在比较区分大小写无意义的数据类型时,操作符前缀有效忽略 - 而不是被强制转换为人们可能期望的字符串; e.g:

-lt

在后一种情况下,我会期望使用-ilt来强制操作数到字符串,因为区分大小写的比较只是字符串比较中的一个有意义的概念,但这不是它的工作原理。 / p>

如果您想深入了解PowerShell的运作方式,请参阅下面的@ PetSerAl评论。

答案 1 :(得分:1)

在处理字符串/字符时,不太确定除了比较之外的其他内容都是正确的。如果您想进行有序比较,请进行有序比较,然后根据该结果得到结果。

Best Practices for Using Strings in the .NET Framework

[string]::Compare('L','l')
returns 1

[string]::Compare("L","l", [stringcomparison]::Ordinal)
returns -32

不知道在这里添加什么来帮助澄清。

另见:Upper vs Lower Case