-Added support for constructors with out and ref parameters

This commit is contained in:
James Newton-King
2014-08-31 15:33:19 +12:00
parent 695742563b
commit 4a375721ac
15 changed files with 290 additions and 14 deletions
@@ -286,6 +286,7 @@
<Compile Include="Utilities\DateTimeUtilsTests.cs" />
<Compile Include="Utilities\DynamicReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\ExpressionReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\LateboundReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\ReflectionUtilsTests.cs" />
<Compile Include="Utilities\StringUtilsTests.cs" />
</ItemGroup>
@@ -329,6 +329,7 @@
<Compile Include="Utilities\DateTimeUtilsTests.cs" />
<Compile Include="Utilities\DynamicReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\ExpressionReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\LateboundReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\ReflectionUtilsTests.cs" />
<Compile Include="Utilities\StringUtilsTests.cs" />
</ItemGroup>
@@ -451,6 +451,7 @@
<Compile Include="Utilities\DateTimeUtilsTests.cs" />
<Compile Include="Utilities\DynamicReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\ExpressionReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\LateboundReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\ReflectionUtilsTests.cs" />
<Compile Include="Utilities\StringUtilsTests.cs" />
</ItemGroup>
@@ -332,6 +332,7 @@
<Compile Include="Utilities\ExpressionReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\DateTimeUtilsTests.cs" />
<Compile Include="Utilities\DynamicReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\LateboundReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\ReflectionUtilsTests.cs" />
<Compile Include="Utilities\StringUtilsTests.cs" />
</ItemGroup>
@@ -329,6 +329,7 @@
<Compile Include="Utilities\DateTimeUtilsTests.cs" />
<Compile Include="Utilities\DynamicReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\ExpressionReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\LateboundReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\ReflectionUtilsTests.cs" />
<Compile Include="Utilities\StringUtilsTests.cs" />
</ItemGroup>
@@ -407,6 +407,7 @@
<Compile Include="Utilities\DateTimeUtilsTests.cs" />
<Compile Include="Utilities\DynamicReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\ExpressionReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\LateboundReflectionDelegateFactoryTests.cs" />
<Compile Include="Utilities\ReflectionUtilsTests.cs" />
<Compile Include="Utilities\StringUtilsTests.cs" />
</ItemGroup>
@@ -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")]
@@ -127,7 +127,7 @@ namespace Newtonsoft.Json.Tests.Serialization
}
#endif
#if !NET20
#if !(NET20 || NET35)
[Test]
public void DeserializeObservableCollection()
{
@@ -48,7 +48,6 @@ using System.Collections;
using Newtonsoft.Json.Utilities.LinqBridge;
#else
using System.Linq;
#endif
namespace Newtonsoft.Json.Tests
@@ -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()
{
@@ -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()
{
@@ -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);
}
}
}
@@ -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)]
@@ -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
@@ -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<ByRefParameter> refParameterMap = new List<ByRefParameter>();
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<ParameterExpression> variableExpressions = new List<ParameterExpression>();
IList<Expression> bodyExpressions = new List<Expression>();
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;
}