Powershell: Implementing IConvertible prevents casting to string

Created on 21 Feb 2020  路  6Comments  路  Source: PowerShell/PowerShell

Beginning sometime after PowerShell 5.1 a class implementing IConvertible throws an exception when casting to a string.

Note that in the repro below simply removing : IConvertible is sufficient to restore the ability to cast to string.

Steps to reproduce

```C#
//type.cs
using System;
public class C : IConvertible
{
public readonly string Value = null;
public C(string v) { Value = v; }
override public String ToString() {return Value; }

public TypeCode GetTypeCode() { return Value.GetTypeCode(); }
public object   ToType(Type conversionType, IFormatProvider provider) { return ((IConvertible)Value).ToType(conversionType, provider); }
public bool     ToBoolean(IFormatProvider provider)  { return ((IConvertible)Value).ToBoolean(provider); }
public byte     ToByte(IFormatProvider provider)     { return ((IConvertible)Value).ToByte(provider); }
public char     ToChar(IFormatProvider provider)     { return ((IConvertible)Value).ToChar(provider); }
public DateTime ToDateTime(IFormatProvider provider) { return ((IConvertible)Value).ToDateTime(provider); }
public decimal  ToDecimal(IFormatProvider provider)  { return ((IConvertible)Value).ToDecimal(provider); }
public double   ToDouble(IFormatProvider provider)   { return ((IConvertible)Value).ToDouble(provider); }
public short    ToInt16(IFormatProvider provider)    { return ((IConvertible)Value).ToInt16(provider); }
public int      ToInt32(IFormatProvider provider)    { return ((IConvertible)Value).ToInt32(provider); }
public long     ToInt64(IFormatProvider provider)    { return ((IConvertible)Value).ToInt64(provider); }
public sbyte    ToSByte(IFormatProvider provider)    { return ((IConvertible)Value).ToSByte(provider); }
public float    ToSingle(IFormatProvider provider)   { return ((IConvertible)Value).ToSingle(provider); }
public string   ToString(IFormatProvider provider)   { return Value.ToString(provider); }
public ushort   ToUInt16(IFormatProvider provider)   { return ((IConvertible)Value).ToUInt16(provider); }
public uint     ToUInt32(IFormatProvider provider)   { return ((IConvertible)Value).ToUInt32(provider); }
public ulong    ToUInt64(IFormatProvider provider)   {return ((IConvertible)Value).ToUInt64(provider); }

}


```powershell
Add-Type -Path "$PSScriptRoot\type.cs"

$c = [C]::new('value')
$c.ToString()
[string]$c

Expected behavior

value
value

Actual behavior

PowerShell 7

value
OperationStopped: C:\test.ps1:5
Line |
   5 |  [string]$c
     |  ~~~~~~~~~~
     | Unable to cast object of type 'C' to type 'System.String'.

PowerShell 5.1

value
value

Environment data

Name                           Value
----                           -----
PSVersion                      7.0.0-rc.2
PSEdition                      Core
GitCommitId                    7.0.0-rc.2
OS                             Microsoft Windows 6.3.9600
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0.}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Issue-Bug WG-Engine

All 6 comments

I think it is .Net Core issue.
If you can create C# repo please report to .Net Core repo. Otherwise we should investigate in depth before report there.

@iSazonov I thought that might be the case too. But casting to string in C# like the following doesn't seem to be permitted by either compiler I've tried with Core 3.1 or Framework 4.7.2 (those links are dotnetfiddle repros):

```C#
public class C : IConvertible {...}

public class Program
{
public static void Main()
{
C c = new C("value");
String s = (String)c; // Compilation error: Cannot convert type 'C' to 'string'
}
}
```

I think that error makes sense in C# since there is no conversion operator defined for C on String.

Having the corresponding restriction in PowerShell, however, seems odd since [string]$anyObject seems to succeed for any type other than those for which IConvertible is implemented.

I'm... pretty sure this is a PowerShell thing. IConvertible has specific conversion methods associated with it, and those methods all assume the input is numeric as far as I recall.. so I think this object is making it to those methods and then failing, or we're missing a specific IConvertible conversion path that doesn't assume the input is a number.

If you look through the LanguagePrimitives.cs file for references to ConvertIConvertible you should come up with a few good places to start.

It works in PowerShell-7.0.0-preview.1 but not later.
I found https://github.com/PowerShell/PowerShell/pull/9893 for the time but I am not sure that this is the cause of the error.
/cc @daxian-dbw

Was this page helpful?
0 / 5 - 0 ratings