Why does an implicit conversion to [byte]
work, but when replacing byte
by bool
it no longer works?
I. e. the following works...
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
...while this very similar code does not:
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
This produces the following error:
Cannot convert value "MyBool" to type "System.Boolean". Boolean parameters accept only Boolean values and numbers, such as $True, $False, 1 or 0.
Note that in C# the implicit conversion to bool
works as expected:
public class MyBoolTest {
public static void Test() {
bool b = new MyBool( true ); // OK
}
}
So this seems to be a PowerShell issue only.
(PSVersion: 7.2.2)
CodePudding user response:
You've done most of the discovery yourself already, assisted by Santiago Squarzon, but let me try to summarize:
You're seeing two separate problematic PowerShell behaviors:
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.
- Note that
This problematic behavior - inappropriately applying parameter logic to (non-parameter) variables - is the subject of GitHub issue #10426.
[1] The difference between the two is that type-constraining - [bool] $b = ...
- effectively locks in the data type of variable $b
, so that latter attempts to assign new values are coerced to the same type. By contrast, $b = [bool] (...)
merely applies an ad hoc cast to force a conversion, without preventing later assignments from assigning values with different data types.