Skip to content

Commit 0b23f3b

Browse files
authored
Add logic to convert any array to object array. (#731)
1 parent 381f0a0 commit 0b23f3b

File tree

7 files changed

+411
-228
lines changed

7 files changed

+411
-228
lines changed

src-console/ConsoleApp_net6.0/Program.cs

+52-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using System.Linq.Dynamic.Core;
5+
using System.Linq.Expressions;
46

57
namespace ConsoleApp_net6._0
68
{
@@ -19,6 +21,11 @@ class Program
1921
{
2022
static void Main(string[] args)
2123
{
24+
Issue389DoesNotWork();
25+
return;
26+
Issue389_Works();
27+
return;
28+
2229
var q = new[]
2330
{
2431
new X { Key = "x" },
@@ -32,6 +39,50 @@ static void Main(string[] args)
3239
Dynamic();
3340
}
3441

42+
private static void Issue389_Works()
43+
{
44+
var strArray = new[] { "1", "2", "3", "4" };
45+
var x = new List<ParameterExpression>();
46+
x.Add(Expression.Parameter(strArray.GetType(), "strArray"));
47+
48+
string query = "string.Join(\",\", strArray)";
49+
50+
var e = DynamicExpressionParser.ParseLambda(x.ToArray(), null, query);
51+
Delegate del = e.Compile();
52+
var result1 = del.DynamicInvoke(new object?[] { strArray });
53+
Console.WriteLine(result1);
54+
}
55+
56+
private static void Issue389WorksWithInts()
57+
{
58+
var intArray = new object[] { 1, 2, 3, 4 };
59+
var x = new List<ParameterExpression>();
60+
x.Add(Expression.Parameter(intArray.GetType(), "intArray"));
61+
62+
string query = "string.Join(\",\", intArray)";
63+
64+
var e = DynamicExpressionParser.ParseLambda(x.ToArray(), null, query);
65+
Delegate del = e.Compile();
66+
var result = del.DynamicInvoke(new object?[] { intArray });
67+
68+
Console.WriteLine(result);
69+
}
70+
71+
private static void Issue389DoesNotWork()
72+
{
73+
var intArray = new [] { 1, 2, 3, 4 };
74+
var x = new List<ParameterExpression>();
75+
x.Add(Expression.Parameter(intArray.GetType(), "intArray"));
76+
77+
string query = "string.Join(\",\", intArray)";
78+
79+
var e = DynamicExpressionParser.ParseLambda(x.ToArray(), null, query);
80+
Delegate del = e.Compile();
81+
var result = del.DynamicInvoke(new object?[] { intArray });
82+
83+
Console.WriteLine(result);
84+
}
85+
3586
private static void Normal()
3687
{
3788
var e = new int[0].AsQueryable();

src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs

+30-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System.Collections;
2+
using System.Collections.Generic;
23
using System.Diagnostics.CodeAnalysis;
34
using System.Globalization;
45
using System.Linq.Dynamic.Core.Validation;
@@ -302,11 +303,6 @@ public Expression ConvertToExpandoObjectAndCreateDynamicExpression(Expression ex
302303
#endif
303304
}
304305

305-
private Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right)
306-
{
307-
return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right });
308-
}
309-
310306
private void WrapConstantExpressions(ref Expression left, ref Expression right)
311307
{
312308
if (_parsingConfig.UseParameterizedNamesInDynamicQuery)
@@ -359,6 +355,17 @@ public Expression GenerateDefaultExpression(Type type)
359355
#endif
360356
}
361357

358+
public Expression ConvertAnyArrayToObjectArray(Expression arrayExpression)
359+
{
360+
Check.NotNull(arrayExpression);
361+
362+
return Expression.Call(
363+
null,
364+
typeof(ExpressionHelper).GetMethod(nameof(ConvertIfIEnumerableHasValues), BindingFlags.Static | BindingFlags.NonPublic)!,
365+
arrayExpression
366+
);
367+
}
368+
362369
private Expression? GetMemberExpression(Expression? expression)
363370
{
364371
if (ExpressionQualifiesForNullPropagation(expression))
@@ -436,6 +443,11 @@ private List<Expression> CollectExpressions(bool addSelf, Expression sourceExpre
436443
return list;
437444
}
438445

446+
private static Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right)
447+
{
448+
return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right });
449+
}
450+
439451
private static MethodInfo GetStaticMethod(string methodName, Expression left, Expression right)
440452
{
441453
var methodInfo = left.Type.GetMethod(methodName, new[] { left.Type, right.Type });
@@ -463,4 +475,16 @@ private static MethodInfo GetStaticMethod(string methodName, Expression left, Ex
463475
{
464476
return unaryExpression?.Operand;
465477
}
478+
479+
private static object[] ConvertIfIEnumerableHasValues(IEnumerable? input)
480+
{
481+
// ReSharper disable once PossibleMultipleEnumeration
482+
if (input != null && input.Cast<object>().Any())
483+
{
484+
// ReSharper disable once PossibleMultipleEnumeration
485+
return input.Cast<object>().ToArray();
486+
}
487+
488+
return new object[0];
489+
}
466490
}

src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

+11-5
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ public ExpressionParser(ParameterExpression[]? parameters, string expression, ob
7474
_keywordsHelper = new KeywordsHelper(_parsingConfig);
7575
_textParser = new TextParser(_parsingConfig, expression);
7676
_numberParser = new NumberParser(parsingConfig);
77-
_methodFinder = new MethodFinder(_parsingConfig);
7877
_expressionHelper = new ExpressionHelper(_parsingConfig);
78+
_methodFinder = new MethodFinder(_parsingConfig, _expressionHelper);
7979
_typeFinder = new TypeFinder(_parsingConfig, _keywordsHelper);
8080
_typeConverterFactory = new TypeConverterFactory(_parsingConfig);
8181

@@ -1725,29 +1725,34 @@ private bool TryGenerateConversion(Expression sourceExpression, Type destination
17251725

17261726
private Expression ParseMemberAccess(Type? type, Expression? expression)
17271727
{
1728+
var isStaticAccess = false;
17281729
if (expression != null)
17291730
{
17301731
type = expression.Type;
17311732
}
1733+
else
1734+
{
1735+
isStaticAccess = true;
1736+
}
17321737

17331738
int errorPos = _textParser.CurrentToken.Pos;
17341739
string id = GetIdentifier();
17351740
_textParser.NextToken();
17361741

17371742
if (_textParser.CurrentToken.Id == TokenId.OpenParen)
17381743
{
1739-
if (expression != null && type != typeof(string))
1744+
if (!isStaticAccess && type != typeof(string))
17401745
{
17411746
var enumerableType = TypeHelper.FindGenericType(typeof(IEnumerable<>), type);
17421747
if (enumerableType != null)
17431748
{
17441749
Type elementType = enumerableType.GetTypeInfo().GetGenericTypeArguments()[0];
1745-
return ParseEnumerable(expression, elementType, id, errorPos, type);
1750+
return ParseEnumerable(expression!, elementType, id, errorPos, type);
17461751
}
17471752
}
17481753

17491754
Expression[] args = ParseArgumentList();
1750-
switch (_methodFinder.FindMethod(type, id, expression == null, ref expression, ref args, out var methodBase))
1755+
switch (_methodFinder.FindMethod(type, id, isStaticAccess, ref expression, ref args, out var methodBase))
17511756
{
17521757
case 0:
17531758
throw ParseError(errorPos, Res.NoApplicableMethod, id, TypeHelper.GetTypeName(type));
@@ -1764,9 +1769,10 @@ private Expression ParseMemberAccess(Type? type, Expression? expression)
17641769
var genericParameters = method.GetParameters().Where(p => p.ParameterType.IsGenericParameter);
17651770
var typeArguments = genericParameters.Select(a => args[a.Position].Type);
17661771
var constructedMethod = method.MakeGenericMethod(typeArguments.ToArray());
1772+
17671773
return Expression.Call(expression, constructedMethod, args);
17681774
}
1769-
1775+
17701776
return Expression.Call(expression, method, args);
17711777

17721778
default:

src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs

+2
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,6 @@ internal interface IExpressionHelper
4646
Expression ConvertToExpandoObjectAndCreateDynamicExpression(Expression expression, Type type, string propertyName);
4747

4848
Expression GenerateDefaultExpression(Type type);
49+
50+
Expression ConvertAnyArrayToObjectArray(Expression arrayExpression);
4951
}

src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs

+26-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using System.Linq.Dynamic.Core.Validation;
23
using System.Linq.Expressions;
34
using System.Reflection;
45

@@ -7,14 +8,12 @@ namespace System.Linq.Dynamic.Core.Parser.SupportedMethods
78
internal class MethodFinder
89
{
910
private readonly ParsingConfig _parsingConfig;
11+
private readonly IExpressionHelper _expressionHelper;
1012

11-
/// <summary>
12-
/// Get an instance
13-
/// </summary>
14-
/// <param name="parsingConfig"></param>
15-
public MethodFinder(ParsingConfig parsingConfig)
13+
public MethodFinder(ParsingConfig parsingConfig, IExpressionHelper expressionHelper)
1614
{
17-
_parsingConfig = parsingConfig;
15+
_parsingConfig = Check.NotNull(parsingConfig);
16+
_expressionHelper = Check.NotNull(expressionHelper);
1817
}
1918

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

119-
args = methodData.Args;
118+
if (args.Length == 0 || args.Length != methodData.Args.Length)
119+
{
120+
args = methodData.Args;
121+
}
122+
else
123+
{
124+
for (var i = 0; i < args.Length; i++)
125+
{
126+
if (args[i].Type != methodData.Args[i].Type &&
127+
args[i].Type.IsArray && methodData.Args[i].Type.IsArray &&
128+
args[i].Type != typeof(string) && methodData.Args[i].Type == typeof(object[]))
129+
{
130+
args[i] = _expressionHelper.ConvertAnyArrayToObjectArray(args[i]);
131+
}
132+
else
133+
{
134+
args[i] = methodData.Args[i];
135+
}
136+
}
137+
}
120138
}
121139
else
122140
{
@@ -134,7 +152,7 @@ public int FindIndexer(Type type, Expression[] args, out MethodBase? method)
134152
if (members.Length != 0)
135153
{
136154
IEnumerable<MethodBase> methods = members.OfType<PropertyInfo>().
137-
#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD)
155+
#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD)
138156
Select(p => (MethodBase)p.GetGetMethod()).
139157
Where(m => m != null);
140158
#else

test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs

+63
Original file line numberDiff line numberDiff line change
@@ -1696,6 +1696,69 @@ public void DynamicExpressionParser_ParseComparisonOperator_DynamicClass_For_Str
16961696
isValid.Should().BeTrue();
16971697
}
16981698

1699+
[Fact]
1700+
public void DynamicExpressionParser_ParseLambda_HandleStringArray_For_StringJoin()
1701+
{
1702+
// Arrange
1703+
var strArray = new[] { "a", "b", "c" };
1704+
var parameterExpressions = new List<ParameterExpression>
1705+
{
1706+
Expression.Parameter(strArray.GetType(), "strArray")
1707+
};
1708+
1709+
var expression = "string.Join(\",\", strArray)";
1710+
1711+
// Act
1712+
var lambdaExpression = DynamicExpressionParser.ParseLambda(parameterExpressions.ToArray(), null, expression);
1713+
var @delegate = lambdaExpression.Compile();
1714+
var result = (string)@delegate.DynamicInvoke(new object[] { strArray });
1715+
1716+
// Assert
1717+
result.Should().Be("a,b,c");
1718+
}
1719+
1720+
[Fact]
1721+
public void DynamicExpressionParser_ParseLambda_HandleObjectArray_For_StringJoin()
1722+
{
1723+
// Arrange
1724+
var objectArray = new object[] { 1, 2, 3 };
1725+
var parameterExpressions = new List<ParameterExpression>
1726+
{
1727+
Expression.Parameter(objectArray.GetType(), "objectArray")
1728+
};
1729+
1730+
var expression = "string.Join(\",\", objectArray)";
1731+
1732+
// Act
1733+
var lambdaExpression = DynamicExpressionParser.ParseLambda(parameterExpressions.ToArray(), null, expression);
1734+
var @delegate = lambdaExpression.Compile();
1735+
var result = (string)@delegate.DynamicInvoke(new object[] { objectArray });
1736+
1737+
// Assert
1738+
result.Should().Be("1,2,3");
1739+
}
1740+
1741+
[Fact]
1742+
public void DynamicExpressionParser_ParseLambda_HandleIntArray_For_StringJoin()
1743+
{
1744+
// Arrange
1745+
var intArray = new[] { 1, 2, 3 };
1746+
var parameterExpressions = new List<ParameterExpression>
1747+
{
1748+
Expression.Parameter(intArray.GetType(), "intArray")
1749+
};
1750+
1751+
var expression = "string.Join(\",\", intArray)";
1752+
1753+
// Act
1754+
var lambdaExpression = DynamicExpressionParser.ParseLambda(parameterExpressions.ToArray(), null, expression);
1755+
var @delegate = lambdaExpression.Compile();
1756+
var result = (string)@delegate.DynamicInvoke(intArray);
1757+
1758+
// Assert
1759+
result.Should().Be("1,2,3");
1760+
}
1761+
16991762
public class DefaultDynamicLinqCustomTypeProviderForGenericExtensionMethod : DefaultDynamicLinqCustomTypeProvider
17001763
{
17011764
public override HashSet<Type> GetCustomTypes() => new HashSet<Type>(base.GetCustomTypes()) { typeof(Methods), typeof(MethodsItemExtension) };

0 commit comments

Comments
 (0)