Skip to content

Commit

Permalink
Add logic to convert any array to object array. (#731)
Browse files Browse the repository at this point in the history
  • Loading branch information
StefH authored Aug 9, 2023
1 parent 381f0a0 commit 0b23f3b
Show file tree
Hide file tree
Showing 7 changed files with 411 additions and 228 deletions.
53 changes: 52 additions & 1 deletion src-console/ConsoleApp_net6.0/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;

namespace ConsoleApp_net6._0
{
Expand All @@ -19,6 +21,11 @@ class Program
{
static void Main(string[] args)
{
Issue389DoesNotWork();
return;
Issue389_Works();
return;

var q = new[]
{
new X { Key = "x" },
Expand All @@ -32,6 +39,50 @@ static void Main(string[] args)
Dynamic();
}

private static void Issue389_Works()
{
var strArray = new[] { "1", "2", "3", "4" };
var x = new List<ParameterExpression>();
x.Add(Expression.Parameter(strArray.GetType(), "strArray"));

string query = "string.Join(\",\", strArray)";

var e = DynamicExpressionParser.ParseLambda(x.ToArray(), null, query);
Delegate del = e.Compile();
var result1 = del.DynamicInvoke(new object?[] { strArray });
Console.WriteLine(result1);
}

private static void Issue389WorksWithInts()
{
var intArray = new object[] { 1, 2, 3, 4 };
var x = new List<ParameterExpression>();
x.Add(Expression.Parameter(intArray.GetType(), "intArray"));

string query = "string.Join(\",\", intArray)";

var e = DynamicExpressionParser.ParseLambda(x.ToArray(), null, query);
Delegate del = e.Compile();
var result = del.DynamicInvoke(new object?[] { intArray });

Console.WriteLine(result);
}

private static void Issue389DoesNotWork()
{
var intArray = new [] { 1, 2, 3, 4 };
var x = new List<ParameterExpression>();
x.Add(Expression.Parameter(intArray.GetType(), "intArray"));

string query = "string.Join(\",\", intArray)";

var e = DynamicExpressionParser.ParseLambda(x.ToArray(), null, query);
Delegate del = e.Compile();
var result = del.DynamicInvoke(new object?[] { intArray });

Console.WriteLine(result);
}

private static void Normal()
{
var e = new int[0].AsQueryable();
Expand Down
36 changes: 30 additions & 6 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq.Dynamic.Core.Validation;
Expand Down Expand Up @@ -302,11 +303,6 @@ public Expression ConvertToExpandoObjectAndCreateDynamicExpression(Expression ex
#endif
}

private Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right)
{
return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right });
}

private void WrapConstantExpressions(ref Expression left, ref Expression right)
{
if (_parsingConfig.UseParameterizedNamesInDynamicQuery)
Expand Down Expand Up @@ -359,6 +355,17 @@ public Expression GenerateDefaultExpression(Type type)
#endif
}

public Expression ConvertAnyArrayToObjectArray(Expression arrayExpression)
{
Check.NotNull(arrayExpression);

return Expression.Call(
null,
typeof(ExpressionHelper).GetMethod(nameof(ConvertIfIEnumerableHasValues), BindingFlags.Static | BindingFlags.NonPublic)!,
arrayExpression
);
}

private Expression? GetMemberExpression(Expression? expression)
{
if (ExpressionQualifiesForNullPropagation(expression))
Expand Down Expand Up @@ -436,6 +443,11 @@ private List<Expression> CollectExpressions(bool addSelf, Expression sourceExpre
return list;
}

private static Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right)
{
return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right });
}

private static MethodInfo GetStaticMethod(string methodName, Expression left, Expression right)
{
var methodInfo = left.Type.GetMethod(methodName, new[] { left.Type, right.Type });
Expand Down Expand Up @@ -463,4 +475,16 @@ private static MethodInfo GetStaticMethod(string methodName, Expression left, Ex
{
return unaryExpression?.Operand;
}

private static object[] ConvertIfIEnumerableHasValues(IEnumerable? input)
{
// ReSharper disable once PossibleMultipleEnumeration
if (input != null && input.Cast<object>().Any())
{
// ReSharper disable once PossibleMultipleEnumeration
return input.Cast<object>().ToArray();
}

return new object[0];
}
}
16 changes: 11 additions & 5 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ public ExpressionParser(ParameterExpression[]? parameters, string expression, ob
_keywordsHelper = new KeywordsHelper(_parsingConfig);
_textParser = new TextParser(_parsingConfig, expression);
_numberParser = new NumberParser(parsingConfig);
_methodFinder = new MethodFinder(_parsingConfig);
_expressionHelper = new ExpressionHelper(_parsingConfig);
_methodFinder = new MethodFinder(_parsingConfig, _expressionHelper);
_typeFinder = new TypeFinder(_parsingConfig, _keywordsHelper);
_typeConverterFactory = new TypeConverterFactory(_parsingConfig);

Expand Down Expand Up @@ -1725,29 +1725,34 @@ private bool TryGenerateConversion(Expression sourceExpression, Type destination

private Expression ParseMemberAccess(Type? type, Expression? expression)
{
var isStaticAccess = false;
if (expression != null)
{
type = expression.Type;
}
else
{
isStaticAccess = true;
}

int errorPos = _textParser.CurrentToken.Pos;
string id = GetIdentifier();
_textParser.NextToken();

if (_textParser.CurrentToken.Id == TokenId.OpenParen)
{
if (expression != null && type != typeof(string))
if (!isStaticAccess && type != typeof(string))
{
var enumerableType = TypeHelper.FindGenericType(typeof(IEnumerable<>), type);
if (enumerableType != null)
{
Type elementType = enumerableType.GetTypeInfo().GetGenericTypeArguments()[0];
return ParseEnumerable(expression, elementType, id, errorPos, type);
return ParseEnumerable(expression!, elementType, id, errorPos, type);
}
}

Expression[] args = ParseArgumentList();
switch (_methodFinder.FindMethod(type, id, expression == null, ref expression, ref args, out var methodBase))
switch (_methodFinder.FindMethod(type, id, isStaticAccess, ref expression, ref args, out var methodBase))
{
case 0:
throw ParseError(errorPos, Res.NoApplicableMethod, id, TypeHelper.GetTypeName(type));
Expand All @@ -1764,9 +1769,10 @@ private Expression ParseMemberAccess(Type? type, Expression? expression)
var genericParameters = method.GetParameters().Where(p => p.ParameterType.IsGenericParameter);
var typeArguments = genericParameters.Select(a => args[a.Position].Type);
var constructedMethod = method.MakeGenericMethod(typeArguments.ToArray());

return Expression.Call(expression, constructedMethod, args);
}

return Expression.Call(expression, method, args);

default:
Expand Down
2 changes: 2 additions & 0 deletions src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,6 @@ internal interface IExpressionHelper
Expression ConvertToExpandoObjectAndCreateDynamicExpression(Expression expression, Type type, string propertyName);

Expression GenerateDefaultExpression(Type type);

Expression ConvertAnyArrayToObjectArray(Expression arrayExpression);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq.Dynamic.Core.Validation;
using System.Linq.Expressions;
using System.Reflection;

Expand All @@ -7,14 +8,12 @@ namespace System.Linq.Dynamic.Core.Parser.SupportedMethods
internal class MethodFinder
{
private readonly ParsingConfig _parsingConfig;
private readonly IExpressionHelper _expressionHelper;

/// <summary>
/// Get an instance
/// </summary>
/// <param name="parsingConfig"></param>
public MethodFinder(ParsingConfig parsingConfig)
public MethodFinder(ParsingConfig parsingConfig, IExpressionHelper expressionHelper)
{
_parsingConfig = parsingConfig;
_parsingConfig = Check.NotNull(parsingConfig);
_expressionHelper = Check.NotNull(expressionHelper);
}

public bool ContainsMethod(Type type, string methodName, bool staticAccess, Expression? instance, ref Expression[] args)
Expand Down Expand Up @@ -116,7 +115,26 @@ public int FindBestMethodBasedOnArguments(IEnumerable<MethodBase> methods, ref E
method = methodData.MethodBase;
}

args = methodData.Args;
if (args.Length == 0 || args.Length != methodData.Args.Length)
{
args = methodData.Args;
}
else
{
for (var i = 0; i < args.Length; i++)
{
if (args[i].Type != methodData.Args[i].Type &&
args[i].Type.IsArray && methodData.Args[i].Type.IsArray &&
args[i].Type != typeof(string) && methodData.Args[i].Type == typeof(object[]))
{
args[i] = _expressionHelper.ConvertAnyArrayToObjectArray(args[i]);
}
else
{
args[i] = methodData.Args[i];
}
}
}
}
else
{
Expand All @@ -134,7 +152,7 @@ public int FindIndexer(Type type, Expression[] args, out MethodBase? method)
if (members.Length != 0)
{
IEnumerable<MethodBase> methods = members.OfType<PropertyInfo>().
#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD)
#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD)
Select(p => (MethodBase)p.GetGetMethod()).
Where(m => m != null);
#else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1696,6 +1696,69 @@ public void DynamicExpressionParser_ParseComparisonOperator_DynamicClass_For_Str
isValid.Should().BeTrue();
}

[Fact]
public void DynamicExpressionParser_ParseLambda_HandleStringArray_For_StringJoin()
{
// Arrange
var strArray = new[] { "a", "b", "c" };
var parameterExpressions = new List<ParameterExpression>
{
Expression.Parameter(strArray.GetType(), "strArray")
};

var expression = "string.Join(\",\", strArray)";

// Act
var lambdaExpression = DynamicExpressionParser.ParseLambda(parameterExpressions.ToArray(), null, expression);
var @delegate = lambdaExpression.Compile();
var result = (string)@delegate.DynamicInvoke(new object[] { strArray });

// Assert
result.Should().Be("a,b,c");
}

[Fact]
public void DynamicExpressionParser_ParseLambda_HandleObjectArray_For_StringJoin()
{
// Arrange
var objectArray = new object[] { 1, 2, 3 };
var parameterExpressions = new List<ParameterExpression>
{
Expression.Parameter(objectArray.GetType(), "objectArray")
};

var expression = "string.Join(\",\", objectArray)";

// Act
var lambdaExpression = DynamicExpressionParser.ParseLambda(parameterExpressions.ToArray(), null, expression);
var @delegate = lambdaExpression.Compile();
var result = (string)@delegate.DynamicInvoke(new object[] { objectArray });

// Assert
result.Should().Be("1,2,3");
}

[Fact]
public void DynamicExpressionParser_ParseLambda_HandleIntArray_For_StringJoin()
{
// Arrange
var intArray = new[] { 1, 2, 3 };
var parameterExpressions = new List<ParameterExpression>
{
Expression.Parameter(intArray.GetType(), "intArray")
};

var expression = "string.Join(\",\", intArray)";

// Act
var lambdaExpression = DynamicExpressionParser.ParseLambda(parameterExpressions.ToArray(), null, expression);
var @delegate = lambdaExpression.Compile();
var result = (string)@delegate.DynamicInvoke(intArray);

// Assert
result.Should().Be("1,2,3");
}

public class DefaultDynamicLinqCustomTypeProviderForGenericExtensionMethod : DefaultDynamicLinqCustomTypeProvider
{
public override HashSet<Type> GetCustomTypes() => new HashSet<Type>(base.GetCustomTypes()) { typeof(Methods), typeof(MethodsItemExtension) };
Expand Down
Loading

0 comments on commit 0b23f3b

Please sign in to comment.