在PowerShell中定义的C#结构体隐式转换为布尔值失败

3
为什么隐式转换到 [byte] 是有效的,但当将 byte 替换为 bool 时就不再有效?
例如,以下内容是有效的...
Add-Type -TypeDefinition @'
public readonly struct MyByte
{
    private readonly byte value;

    public MyByte( byte b ) => this.value = b;

    public static implicit operator byte( MyByte b ) => b.value;
    public static explicit operator MyByte( byte b ) => new MyByte( b );

    public override string ToString() => $"{value}";
}
'@

[byte] $d = [MyByte]::new( 1 )    # OK

尽管这段代码非常相似,但它并不能正常工作:

Add-Type -TypeDefinition @'
public readonly struct MyBool
{
    private readonly bool value;

    public MyBool( bool b ) => this.value = b;

    public static implicit operator bool( MyBool b ) => b.value;
    public static explicit operator MyBool( bool b ) => new MyBool( b );

    public override string ToString() => $"{value}";
}
'@

[bool] $b = [MyBool]::new( $true )    # Error

这将产生以下错误:

无法将“MyBool”值转换为“System.Boolean”类型。布尔参数只接受布尔值和数字,如$True、$False、1或0。

请注意,在C#中,隐式转换为bool按预期工作:

public class MyBoolTest {
    public static void Test() {
        bool b = new MyBool( true );    // OK
    }
}

所以这似乎只是一个 PowerShell 的问题。

(PS版本:7.2.2)


2
很奇怪 $b = [bool][MyBool]::new( $true ) 可以工作,但 [bool] $b = [MyBool]::new( $true ) 不行。 - Santiago Squarzon
1
@SantiagoSquarzon 这两者的区别在于第一个是显式转换。隐式转换操作符可以用于两种情况,但 PoSh 似乎不认识它的隐式情况。 - zett42
2
@SantiagoSquarzon [bool][MyBool]::new( $false ) -> 出乎意料地打印出 True - zett42
2
@SantiagoSquarzon 像往常一样,@mklement0 似乎已经找到了答案。简而言之:转换为 bool 完全由 PowerShell 处理,并忽略任何隐式和显式转换运算符(在 LanguagePrimitives.cs 的注释中进一步详细说明)。 - zett42
1
顺便说一下,$a = [MyBool]::new(1); $a | gm -force 现在 $a 不再有值了 LOL。 - Santiago Squarzon
显示剩余2条评论
1个回答

2
您已经完成了大部分的发现工作,得到了Santiago Squarzon的协助,但让我来总结一下:

您看到了两个不同的有问题的PowerShell行为:

  • Problematic behavior A: PowerShell has its own, built in to-Boolean conversion logic, which, unfortunately, does not honor implicit or explicit .NET conversion operators.

    • The bottom section of this answer summarizes the rules of this built-in logic, which explains why it considers any instance of your [MyBool] type - even [MyBool]::new($false) - $true, unfortunately.

    • Only in operations where an instance isn't coerced to a Boolean first are the conversion operators honored, which for most operators means using the instance on the LHS:

      [MyBool]::new($false) -eq $false # -> $true
      
      [MyBool]::new($false), 'other' -contains $false # -> $true
      
      # With -in, it is the *RHS* that matters 
      $false -in [MyBool]::new($false), 'other' # -> $true
      
    • By contrast, if you force a Boolean context - either by using a Boolean on the (typically) LHS or with implicit to-Boolean coercion - PowerShell's built-in logic - which doesn't honor conversion operators - kicks in:

      $false -eq [MyBool]::new($false) # -> !! $false
      
      $false, 'other' -contains [MyBool]::new($false) # -> !! $false
      
      # With -in, it is the *RHS* that matters 
      [MyBool]::new($false) -in $false, 'other' # -> !! $false
      
      # Most insidiously, with *implicit* coercion.
      if ([MyBool]::new($false)) { 'what?' } # -> !! 'what?'
      
  • Problematic behavior B: When you type-constrain a variable with [bool], i.e. when you place the type literal to the left of the variable being assigned (e.g, [bool] $b = ..., as opposed to $b = [bool] (...),[1] the rules for binding a [bool] parameter - unexpectedly and inappropriately - kick in, which - unlike the any-type-accepted built-in to-Boolean conversion - are quite restrictive, as the error message indicates.

    • That is, only $true, $false and numbers (with zero mapping to $false and any nonzero value to $true) may be passed to a parameter typed [bool].

      • Note that [bool] parameters themselves are rare, because Boolean logic is PowerShell-idiomatically expressed with a [switch] parameter instead, which, when it is (non-typically) given an explicit argument, is even more restrictive and accepts $true and $false only.
    • This problematic behavior - inappropriately applying parameter logic to (non-parameter) variables - is the subject of GitHub issue #10426.


两者之间的区别在于类型约束 - [bool] $b = ... - 有效地“锁定”了变量$b的数据类型,因此后续尝试分配新值将被强制转换为相同的类型。相比之下,$b = [bool] (...)仅适用于临时的强制转换,不会阻止后续赋值分配具有不同数据类型的值。

2
@Santiago,这种行为确实非常奇怪。请注意,被剥离的不是_value_,而是用于显示的格式突然停止工作(打印一个空行而不是值)。您将看到该值仍然存在于"$a"中。我鼓励您在此处提出新问题或在GitHub上创建问题。 - mklement0
1
@Santiago:是的,毫无疑问。 - mklement0
1
好的,同样的行为也会在具有“hidden”属性的PS类中发生。而且不仅是“gm”破坏了用于显示的格式,“select *”也是如此。 - Santiago Squarzon
1
对于那些感兴趣的人:Santiago在这里发布了一个后续问题(https://stackoverflow.com/q/71632827/45375)。 - mklement0
1
已提交 GitHub Issue #17071,感谢您对“for-display formatting”的更正 :) 我将您的评论链接到此问答中,以便其他人可以看到。如果您不介意,请不要删除该评论。 - Santiago Squarzon
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接