Repository: pythonnet/pythonnet (language: python)

Issue:
Python int type is translated to pyInt when passed as a .net Dictionary value only in 3.x version
### Environment

-   Pythonnet version: 3.0.0a2
-   Python version: 3.7, 3.8, 3.9, 3.10
-   Operating System: Windows
-   .NET Runtime: 4.7.2 framework

### Details

After upgrading to pre release 3.0.0a2 version, the python int type is getting translated to PyInt instead of System.int32 inside the .NET DLL, and hence getting a unable to cast exception. this is working fine in 2.x version.

DLL method: 
```csharp
public void DictMethod(Dictionary<string, object> paramDict)
  {
            Console.WriteLine("Inside");
            Console.WriteLine(paramDict["index"].GetType().ToString());
            var index = Convert.ToInt32((paramDict["index"]));
            
        }
```
Python script: 

```python
   clr.AddReference("System.Collections")
from System.Collections.Generic import Dictionary
from System import String
from System import Object
clr.AddReference("PyTest")
from PyTest import PyClass

class Utils(object):

    @staticmethod
    def prepare_dict(**kwargs):

        dict_net = Dictionary[String,Object]()
        for key_value in kwargs.keys():
            dict_net.Add(key_value,kwargs[key_value])
        return dict_net

parameters = {}
parameters['index'] = 1
net_dict = Utils.prepare_dict(**parameters)
obj = PyClass()
obj.DictMethod(net_dict)
```

Output in 3.x version:
```
C:\Users\Athul\Desktop>c:\Python37\python.exe pynettest.py
Inside
Python.Runtime.PyInt
Traceback (most recent call last):
  File "pynettest.py", line 50, in <module>
    obj.DictMethod(net_dict)
System.InvalidCastException: Unable to cast object of type 'Python.Runtime.PyInt' to type 'System.IConvertible'.
   at System.Convert.ToInt32(Object value)
   at PyTest.PyClass.DictMethod(Dictionary`2 paramDict) in C:\Users\Athul\source\repos\PyTest\PyClass.cs:line 46
```


Output in 2.x version : 

```
C:\Users\Athul\Desktop>c:\Python37\python.exe pynettest.py
Inside
System.Int32

C:\Users\Athul\Desktop>
```


Discussion / hints:
This behavioural change is intentional as converting automatically to int32 (or int64, or maybe BigInteger) created too many problems (https://github.com/pythonnet/pythonnet/pull/1568). One might be able to implement `IConvertible` to recover your particular use-case, PRs are welcome :)
@filmor @athulds  Any idea , when will be correct version of pythonnet which is supported by python 3.10 is getting released ?

Fix patch:
diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs
index a90f31513..a99961aaa 100644
--- a/src/runtime/Converter.cs
+++ b/src/runtime/Converter.cs
@@ -550,7 +550,7 @@ internal static int ToInt32(BorrowedReference value)
         /// <summary>
         /// Convert a Python value to an instance of a primitive managed type.
         /// </summary>
-        private static bool ToPrimitive(BorrowedReference value, Type obType, out object? result, bool setError)
+        internal static bool ToPrimitive(BorrowedReference value, Type obType, out object? result, bool setError)
         {
             result = null;
             if (obType.IsEnum)
diff --git a/src/runtime/PythonTypes/PyFloat.cs b/src/runtime/PythonTypes/PyFloat.cs
index 7fb9e8f4d..10104c10f 100644
--- a/src/runtime/PythonTypes/PyFloat.cs
+++ b/src/runtime/PythonTypes/PyFloat.cs
@@ -101,5 +101,7 @@ public static PyFloat AsFloat(PyObject value)
             PythonException.ThrowIfIsNull(op);
             return new PyFloat(op.Steal());
         }
+
+        public override TypeCode GetTypeCode() => TypeCode.Double;
     }
 }
diff --git a/src/runtime/PythonTypes/PyInt.cs b/src/runtime/PythonTypes/PyInt.cs
index 3dcc6ddb2..6b3dbf210 100644
--- a/src/runtime/PythonTypes/PyInt.cs
+++ b/src/runtime/PythonTypes/PyInt.cs
@@ -230,5 +230,7 @@ public string ToString(string format, IFormatProvider formatProvider)
             using var _ = Py.GIL();
             return ToBigInteger().ToString(format, formatProvider);
         }
+
+        public override TypeCode GetTypeCode() => TypeCode.Int64;
     }
 }
diff --git a/src/runtime/PythonTypes/PyObject.IConvertible.cs b/src/runtime/PythonTypes/PyObject.IConvertible.cs
new file mode 100644
index 000000000..503d3cab4
--- /dev/null
+++ b/src/runtime/PythonTypes/PyObject.IConvertible.cs
@@ -0,0 +1,53 @@
+using System;
+
+namespace Python.Runtime;
+
+public partial class PyObject : IConvertible
+{
+    public virtual TypeCode GetTypeCode() => TypeCode.Object;
+
+    private T DoConvert<T>()
+    {
+        using var _ = Py.GIL();
+        if (Converter.ToPrimitive(Reference, typeof(T), out object? result, setError: false))
+        {
+            return (T)result!;
+        }
+        else
+        {
+            throw new InvalidCastException();
+        }
+    }
+
+    public bool ToBoolean(IFormatProvider provider) => DoConvert<bool>();
+    public byte ToByte(IFormatProvider provider) => DoConvert<byte>();
+    public char ToChar(IFormatProvider provider) => DoConvert<char>();
+    public short ToInt16(IFormatProvider provider) => DoConvert<short>();
+    public int ToInt32(IFormatProvider provider) => DoConvert<int>();
+    public long ToInt64(IFormatProvider provider) => DoConvert<long>();
+    public sbyte ToSByte(IFormatProvider provider) => DoConvert<sbyte>();
+    public ushort ToUInt16(IFormatProvider provider) => DoConvert<ushort>();
+    public uint ToUInt32(IFormatProvider provider) => DoConvert<uint>();
+    public ulong ToUInt64(IFormatProvider provider) => DoConvert<ulong>();
+
+    public float ToSingle(IFormatProvider provider) => DoConvert<float>();
+    public double ToDouble(IFormatProvider provider) => DoConvert<double>();
+
+    public string ToString(IFormatProvider provider) => DoConvert<string>();
+
+    public DateTime ToDateTime(IFormatProvider provider) => throw new InvalidCastException();
+    public decimal ToDecimal(IFormatProvider provider) => throw new InvalidCastException();
+
+    public object ToType(Type conversionType, IFormatProvider provider)
+    {
+        if (Converter.ToManaged(Reference, conversionType, out object? result, setError: false))
+        {
+            return result!;
+        }
+        else
+        {
+            throw new InvalidCastException();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/runtime/PythonTypes/PyString.cs b/src/runtime/PythonTypes/PyString.cs
index cdd45e2c3..d54397fcf 100644
--- a/src/runtime/PythonTypes/PyString.cs
+++ b/src/runtime/PythonTypes/PyString.cs
@@ -59,5 +59,7 @@ public static bool IsStringType(PyObject value)
         {
             return Runtime.PyString_Check(value.obj);
         }
+
+        public override TypeCode GetTypeCode() => TypeCode.String;
     }
 }


Test patch:
diff --git a/tests/test_conversion.py b/tests/test_conversion.py
index 341b11b90..4de286b14 100644
--- a/tests/test_conversion.py
+++ b/tests/test_conversion.py
@@ -670,3 +670,10 @@ def test_int_param_resolution_required():
     data = list(mri.MethodA(0x100000000, 10))
     assert len(data) == 10
     assert data[0] == 0
+
+def test_iconvertible_conversion():
+    change_type = System.Convert.ChangeType
+
+    assert 1024 == change_type(1024, System.Int32)
+    assert 1024 == change_type(1024, System.Int64)
+    assert 1024 == change_type(1024, System.Int16)