From b1a495105e1c8c8d69f016d48c02a126054fccc3 Mon Sep 17 00:00:00 2001 From: randomC0der <39627670+randomC0der@users.noreply.github.com> Date: Thu, 8 Apr 2021 17:08:47 +0000 Subject: [PATCH] Valuetype bugfix (#1) * Add value type test cases * Fix runtime value type * Fix design time value type * Tidying * Remove tail call optimisation * Remove argument count * Removing unused Lambdas Co-authored-by: Steve Wilkes --- .../InstanceFromTypeCreationTests.cs | 36 ++++++++++++ ...reateInstanceFromType2020DesignTimeArgs.cs | 41 +++++++++---- .../CreateInstanceFromType2020RuntimeArgs.cs | 58 ++++++++++++------- 3 files changed, 105 insertions(+), 30 deletions(-) diff --git a/CreateInstanceFromType.Tests/InstanceFromTypeCreationTests.cs b/CreateInstanceFromType.Tests/InstanceFromTypeCreationTests.cs index 246685b..23c5bd0 100644 --- a/CreateInstanceFromType.Tests/InstanceFromTypeCreationTests.cs +++ b/CreateInstanceFromType.Tests/InstanceFromTypeCreationTests.cs @@ -15,6 +15,24 @@ namespace CreateInstanceFromType.Tests Assert.NotNull(instance); } + [Fact] + public void ShouldUseAParameterlessValueTypeCtor() + { + var instance = (Guid)CreateInstanceFromType2020RuntimeArgs + .GetInstance(typeof(Guid)); + + Assert.Equal(default, instance); + } + + [Fact] + public void ShouldUseAParameterlessValueTypeCtorDesign() + { + var instance = (Guid)CreateInstanceFromType2020DesignTimeArgs + .GetInstance(typeof(Guid)); + + Assert.Equal(default, instance); + } + [Fact] public void ShouldUseASingleParameterCtor() { @@ -24,6 +42,24 @@ namespace CreateInstanceFromType.Tests Assert.NotNull(instance); Assert.Equal("hello!", instance.Value); } + + [Fact] + public void ShouldUseASingleParameterValueTypeCtor() + { + var instance = (Guid)CreateInstanceFromType2020RuntimeArgs + .GetInstance(typeof(Guid), "5e55498a-86e1-495c-b829-0c5170346ef5"); + + Assert.Equal(Guid.Parse("5e55498a-86e1-495c-b829-0c5170346ef5"), instance); + } + + [Fact] + public void ShouldUseASingleParameterValueTypeCtorDesign() + { + var instance = (Guid)CreateInstanceFromType2020DesignTimeArgs + .GetInstance(typeof(Guid), "5e55498a-86e1-495c-b829-0c5170346ef5"); + + Assert.Equal(Guid.Parse("5e55498a-86e1-495c-b829-0c5170346ef5"), instance); + } [Fact] public void ShouldUseATwoParameterCtor() diff --git a/CreateInstanceFromType/CreateInstanceFromType2020DesignTimeArgs.cs b/CreateInstanceFromType/CreateInstanceFromType2020DesignTimeArgs.cs index 3caea69..51f4817 100644 --- a/CreateInstanceFromType/CreateInstanceFromType2020DesignTimeArgs.cs +++ b/CreateInstanceFromType/CreateInstanceFromType2020DesignTimeArgs.cs @@ -93,11 +93,14 @@ namespace CreateInstanceFromType { return _factoriesByType.GetOrAdd(type, t => { - // The default, parameterless constructor: - var ctor = t.GetConstructor(Type.EmptyTypes); - // An Expression representing the default constructor call: - var instanceCreation = Expression.New(ctor); + Expression instanceCreation = Expression.New(t); + + if (t.IsValueType) + { + // A value type needs additional boxing: + instanceCreation = Expression.Convert(instanceCreation, typeof(object)); + } // Compile the Expression into a Func which returns the // constructed object: @@ -124,14 +127,20 @@ namespace CreateInstanceFromType // The matching constructor: var ctor = t.GetConstructor(new[] { argType }); - + // An Expression representing the parameter to // pass to the Func and constructor: var argument = Expression.Parameter(argType, "param"); // An Expression representing the constructor call, // passing in the constructor parameter: - var instanceCreation = Expression.New(ctor, argument); + Expression instanceCreation = Expression.New(ctor, argument); + + if (t.IsValueType) + { + // A value type needs additional boxing: + instanceCreation = Expression.Convert(instanceCreation, typeof(object)); + } // Compile the Expression into a Func which takes one // argument and returns the constructed object: @@ -159,7 +168,7 @@ namespace CreateInstanceFromType // The matching constructor: var ctor = t.GetConstructor(new[] { arg1Type, arg2Type }); - + // A set of Expressions representing the parameters to // pass to the Func and constructor: var argument1 = Expression.Parameter(arg1Type, "param1"); @@ -167,9 +176,15 @@ namespace CreateInstanceFromType // An Expression representing the constructor call, // passing in the constructor parameters: - var instanceCreation = Expression + Expression instanceCreation = Expression .New(ctor, argument1, argument2); + if (t.IsValueType) + { + // A value type needs additional boxing: + instanceCreation = Expression.Convert(instanceCreation, typeof(object)); + } + // Compile the Expression into a Func which takes two // arguments and returns the constructed object: var instanceCreationLambda = Expression @@ -197,7 +212,7 @@ namespace CreateInstanceFromType // The matching constructor: var ctor = t.GetConstructor(new[] { arg1Type, arg2Type, arg3Type }); - + // A set of Expressions representing the parameters to // pass to the Func and constructor: var argument1 = Expression.Parameter(arg1Type, "param1"); @@ -206,9 +221,15 @@ namespace CreateInstanceFromType // An Expression representing the constructor call, // passing in the constructor parameters: - var instanceCreation = Expression + Expression instanceCreation = Expression .New(ctor, argument1, argument2, argument3); + if (t.IsValueType) + { + // A value type needs additional boxing: + instanceCreation = Expression.Convert(instanceCreation, typeof(object)); + } + // Compile the Expression into a Func which takes three // arguments and returns the constructed object: var instanceCreationLambda = Expression diff --git a/CreateInstanceFromType/CreateInstanceFromType2020RuntimeArgs.cs b/CreateInstanceFromType/CreateInstanceFromType2020RuntimeArgs.cs index e9354dc..27aab99 100644 --- a/CreateInstanceFromType/CreateInstanceFromType2020RuntimeArgs.cs +++ b/CreateInstanceFromType/CreateInstanceFromType2020RuntimeArgs.cs @@ -27,16 +27,43 @@ private static Func CreateObjectFactory(TypeFactoryKey key) { - var argumentTypes = key.ArgumentTypes; - var argumentCount = argumentTypes.Length; - - // The constructor which matches the given argument types: - var instanceTypeCtor = GetMatchingConstructor(key, argumentTypes, argumentCount); - // An Expression representing the parameter to pass // to the Func: var lambdaParameters = Expression.Parameter(typeof(object[]), "params"); + // Get an Expression representing the 'new' constructor call: + var instanceCreation = GetInstanceCreation(key, lambdaParameters); + + if (key.Type.IsValueType) + { + // A value type needs additional boxing: + instanceCreation = Expression + .Convert(instanceCreation, typeof(object)); + } + + // Compile the Expression into a Func which takes an + // object argument array and returns the constructed object: + var instanceCreationLambda = Expression + .Lambda>(instanceCreation, lambdaParameters); + + return instanceCreationLambda.Compile(); + } + + private static Expression GetInstanceCreation( + TypeFactoryKey key, + Expression lambdaParameters) + { + var argumentTypes = key.ArgumentTypes; + var argumentCount = argumentTypes.Length; + + if (key.Type.IsValueType && argumentCount == 0) + { + return Expression.New(key.Type); + } + + // The constructor which matches the given argument types: + var instanceTypeCtor = GetMatchingConstructor(key, argumentTypes); + // A set of Expressions representing the parameters to pass // to the constructor: var ctorArguments = new Expression[argumentCount]; @@ -58,35 +85,26 @@ // An Expression representing the constructor call, // passing in the constructor parameters: - var instanceCreation = Expression.New(instanceTypeCtor, ctorArguments); - - // Compile the Expression into a Func which takes an - // object argument array and returns the constructed object: - var instanceCreationLambda = Expression - .Lambda>(instanceCreation, lambdaParameters); - - return instanceCreationLambda.Compile(); + return Expression.New(instanceTypeCtor, ctorArguments); } - private static ConstructorInfo GetMatchingConstructor( - TypeFactoryKey key, - Type[] argumentTypes, - int argumentCount) + private static ConstructorInfo GetMatchingConstructor(TypeFactoryKey key, Type[] argumentTypes) { if (!key.HasNullArgumentTypes) { return key.Type.GetConstructor( Public | Instance, - binder: null, + Type.DefaultBinder, CallingConventions.HasThis, argumentTypes, - new ParameterModifier[0]) ?? + Array.Empty()) ?? throw new NotSupportedException("Failed to find a matching constructor"); } var constructors = key.Type.GetConstructors(Public | Instance); var matchingCtor = default(ConstructorInfo); var parameters = default(ParameterInfo[]); + var argumentCount = argumentTypes.Length; for (int i = 0, l = constructors.Length; i < l; ++i) {