Skip to content

Commit 245b098

Browse files
authored
Add support for casting a string to another type (e.g. int) for Linq2Objects (#686)
* . * . * c * . * tst
1 parent 1c0fd29 commit 245b098

File tree

9 files changed

+186
-141
lines changed

9 files changed

+186
-141
lines changed

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

+36-34
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,10 @@ public Expression GenerateGreaterThanEqual(Expression left, Expression right)
176176

177177
if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum)
178178
{
179-
return Expression.GreaterThanOrEqual(left.Type.GetTypeInfo().IsEnum ? Expression.Convert(left, Enum.GetUnderlyingType(left.Type)) : left,
180-
right.Type.GetTypeInfo().IsEnum ? Expression.Convert(right, Enum.GetUnderlyingType(right.Type)) : right);
179+
return Expression.GreaterThanOrEqual(
180+
left.Type.GetTypeInfo().IsEnum ? Expression.Convert(left, Enum.GetUnderlyingType(left.Type)) : left,
181+
right.Type.GetTypeInfo().IsEnum ? Expression.Convert(right, Enum.GetUnderlyingType(right.Type)) : right
182+
);
181183
}
182184

183185
WrapConstantExpressions(ref left, ref right);
@@ -194,8 +196,10 @@ public Expression GenerateLessThan(Expression left, Expression right)
194196

195197
if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum)
196198
{
197-
return Expression.LessThan(left.Type.GetTypeInfo().IsEnum ? Expression.Convert(left, Enum.GetUnderlyingType(left.Type)) : left,
198-
right.Type.GetTypeInfo().IsEnum ? Expression.Convert(right, Enum.GetUnderlyingType(right.Type)) : right);
199+
return Expression.LessThan(
200+
left.Type.GetTypeInfo().IsEnum ? Expression.Convert(left, Enum.GetUnderlyingType(left.Type)) : left,
201+
right.Type.GetTypeInfo().IsEnum ? Expression.Convert(right, Enum.GetUnderlyingType(right.Type)) : right
202+
);
199203
}
200204

201205
WrapConstantExpressions(ref left, ref right);
@@ -212,8 +216,10 @@ public Expression GenerateLessThanEqual(Expression left, Expression right)
212216

213217
if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum)
214218
{
215-
return Expression.LessThanOrEqual(left.Type.GetTypeInfo().IsEnum ? Expression.Convert(left, Enum.GetUnderlyingType(left.Type)) : left,
216-
right.Type.GetTypeInfo().IsEnum ? Expression.Convert(right, Enum.GetUnderlyingType(right.Type)) : right);
219+
return Expression.LessThanOrEqual(
220+
left.Type.GetTypeInfo().IsEnum ? Expression.Convert(left, Enum.GetUnderlyingType(left.Type)) : left,
221+
right.Type.GetTypeInfo().IsEnum ? Expression.Convert(right, Enum.GetUnderlyingType(right.Type)) : right
222+
);
217223
}
218224

219225
WrapConstantExpressions(ref left, ref right);
@@ -264,22 +270,22 @@ public void OptimizeForEqualityIfPossible(ref Expression left, ref Expression ri
264270
return Expression.Constant(guid, typeof(Guid));
265271
}
266272
#else
267-
try
268-
{
269-
return Expression.Constant(new Guid(text));
270-
}
271-
catch
272-
{
273-
// Doing it in old fashion way when no TryParse interface was provided by .NET
274-
}
273+
try
274+
{
275+
return Expression.Constant(new Guid(text));
276+
}
277+
catch
278+
{
279+
// Doing it in old fashion way when no TryParse interface was provided by .NET
280+
}
275281
#endif
276282
return null;
277283
}
278284

279285
public bool MemberExpressionIsDynamic(Expression expression)
280286
{
281287
#if NET35
282-
return false;
288+
return false;
283289
#else
284290
return expression is MemberExpression memberExpression && memberExpression.Member.GetCustomAttribute<DynamicAttribute>() != null;
285291
#endif
@@ -290,21 +296,10 @@ public Expression ConvertToExpandoObjectAndCreateDynamicExpression(Expression ex
290296
#if !NET35 && !UAP10_0 && !NETSTANDARD1_3
291297
return Expression.Dynamic(new DynamicGetMemberBinder(propertyName, _parsingConfig), type, expression);
292298
#else
293-
throw new NotSupportedException(Res.DynamicExpandoObjectIsNotSupported);
299+
throw new NotSupportedException(Res.DynamicExpandoObjectIsNotSupported);
294300
#endif
295301
}
296302

297-
private MethodInfo GetStaticMethod(string methodName, Expression left, Expression right)
298-
{
299-
var methodInfo = left.Type.GetMethod(methodName, new[] { left.Type, right.Type });
300-
if (methodInfo == null)
301-
{
302-
methodInfo = right.Type.GetMethod(methodName, new[] { left.Type, right.Type })!;
303-
}
304-
305-
return methodInfo;
306-
}
307-
308303
private Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right)
309304
{
310305
return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right });
@@ -350,17 +345,13 @@ public bool TryGenerateAndAlsoNotNullExpression(Expression sourceExpression, boo
350345

351346
public bool ExpressionQualifiesForNullPropagation(Expression? expression)
352347
{
353-
return
354-
expression is MemberExpression ||
355-
expression is ParameterExpression ||
356-
expression is MethodCallExpression ||
357-
expression is UnaryExpression;
348+
return expression is MemberExpression or ParameterExpression or MethodCallExpression or UnaryExpression;
358349
}
359350

360351
public Expression GenerateDefaultExpression(Type type)
361352
{
362353
#if NET35
363-
return Expression.Constant(Activator.CreateInstance(type));
354+
return Expression.Constant(Activator.CreateInstance(type));
364355
#else
365356
return Expression.Default(type);
366357
#endif
@@ -391,7 +382,7 @@ public Expression GenerateDefaultExpression(Type type)
391382

392383
private List<Expression> CollectExpressions(bool addSelf, Expression sourceExpression)
393384
{
394-
Expression? expression = GetMemberExpression(sourceExpression);
385+
var expression = GetMemberExpression(sourceExpression);
395386

396387
var list = new List<Expression>();
397388

@@ -443,6 +434,17 @@ private List<Expression> CollectExpressions(bool addSelf, Expression sourceExpre
443434
return list;
444435
}
445436

437+
private static MethodInfo GetStaticMethod(string methodName, Expression left, Expression right)
438+
{
439+
var methodInfo = left.Type.GetMethod(methodName, new[] { left.Type, right.Type });
440+
if (methodInfo == null)
441+
{
442+
methodInfo = right.Type.GetMethod(methodName, new[] { left.Type, right.Type })!;
443+
}
444+
445+
return methodInfo;
446+
}
447+
446448
private static Expression? GetMethodCallExpression(MethodCallExpression methodCallExpression)
447449
{
448450
if (methodCallExpression.Object != null)

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

+49-36
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections;
22
using System.Collections.Generic;
33
using System.ComponentModel;
4+
using System.Diagnostics.CodeAnalysis;
45
using System.Globalization;
56
using System.Linq.Dynamic.Core.Exceptions;
67
using System.Linq.Dynamic.Core.Parser.SupportedMethods;
@@ -19,10 +20,10 @@ namespace System.Linq.Dynamic.Core.Parser;
1920
/// </summary>
2021
public class ExpressionParser
2122
{
22-
private static readonly string methodOrderBy = nameof(Queryable.OrderBy);
23-
private static readonly string methodOrderByDescending = nameof(Queryable.OrderByDescending);
24-
private static readonly string methodThenBy = nameof(Queryable.ThenBy);
25-
private static readonly string methodThenByDescending = nameof(Queryable.ThenByDescending);
23+
private const string MethodOrderBy = nameof(Queryable.OrderBy);
24+
private const string MethodOrderByDescending = nameof(Queryable.OrderByDescending);
25+
private const string MethodThenBy = nameof(Queryable.ThenBy);
26+
private const string MethodThenByDescending = nameof(Queryable.ThenByDescending);
2627

2728
private readonly ParsingConfig _parsingConfig;
2829
private readonly MethodFinder _methodFinder;
@@ -51,7 +52,7 @@ public class ExpressionParser
5152
/// There was a problem when an expression contained multiple lambdas where
5253
/// the ItName was not cleared and freed for the next lambda. This variable
5354
/// stores the ItName of the last parsed lambda.
54-
/// Not used internally by ExpressionParser, but used to preserve compatiblity of parsingConfig.RenameParameterExpression
55+
/// Not used internally by ExpressionParser, but used to preserve compatibility of parsingConfig.RenameParameterExpression
5556
/// which was designed to only work with mono-lambda expressions.
5657
/// </summary>
5758
public string LastLambdaItName { get; private set; } = KeywordsHelper.KEYWORD_IT;
@@ -93,7 +94,7 @@ private void ProcessParameters(ParameterExpression[] parameters)
9394
{
9495
foreach (ParameterExpression pe in parameters.Where(p => !string.IsNullOrEmpty(p.Name)))
9596
{
96-
AddSymbol(pe.Name, pe);
97+
AddSymbol(pe.Name!, pe);
9798
}
9899

99100
// If there is only 1 ParameterExpression, do also allow access using 'it'
@@ -185,11 +186,11 @@ internal IList<DynamicOrdering> ParseOrdering(bool forceThenBy = false)
185186
string methodName;
186187
if (forceThenBy || orderings.Count > 0)
187188
{
188-
methodName = ascending ? methodThenBy : methodThenByDescending;
189+
methodName = ascending ? MethodThenBy : MethodThenByDescending;
189190
}
190191
else
191192
{
192-
methodName = ascending ? methodOrderBy : methodOrderByDescending;
193+
methodName = ascending ? MethodOrderBy : MethodOrderByDescending;
193194
}
194195

195196
orderings.Add(new DynamicOrdering { Selector = expr, Ascending = ascending, MethodName = methodName });
@@ -404,11 +405,11 @@ private Expression ParseLogicalAndOrOperator()
404405
switch (op.Id)
405406
{
406407
case TokenId.Ampersand:
407-
if (left.Type == typeof(string) && left.NodeType == ExpressionType.Constant && int.TryParse((string)((ConstantExpression)left).Value, out var parseValue) && TypeHelper.IsNumericType(right.Type))
408+
if (left.Type == typeof(string) && left.NodeType == ExpressionType.Constant && int.TryParse((string?)((ConstantExpression)left).Value, out var parseValue) && TypeHelper.IsNumericType(right.Type))
408409
{
409410
left = Expression.Constant(parseValue);
410411
}
411-
else if (right.Type == typeof(string) && right.NodeType == ExpressionType.Constant && int.TryParse((string)((ConstantExpression)right).Value, out parseValue) && TypeHelper.IsNumericType(left.Type))
412+
else if (right.Type == typeof(string) && right.NodeType == ExpressionType.Constant && int.TryParse((string?)((ConstantExpression)right).Value, out parseValue) && TypeHelper.IsNumericType(left.Type))
412413
{
413414
right = Expression.Constant(parseValue);
414415
}
@@ -1172,7 +1173,14 @@ private Expression ParseFunctionCast()
11721173
it = args[0];
11731174
}
11741175

1175-
return Expression.ConvertChecked(it, ResolveTypeFromArgumentExpression(functionName, typeArgument, args.Length));
1176+
var destinationType = ResolveTypeFromArgumentExpression(functionName, typeArgument, args.Length);
1177+
1178+
if (TryGenerateConversion(it, destinationType, out var conversionExpression))
1179+
{
1180+
return conversionExpression;
1181+
}
1182+
1183+
return Expression.ConvertChecked(it, destinationType);
11761184
}
11771185

11781186
private Expression GenerateConditional(Expression test, Expression expressionIfTrue, Expression expressionIfFalse, bool nullPropagating, int errorPos)
@@ -1360,9 +1368,9 @@ private Expression ParseNew()
13601368
&& methodCallExpression.Arguments.Count == 1
13611369
&& methodCallExpression.Arguments[0] is ConstantExpression methodCallExpressionArgument
13621370
&& methodCallExpressionArgument.Type == typeof(string)
1363-
&& properties.All(x => x.Name != (string)methodCallExpressionArgument.Value))
1371+
&& properties.All(x => x.Name != (string?)methodCallExpressionArgument.Value))
13641372
{
1365-
propName = (string)methodCallExpressionArgument.Value;
1373+
propName = (string?)methodCallExpressionArgument.Value;
13661374
}
13671375
else
13681376
{
@@ -1442,7 +1450,7 @@ private Expression CreateNewExpression(List<DynamicProperty> properties, List<Ex
14421450
// Create an expression tree that represents creating and initializing a one-dimensional array of type KeyValuePair<string, object>.
14431451
NewArrayExpression newArrayExpression = Expression.NewArrayInit(typeof(KeyValuePair<string, object>), arrayIndexParams);
14441452

1445-
// Get the "public DynamicClass(KeyValuePair<string, object>[] propertylist)" constructor
1453+
// Get the "public DynamicClass(KeyValuePair<string, object>[])" constructor
14461454
ConstructorInfo constructor = type.GetTypeInfo().DeclaredConstructors.First();
14471455

14481456
return Expression.New(constructor, newArrayExpression);
@@ -1621,7 +1629,7 @@ private Expression ParseTypeAccess(Type type, bool getNext)
16211629
case 0:
16221630
if (args.Length == 1 && TryGenerateConversion(args[0], type, out generatedExpression))
16231631
{
1624-
return generatedExpression!;
1632+
return generatedExpression;
16251633
}
16261634

16271635
throw ParseError(errorPos, Res.NoMatchingConstructor, TypeHelper.GetTypeName(type));
@@ -1640,56 +1648,61 @@ private Expression ParseTypeAccess(Type type, bool getNext)
16401648
return ParseMemberAccess(type, null);
16411649
}
16421650

1643-
private bool TryGenerateConversion(Expression sourceExpression, Type type, out Expression? expression)
1651+
private bool TryGenerateConversion(Expression sourceExpression, Type destinationType, [NotNullWhen(true) ]out Expression? expression)
16441652
{
16451653
Type exprType = sourceExpression.Type;
1646-
if (exprType == type)
1654+
if (exprType == destinationType)
16471655
{
16481656
expression = sourceExpression;
16491657
return true;
16501658
}
16511659

1652-
if (exprType.GetTypeInfo().IsValueType && type.GetTypeInfo().IsValueType)
1660+
if (exprType.GetTypeInfo().IsValueType && destinationType.GetTypeInfo().IsValueType)
16531661
{
1654-
if ((TypeHelper.IsNullableType(exprType) || TypeHelper.IsNullableType(type)) && TypeHelper.GetNonNullableType(exprType) == TypeHelper.GetNonNullableType(type))
1662+
if ((TypeHelper.IsNullableType(exprType) || TypeHelper.IsNullableType(destinationType)) && TypeHelper.GetNonNullableType(exprType) == TypeHelper.GetNonNullableType(destinationType))
16551663
{
1656-
expression = Expression.Convert(sourceExpression, type);
1664+
expression = Expression.Convert(sourceExpression, destinationType);
16571665
return true;
16581666
}
16591667

1660-
if ((TypeHelper.IsNumericType(exprType) || TypeHelper.IsEnumType(exprType)) && TypeHelper.IsNumericType(type) || TypeHelper.IsEnumType(type))
1668+
if ((TypeHelper.IsNumericType(exprType) || TypeHelper.IsEnumType(exprType)) && TypeHelper.IsNumericType(destinationType) || TypeHelper.IsEnumType(destinationType))
16611669
{
1662-
expression = Expression.ConvertChecked(sourceExpression, type);
1670+
expression = Expression.ConvertChecked(sourceExpression, destinationType);
16631671
return true;
16641672
}
16651673
}
16661674

1667-
if (exprType.IsAssignableFrom(type) || type.IsAssignableFrom(exprType) || exprType.GetTypeInfo().IsInterface || type.GetTypeInfo().IsInterface)
1675+
if (exprType.IsAssignableFrom(destinationType) || destinationType.IsAssignableFrom(exprType) || exprType.GetTypeInfo().IsInterface || destinationType.GetTypeInfo().IsInterface)
16681676
{
1669-
expression = Expression.Convert(sourceExpression, type);
1677+
expression = Expression.Convert(sourceExpression, destinationType);
16701678
return true;
16711679
}
16721680

16731681
// Try to Parse the string rather than just generate the convert statement
1674-
if (sourceExpression.NodeType == ExpressionType.Constant && exprType == typeof(string))
1682+
if (sourceExpression is ConstantExpression { Value: string constantStringValue })
16751683
{
1676-
string text = (string)((ConstantExpression)sourceExpression).Value;
1677-
1678-
var typeConvertor = _typeConverterFactory.GetConverter(type);
1679-
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
1680-
if (typeConvertor != null && typeConvertor.CanConvertFrom(typeof(string)))
1684+
var typeConvertor = _typeConverterFactory.GetConverter(destinationType);
1685+
if (typeConvertor.CanConvertFrom(typeof(string)))
16811686
{
1682-
var value = typeConvertor.ConvertFromInvariantString(text);
1683-
expression = Expression.Constant(value, type);
1687+
var value = typeConvertor.ConvertFromInvariantString(constantStringValue);
1688+
expression = Expression.Constant(value, destinationType);
16841689
return true;
16851690
}
16861691
}
16871692

16881693
// Check if there are any explicit conversion operators on the source type which fit the requirement (cast to the return type).
1689-
bool explicitOperatorAvailable = exprType.GetTypeInfo().GetDeclaredMethods("op_Explicit").Any(m => m.ReturnType == type);
1694+
bool explicitOperatorAvailable = exprType.GetTypeInfo().GetDeclaredMethods("op_Explicit").Any(m => m.ReturnType == destinationType);
16901695
if (explicitOperatorAvailable)
16911696
{
1692-
expression = Expression.Convert(sourceExpression, type);
1697+
expression = Expression.Convert(sourceExpression, destinationType);
1698+
return true;
1699+
}
1700+
1701+
// Try to find a destinationType.Parse(...) method for the specific sourceExpression Type.
1702+
var parseMethod = destinationType.GetMethod("Parse", new Type[] { sourceExpression.Type });
1703+
if (parseMethod != null)
1704+
{
1705+
expression = Expression.Call(parseMethod, sourceExpression);
16931706
return true;
16941707
}
16951708

@@ -1788,7 +1801,7 @@ private Expression ParseMemberAccess(Type? type, Expression? expression)
17881801
if (type == typeof(object))
17891802
{
17901803
// The member is a dynamic or ExpandoObject, so convert this
1791-
return _expressionHelper.ConvertToExpandoObjectAndCreateDynamicExpression(expression, type, id);
1804+
return _expressionHelper.ConvertToExpandoObjectAndCreateDynamicExpression(expression!, type, id);
17921805
}
17931806
#endif
17941807
// Parse as Lambda
@@ -1798,7 +1811,7 @@ private Expression ParseMemberAccess(Type? type, Expression? expression)
17981811
}
17991812

18001813
// This could be enum like "A.B.C.MyEnum.Value1" or "A.B.C+MyEnum.Value1"
1801-
if (_textParser.CurrentToken.Id == TokenId.Dot || _textParser.CurrentToken.Id == TokenId.Plus)
1814+
if (_textParser.CurrentToken.Id is TokenId.Dot or TokenId.Plus)
18021815
{
18031816
return ParseAsEnum(id);
18041817
}

src/System.Linq.Dynamic.Core/TypeConverters/TypeConverterFactory.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ public TypeConverter GetConverter(Type type)
3838
}
3939

4040
#if !SILVERLIGHT
41-
var c = TypeDescriptor.GetConverter(type);
42-
return c;
41+
return TypeDescriptor.GetConverter(type);
4342
#else
4443
var attributes = type.GetCustomAttributes(typeof(TypeConverterAttribute), false);
4544

@@ -52,7 +51,7 @@ public TypeConverter GetConverter(Type type)
5251
if (converterType == null)
5352
return new TypeConverter();
5453

55-
return Activator.CreateInstance(converterType) as TypeConverter;
54+
return (TypeConverter) Activator.CreateInstance(converterType);
5655
#endif
5756
}
5857
}

src/System.Linq.Dynamic.Core/Util/ParameterExpressionRenamer.cs

+3-6
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,8 @@ public ParameterExpressionRenamer(string newName) : this(string.Empty, newName)
2929
/// <param name="newName">The new name.</param>
3030
public ParameterExpressionRenamer(string oldName, string newName)
3131
{
32-
Check.NotNull(oldName);
33-
Check.NotEmpty(newName);
34-
35-
_oldName = oldName;
36-
_newName = newName;
32+
_oldName = Check.NotNull(oldName);
33+
_newName = Check.NotEmpty(newName);
3734
}
3835

3936
/// <summary>
@@ -67,4 +64,4 @@ protected override Expression VisitParameter(ParameterExpression node)
6764

6865
return node;
6966
}
70-
}
67+
}

0 commit comments

Comments
 (0)