diff --git a/src/CSnakes.Runtime/PyObjectTypeConverter.Dictionary.cs b/src/CSnakes.Runtime/PyObjectTypeConverter.Dictionary.cs index 9b0ac662..d9d9caab 100644 --- a/src/CSnakes.Runtime/PyObjectTypeConverter.Dictionary.cs +++ b/src/CSnakes.Runtime/PyObjectTypeConverter.Dictionary.cs @@ -4,17 +4,28 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Globalization; +using System.Reflection; namespace CSnakes.Runtime; internal partial class PyObjectTypeConverter { private object? ConvertToDictionary(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture, bool useMappingProtocol = false) { - using PyObject items = useMappingProtocol ? new(CPythonAPI.PyMapping_Items(pyObject)) : new(CPythonAPI.PyDict_Items(pyObject)); - Type item1Type = destinationType.GetGenericArguments()[0]; - Type item2Type = destinationType.GetGenericArguments()[1]; - Type dictType = typeof(Dictionary<,>).MakeGenericType(item1Type, item2Type); - IDictionary dict = (IDictionary)Activator.CreateInstance(dictType)!; + using PyObject items = useMappingProtocol ? new(CPythonAPI.PyMapping_Items(pyObject)) : new(CPythonAPI.PyDict_Items(pyObject)); + + Type item1Type = destinationType.GetGenericArguments()[0]; + Type item2Type = destinationType.GetGenericArguments()[1]; + + if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo)) + { + Type dictType = typeof(Dictionary<,>).MakeGenericType(item1Type, item2Type); + Type returnType = typeof(ReadOnlyDictionary<,>).MakeGenericType(item1Type, item2Type); + + typeInfo = new(returnType.GetConstructor([dictType])!, dictType.GetConstructor([])!); + knownDynamicTypes[destinationType] = typeInfo; + } + + IDictionary dict = (IDictionary)typeInfo.TransientTypeConstructor!.Invoke([]); nint itemsLength = CPythonAPI.PyList_Size(items); for (nint i = 0; i < itemsLength; i++) @@ -24,14 +35,13 @@ internal partial class PyObjectTypeConverter using PyObject item1 = new(CPythonAPI.PyTuple_GetItem(item, 0)); using PyObject item2 = new(CPythonAPI.PyTuple_GetItem(item, 1)); - object? convertedItem1 = AsManagedObject(item1Type, item1, context, culture); - object? convertedItem2 = AsManagedObject(item2Type, item2, context, culture); + object? convertedItem1 = ConvertTo(context, culture, item1, item1Type); + object? convertedItem2 = ConvertTo(context, culture, item2, item2Type); dict.Add(convertedItem1!, convertedItem2); - } - - Type returnType = typeof(ReadOnlyDictionary<,>).MakeGenericType(item1Type, item2Type); - return Activator.CreateInstance(returnType, dict); + } + + return typeInfo.ReturnTypeConstructor.Invoke([dict]); } private PyObject ConvertFromDictionary(ITypeDescriptorContext? context, CultureInfo? culture, IDictionary dictionary) diff --git a/src/CSnakes.Runtime/PyObjectTypeConverter.Generator.cs b/src/CSnakes.Runtime/PyObjectTypeConverter.Generator.cs index 835c7980..20de6132 100644 --- a/src/CSnakes.Runtime/PyObjectTypeConverter.Generator.cs +++ b/src/CSnakes.Runtime/PyObjectTypeConverter.Generator.cs @@ -8,11 +8,17 @@ internal partial class PyObjectTypeConverter { private object? ConvertToGeneratorIterator(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture) { - Type item1Type = destinationType.GetGenericArguments()[0]; - Type item2Type = destinationType.GetGenericArguments()[1]; - Type item3Type = destinationType.GetGenericArguments()[2]; - Type generatorType = typeof(GeneratorIterator<,,>).MakeGenericType(item1Type, item2Type, item3Type); - ConstructorInfo ctor = generatorType.GetConstructors().First(); - return ctor.Invoke([pyObject.Clone()]); + if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo)) + { + Type item1Type = destinationType.GetGenericArguments()[0]; + Type item2Type = destinationType.GetGenericArguments()[1]; + Type item3Type = destinationType.GetGenericArguments()[2]; + Type generatorType = typeof(GeneratorIterator<,,>).MakeGenericType(item1Type, item2Type, item3Type); + ConstructorInfo ctor = generatorType.GetConstructors().First(); + typeInfo = new(ctor); + knownDynamicTypes[destinationType] = typeInfo; + } + + return typeInfo.ReturnTypeConstructor.Invoke([pyObject.Clone()]); } } \ No newline at end of file diff --git a/src/CSnakes.Runtime/PyObjectTypeConverter.List.cs b/src/CSnakes.Runtime/PyObjectTypeConverter.List.cs index 48c127a0..958f6581 100644 --- a/src/CSnakes.Runtime/PyObjectTypeConverter.List.cs +++ b/src/CSnakes.Runtime/PyObjectTypeConverter.List.cs @@ -9,14 +9,20 @@ internal partial class PyObjectTypeConverter { private object? ConvertToList(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture) { - Type genericArgument = destinationType.GetGenericArguments()[0]; - Type listType = typeof(List<>).MakeGenericType(genericArgument); + Type genericArgument = destinationType.GetGenericArguments()[0]; + + if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo)) + { + Type listType = typeof(List<>).MakeGenericType(genericArgument); + typeInfo = new(listType.GetConstructor([])!); + knownDynamicTypes[destinationType] = typeInfo; + } - IList list = (IList)Activator.CreateInstance(listType)!; + IList list = (IList)typeInfo.ReturnTypeConstructor.Invoke([]); for (var i = 0; i < CPythonAPI.PyList_Size(pyObject); i++) { using PyObject item = new(CPythonAPI.PyList_GetItem(pyObject, i)); - list.Add(AsManagedObject(genericArgument, item, context, culture)); + list.Add(ConvertTo(context, culture, item, genericArgument)); } return list; @@ -24,14 +30,21 @@ internal partial class PyObjectTypeConverter private object? ConvertToListFromSequence(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture) { - Type genericArgument = destinationType.GetGenericArguments()[0]; - Type listType = typeof(List<>).MakeGenericType(genericArgument); + Type genericArgument = destinationType.GetGenericArguments()[0]; + + if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo)) + { + Type listType = typeof(List<>).MakeGenericType(genericArgument); + typeInfo = new(listType.GetConstructor([])!); + knownDynamicTypes[destinationType] = typeInfo; + } + + IList list = (IList)typeInfo.ReturnTypeConstructor.Invoke([]); - IList list = (IList)Activator.CreateInstance(listType)!; for (var i = 0; i < CPythonAPI.PySequence_Size(pyObject); i++) { using PyObject item = new(CPythonAPI.PySequence_GetItem(pyObject, i)); - list.Add(AsManagedObject(genericArgument, item, context, culture)); + list.Add(ConvertTo(context, culture, item, genericArgument)); } return list; diff --git a/src/CSnakes.Runtime/PyObjectTypeConverter.Tuple.cs b/src/CSnakes.Runtime/PyObjectTypeConverter.Tuple.cs index b5ff8e1e..ab552015 100644 --- a/src/CSnakes.Runtime/PyObjectTypeConverter.Tuple.cs +++ b/src/CSnakes.Runtime/PyObjectTypeConverter.Tuple.cs @@ -43,7 +43,7 @@ private PyObject ConvertFromTuple(ITypeDescriptorContext? context, CultureInfo? if (tupleValues.Count > 8) { // We are hitting nested tuples here, which will be treated in a different way. - object?[] firstSeven = tupleValues.Take(7).Select((p, i) => AsManagedObject(types[i], p, context, culture)).ToArray(); + object?[] firstSeven = tupleValues.Take(7).Select((p, i) => ConvertTo(context, culture, p, types[i])).ToArray(); // Get the rest of the values and convert them to a nested tuple. IEnumerable rest = tupleValues.Skip(7); @@ -53,23 +53,29 @@ private PyObject ConvertFromTuple(ITypeDescriptorContext? context, CultureInfo? // Use the decoder pipeline to decode the nested tuple (and its values). // We do this because that means if we have nested nested tuples, they'll be decoded as well. - object? nestedTuple = AsManagedObject(types[7], pyTuple, context, culture); + object? nestedTuple = ConvertTo(context, culture, pyTuple, types[7]); // Append our nested tuple to the first seven values. clrValues = [.. firstSeven, nestedTuple]; } else { - clrValues = tupleValues.Select((p, i) => AsManagedObject(types[i], p, context, culture)).ToArray(); + clrValues = tupleValues.Select((p, i) => ConvertTo(context, culture, p, types[i])).ToArray(); } // Dispose of all the Python values that we captured from the Tuple. foreach (var value in tupleValues) { value.Dispose(); + } + + if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo)) + { + ConstructorInfo ctor = destinationType.GetConstructors().First(c => c.GetParameters().Length == clrValues.Length); + typeInfo = new(ctor); + knownDynamicTypes[destinationType] = typeInfo; } - ConstructorInfo ctor = destinationType.GetConstructors().First(c => c.GetParameters().Length == clrValues.Length); - return (ITuple)ctor.Invoke(clrValues); + return (ITuple)typeInfo.ReturnTypeConstructor.Invoke(clrValues); } } \ No newline at end of file diff --git a/src/CSnakes.Runtime/PyObjectTypeConverter.cs b/src/CSnakes.Runtime/PyObjectTypeConverter.cs index e38ae6f6..54ffb3b5 100644 --- a/src/CSnakes.Runtime/PyObjectTypeConverter.cs +++ b/src/CSnakes.Runtime/PyObjectTypeConverter.cs @@ -6,10 +6,13 @@ using System.Globalization; using System.Runtime.CompilerServices; using System.Numerics; +using System.Collections.Concurrent; +using System.Reflection; namespace CSnakes.Runtime; internal partial class PyObjectTypeConverter : TypeConverter { + private readonly ConcurrentDictionary knownDynamicTypes = []; /// /// Convert a Python object to a CLR managed object. @@ -118,11 +121,6 @@ internal partial class PyObjectTypeConverter : TypeConverter throw new InvalidCastException($"Attempting to cast {destinationType} from {pyObject.GetPythonType()}"); } - private object? AsManagedObject(Type type, PyObject p, ITypeDescriptorContext? context, CultureInfo? culture) - { - return ConvertTo(context, culture, p, type); - } - public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) => value switch { @@ -186,4 +184,6 @@ private PyObject ToPython(object? o, ITypeDescriptorContext? context, CultureInf return result is null ? throw new NotImplementedException() : (PyObject)result; } + + record DynamicTypeInfo(ConstructorInfo ReturnTypeConstructor, ConstructorInfo? TransientTypeConstructor = null); }