From 4a375721acdb3dfe07089a7b604144da9e0452d1 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 31 Aug 2014 15:33:19 +1200 Subject: [PATCH] -Added support for constructors with out and ref parameters --- .../Newtonsoft.Json.Tests.Net20.csproj | 1 + .../Newtonsoft.Json.Tests.Net35.csproj | 1 + .../Newtonsoft.Json.Tests.Net40.csproj | 1 + .../Newtonsoft.Json.Tests.Portable.csproj | 1 + .../Newtonsoft.Json.Tests.Portable40.csproj | 1 + .../Newtonsoft.Json.Tests.csproj | 1 + .../Properties/AssemblyInfo.cs | 2 +- .../Serialization/JsonSerializerTest.cs | 2 +- Src/Newtonsoft.Json.Tests/TestFixtureBase.cs | 1 - .../DynamicReflectionDelegateFactoryTests.cs | 47 +++++++++++ ...xpressionReflectionDelegateFactoryTests.cs | 42 ++++++++++ ...LateboundReflectionDelegateFactoryTests.cs | 83 +++++++++++++++++++ .../Properties/AssemblyInfo.cs | 2 +- .../DynamicReflectionDelegateFactory.cs | 69 +++++++++++++-- .../ExpressionReflectionDelegateFactory.cs | 50 ++++++++++- 15 files changed, 290 insertions(+), 14 deletions(-) create mode 100644 Src/Newtonsoft.Json.Tests/Utilities/LateboundReflectionDelegateFactoryTests.cs diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj index d6c44749..97a08bb7 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj @@ -286,6 +286,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net35.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net35.csproj index ba48e820..22f1b1f5 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net35.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net35.csproj @@ -329,6 +329,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net40.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net40.csproj index 356e47d8..92d46d05 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net40.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net40.csproj @@ -451,6 +451,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable.csproj index e06c8aa4..4dbae464 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable.csproj @@ -332,6 +332,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable40.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable40.csproj index b0b00751..57302c64 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable40.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable40.csproj @@ -329,6 +329,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj index b2b696df..773e0b6a 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj @@ -407,6 +407,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index b2213523..aed2b78f 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -75,4 +75,4 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("6.0.0.0")] -[assembly: AssemblyFileVersion("6.0.4.17624")] +[assembly: AssemblyFileVersion("6.0.4.17631")] diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 35d72cf7..92d02a0a 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -127,7 +127,7 @@ namespace Newtonsoft.Json.Tests.Serialization } #endif -#if !NET20 +#if !(NET20 || NET35) [Test] public void DeserializeObservableCollection() { diff --git a/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs b/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs index 3ff2597f..15044ce6 100644 --- a/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs +++ b/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs @@ -48,7 +48,6 @@ using System.Collections; using Newtonsoft.Json.Utilities.LinqBridge; #else using System.Linq; - #endif namespace Newtonsoft.Json.Tests diff --git a/Src/Newtonsoft.Json.Tests/Utilities/DynamicReflectionDelegateFactoryTests.cs b/Src/Newtonsoft.Json.Tests/Utilities/DynamicReflectionDelegateFactoryTests.cs index 9eea3acf..13ac99f7 100644 --- a/Src/Newtonsoft.Json.Tests/Utilities/DynamicReflectionDelegateFactoryTests.cs +++ b/Src/Newtonsoft.Json.Tests/Utilities/DynamicReflectionDelegateFactoryTests.cs @@ -38,12 +38,59 @@ using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Utilities; using Newtonsoft.Json.Tests.TestObjects; using Newtonsoft.Json.Tests.Serialization; +#if NET20 +using Newtonsoft.Json.Utilities.LinqBridge; +#else +using System.Linq; +#endif namespace Newtonsoft.Json.Tests.Utilities { [TestFixture] public class DynamicReflectionDelegateFactoryTests : TestFixtureBase { + [Test] + public void ConstructorWithRefString() + { + ConstructorInfo constructor = typeof(OutAndRefTestClass).GetConstructors().Single(c => c.GetParameters().Count() == 1); + + var creator = DynamicReflectionDelegateFactory.Instance.CreateParametrizedConstructor(constructor); + + object[] args = new object[] { "Input" }; + OutAndRefTestClass o = (OutAndRefTestClass)creator(args); + Assert.IsNotNull(o); + Assert.AreEqual("Input", o.Input); + } + + [Test] + public void ConstructorWithRefStringAndOutBool() + { + ConstructorInfo constructor = typeof(OutAndRefTestClass).GetConstructors().Single(c => c.GetParameters().Count() == 2); + + var creator = DynamicReflectionDelegateFactory.Instance.CreateParametrizedConstructor(constructor); + + object[] args = new object[] { "Input", false }; + OutAndRefTestClass o = (OutAndRefTestClass)creator(args); + Assert.IsNotNull(o); + Assert.AreEqual("Input", o.Input); + Assert.AreEqual(true, o.B1); + } + + [Test] + public void ConstructorWithRefStringAndRefBoolAndRefBool() + { + ConstructorInfo constructor = typeof(OutAndRefTestClass).GetConstructors().Single(c => c.GetParameters().Count() == 3); + + var creator = DynamicReflectionDelegateFactory.Instance.CreateParametrizedConstructor(constructor); + + object[] args = new object[] { "Input", true, null }; + OutAndRefTestClass o = (OutAndRefTestClass)creator(args); + Assert.IsNotNull(o); + Assert.AreEqual("Input", o.Input); + Assert.AreEqual(true, o.B1); + Assert.AreEqual(false, o.B2); + } + [Test] public void CreateGetWithBadObjectTarget() { diff --git a/Src/Newtonsoft.Json.Tests/Utilities/ExpressionReflectionDelegateFactoryTests.cs b/Src/Newtonsoft.Json.Tests/Utilities/ExpressionReflectionDelegateFactoryTests.cs index 6129509b..7db83186 100644 --- a/Src/Newtonsoft.Json.Tests/Utilities/ExpressionReflectionDelegateFactoryTests.cs +++ b/Src/Newtonsoft.Json.Tests/Utilities/ExpressionReflectionDelegateFactoryTests.cs @@ -24,6 +24,7 @@ #endregion #if !(NET20 || NET35) +using System.Linq; using System; using System.Diagnostics; using System.Reflection; @@ -44,6 +45,47 @@ namespace Newtonsoft.Json.Tests.Utilities [TestFixture] public class ExpressionReflectionDelegateFactoryTests : TestFixtureBase { + [Test] + public void ConstructorWithRefString() + { + ConstructorInfo constructor = typeof(OutAndRefTestClass).GetConstructors().Single(c => c.GetParameters().Count() == 1); + + var creator = ExpressionReflectionDelegateFactory.Instance.CreateParametrizedConstructor(constructor); + + object[] args = new object[] { "Input" }; + OutAndRefTestClass o = (OutAndRefTestClass)creator(args); + Assert.IsNotNull(o); + Assert.AreEqual("Input", o.Input); + } + + [Test] + public void ConstructorWithRefStringAndOutBool() + { + ConstructorInfo constructor = typeof(OutAndRefTestClass).GetConstructors().Single(c => c.GetParameters().Count() == 2); + + var creator = ExpressionReflectionDelegateFactory.Instance.CreateParametrizedConstructor(constructor); + + object[] args = new object[] { "Input", null }; + OutAndRefTestClass o = (OutAndRefTestClass)creator(args); + Assert.IsNotNull(o); + Assert.AreEqual("Input", o.Input); + } + + [Test] + public void ConstructorWithRefStringAndRefBoolAndRefBool() + { + ConstructorInfo constructor = typeof(OutAndRefTestClass).GetConstructors().Single(c => c.GetParameters().Count() == 3); + + var creator = ExpressionReflectionDelegateFactory.Instance.CreateParametrizedConstructor(constructor); + + object[] args = new object[] { "Input", true, null }; + OutAndRefTestClass o = (OutAndRefTestClass)creator(args); + Assert.IsNotNull(o); + Assert.AreEqual("Input", o.Input); + Assert.AreEqual(true, o.B1); + Assert.AreEqual(false, o.B2); + } + [Test] public void DefaultConstructor() { diff --git a/Src/Newtonsoft.Json.Tests/Utilities/LateboundReflectionDelegateFactoryTests.cs b/Src/Newtonsoft.Json.Tests/Utilities/LateboundReflectionDelegateFactoryTests.cs new file mode 100644 index 00000000..ef61adc0 --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/Utilities/LateboundReflectionDelegateFactoryTests.cs @@ -0,0 +1,83 @@ +using System.Reflection; +using Newtonsoft.Json.Utilities; +using NUnit.Framework; +#if NET20 +using Newtonsoft.Json.Utilities.LinqBridge; +#else +using System.Linq; +#endif + +namespace Newtonsoft.Json.Tests.Utilities +{ + public class OutAndRefTestClass + { + public string Input { get; set; } + public bool B1 { get; set; } + public bool B2 { get; set; } + + public OutAndRefTestClass(ref string value) + { + Input = value; + value = "Output"; + } + + public OutAndRefTestClass(ref string value, out bool b1) + : this(ref value) + { + b1 = true; + B1 = true; + } + + public OutAndRefTestClass(ref string value, ref bool b1, ref bool b2) + : this(ref value) + { + B1 = b1; + B2 = b2; + } + } + + [TestFixture] + public class LateboundReflectionDelegateFactoryTests : TestFixtureBase + { + [Test] + public void ConstructorWithRefString() + { + ConstructorInfo constructor = typeof(OutAndRefTestClass).GetConstructors().Single(c => c.GetParameters().Count() == 1); + + var creator = LateBoundReflectionDelegateFactory.Instance.CreateParametrizedConstructor(constructor); + + object[] args = new object[] { "Input" }; + OutAndRefTestClass o = (OutAndRefTestClass)creator(args); + Assert.IsNotNull(o); + Assert.AreEqual("Input", o.Input); + } + + [Test] + public void ConstructorWithRefStringAndOutBool() + { + ConstructorInfo constructor = typeof(OutAndRefTestClass).GetConstructors().Single(c => c.GetParameters().Count() == 2); + + var creator = LateBoundReflectionDelegateFactory.Instance.CreateParametrizedConstructor(constructor); + + object[] args = new object[] { "Input", null }; + OutAndRefTestClass o = (OutAndRefTestClass)creator(args); + Assert.IsNotNull(o); + Assert.AreEqual("Input", o.Input); + } + + [Test] + public void ConstructorWithRefStringAndRefBoolAndRefBool() + { + ConstructorInfo constructor = typeof(OutAndRefTestClass).GetConstructors().Single(c => c.GetParameters().Count() == 3); + + var creator = LateBoundReflectionDelegateFactory.Instance.CreateParametrizedConstructor(constructor); + + object[] args = new object[] { "Input", true, null }; + OutAndRefTestClass o = (OutAndRefTestClass)creator(args); + Assert.IsNotNull(o); + Assert.AreEqual("Input", o.Input); + Assert.AreEqual(true, o.B1); + Assert.AreEqual(false, o.B2); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index 52fe5f60..b90d57ef 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -91,5 +91,5 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("6.0.0.0")] -[assembly: AssemblyFileVersion("6.0.4.17624")] +[assembly: AssemblyFileVersion("6.0.4.17631")] [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs index fc5cd25c..4c251e81 100644 --- a/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs @@ -92,15 +92,63 @@ namespace Newtonsoft.Json.Utilities for (int i = 0; i < args.Length; i++) { - Type parameterType = args[i].ParameterType; + ParameterInfo parameter = args[i]; + Type parameterType = parameter.ParameterType; - generator.Emit(OpCodes.Ldarg, argsIndex); - - generator.Emit(OpCodes.Ldc_I4, i); - generator.Emit(OpCodes.Ldelem_Ref); - - if (parameterType.IsValueType()) + if (parameterType.IsByRef) { + parameterType = parameterType.GetElementType(); + + LocalBuilder localVariable = generator.DeclareLocal(parameterType); + + // don't need to set variable for 'out' parameter + if (!parameter.IsOut) + { + generator.Emit(OpCodes.Ldarg, argsIndex); + generator.Emit(OpCodes.Ldc_I4, i); + generator.Emit(OpCodes.Ldelem_Ref); + + if (parameterType.IsValueType()) + { + Label skipSettingDefault = generator.DefineLabel(); + Label finishedProcessingParameter = generator.DefineLabel(); + + // check if parameter is not null + generator.Emit(OpCodes.Brtrue_S, skipSettingDefault); + + // parameter has no value, initialize to default + generator.Emit(OpCodes.Ldloca_S, localVariable); + generator.Emit(OpCodes.Initobj, parameterType); + generator.Emit(OpCodes.Br_S, finishedProcessingParameter); + + // parameter has value, get value from array again and unbox and set to variable + generator.MarkLabel(skipSettingDefault); + generator.Emit(OpCodes.Ldarg, argsIndex); + generator.Emit(OpCodes.Ldc_I4, i); + generator.Emit(OpCodes.Ldelem_Ref); + generator.UnboxIfNeeded(parameterType); + generator.Emit(OpCodes.Stloc, localVariableCount); + + // parameter finished, we out! + generator.MarkLabel(finishedProcessingParameter); + } + else + { + generator.UnboxIfNeeded(parameterType); + generator.Emit(OpCodes.Stloc, localVariableCount); + } + } + + generator.Emit(OpCodes.Ldloca_S, localVariable); + + localVariableCount++; + } + else if (parameterType.IsValueType()) + { + generator.Emit(OpCodes.Ldarg, argsIndex); + generator.Emit(OpCodes.Ldc_I4, i); + generator.Emit(OpCodes.Ldelem_Ref); + // have to check that value type parameters aren't null // otherwise they will error when unboxed Label skipSettingDefault = generator.DefineLabel(); @@ -127,8 +175,12 @@ namespace Newtonsoft.Json.Utilities generator.MarkLabel(finishedProcessingParameter); localVariableCount++; } - else + else { + generator.Emit(OpCodes.Ldarg, argsIndex); + generator.Emit(OpCodes.Ldc_I4, i); + generator.Emit(OpCodes.Ldelem_Ref); + generator.UnboxIfNeeded(parameterType); } } @@ -283,5 +335,4 @@ namespace Newtonsoft.Json.Utilities } } } - #endif \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs index d6f2dbbf..6cb95a37 100644 --- a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs @@ -24,6 +24,9 @@ #endregion #if !(NET20 || NET35) +using System.Collections.Generic; +using System.Linq; +using System.Text; using System; using System.Linq.Expressions; using System.Reflection; @@ -73,15 +76,30 @@ namespace Newtonsoft.Json.Utilities return compiled; } + private class ByRefParameter + { + public Expression Value; + public ParameterExpression Variable; + public bool IsOut; + } + private Expression BuildMethodCall(MethodBase method, Type type, ParameterExpression targetParameterExpression, ParameterExpression argsParameterExpression) { ParameterInfo[] parametersInfo = method.GetParameters(); Expression[] argsExpression = new Expression[parametersInfo.Length]; + IList refParameterMap = new List(); for (int i = 0; i < parametersInfo.Length; i++) { - Type parameterType = parametersInfo[i].ParameterType; + ParameterInfo parameter = parametersInfo[i]; + Type parameterType = parameter.ParameterType; + bool isByRef = false; + if (parameterType.IsByRef) + { + parameterType = parameterType.GetElementType(); + isByRef = true; + } Expression indexExpression = Expression.Constant(i); @@ -100,6 +118,19 @@ namespace Newtonsoft.Json.Utilities argExpression = EnsureCastExpression(paramAccessorExpression, parameterType); } + if (isByRef) + { + ParameterExpression variable = Expression.Variable(parameterType); + refParameterMap.Add(new ByRefParameter + { + Value = argExpression, + Variable = variable, + IsOut = parameter.IsOut + }); + + argExpression = variable; + } + argsExpression[i] = argExpression; } @@ -132,6 +163,23 @@ namespace Newtonsoft.Json.Utilities callExpression = EnsureCastExpression(callExpression, type); } + if (refParameterMap.Count > 0) + { + IList variableExpressions = new List(); + IList bodyExpressions = new List(); + foreach (ByRefParameter p in refParameterMap) + { + if (!p.IsOut) + bodyExpressions.Add(Expression.Assign(p.Variable, p.Value)); + + variableExpressions.Add(p.Variable); + } + + bodyExpressions.Add(callExpression); + + callExpression = Expression.Block(variableExpressions, bodyExpressions); + } + return callExpression; }