diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 50ae95c6..9262ad49 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -2074,6 +2074,7 @@ private bool TryParseEnumerable(Expression instance, Type elementType, string me } args = ParseArgumentList(); + var copyArgs = args.ToArray(); // Revert the _it and _parent to the old values. _it = outerIt; @@ -2082,7 +2083,11 @@ private bool TryParseEnumerable(Expression instance, Type elementType, string me var theType = type ?? instance.Type; if (theType == typeof(string) && _methodFinder.ContainsMethod(theType, methodName, false, instance, ref args)) { - // In case the type is a string, and does contain the methodName (like "IndexOf"), then return false to indicate that the methodName is not an Enumerable method. + // In case the type is a string, and does contain the methodName (like "IndexOf") then: + // - restore the args + // - set the expression to null + // - return false to indicate that the methodName is not an Enumerable method + args = copyArgs; expression = null; return false; } @@ -2235,7 +2240,7 @@ private Expression[] ParseArgumentList() _textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); _textParser.NextToken(); - var args = _textParser.CurrentToken.Id != TokenId.CloseParen ? ParseArguments() : new Expression[0]; + var args = _textParser.CurrentToken.Id != TokenId.CloseParen ? ParseArguments() : []; _textParser.ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); _textParser.NextToken(); diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs index 8d151a76..58437110 100644 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs @@ -210,19 +210,19 @@ public int FindBestMethodBasedOnArguments(IEnumerable methods, ref E public int FindIndexer(Type type, Expression[] args, out MethodBase? method) { - foreach (Type t in SelfAndBaseTypes(type)) + foreach (var t in SelfAndBaseTypes(type)) { - MemberInfo[] members = t.GetDefaultMembers(); + var members = t.GetDefaultMembers(); if (members.Length != 0) { IEnumerable methods = members.OfType(). #if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD) - Select(p => (MethodBase)p.GetGetMethod()). - Where(m => m != null); + Select(p => (MethodBase?)p.GetGetMethod()). + OfType(); #else Select(p => (MethodBase)p.GetMethod); #endif - int count = FindBestMethodBasedOnArguments(methods, ref args, out method); + var count = FindBestMethodBasedOnArguments(methods, ref args, out method); if (count != 0) { return count; @@ -289,7 +289,7 @@ private bool IsApplicable(MethodData method, Expression[] args) if (methodParameter.IsOut && args[i] is ParameterExpression parameterExpression) { #if NET35 - return false; + return false; #else if (!parameterExpression.IsByRef) { @@ -318,33 +318,31 @@ private bool IsApplicable(MethodData method, Expression[] args) private static bool FirstIsBetterThanSecond(Expression[] args, MethodData first, MethodData second) { - // If args count is 0 -> parameterless method is better than method method with parameters + // If args count is 0 -> parameterless method is better than a method with parameters if (args.Length == 0) { return first.Parameters.Length == 0 && second.Parameters.Length != 0; } - bool better = false; - for (int i = 0; i < args.Length; i++) + var better = false; + for (var i = 0; i < args.Length; i++) { - CompareConversionType result = CompareConversions(args[i].Type, first.Parameters[i].ParameterType, second.Parameters[i].ParameterType); - - // If second is better, return false - if (result == CompareConversionType.Second) - { - return false; - } - - // If first is better, return true - if (result == CompareConversionType.First) - { - return true; - } + var result = CompareConversions(args[i].Type, first.Parameters[i].ParameterType, second.Parameters[i].ParameterType); - // If both are same, just set better to true and continue - if (result == CompareConversionType.Both) + switch (result) { - better = true; + // If second is better, return false + case CompareConversionType.Second: + return false; + + // If first is better, return true + case CompareConversionType.First: + return true; + + // If both are same, just set better to true and continue + case CompareConversionType.Both: + better = true; + break; } } @@ -369,8 +367,8 @@ private static CompareConversionType CompareConversions(Type source, Type first, return CompareConversionType.Second; } - bool firstIsCompatibleWithSecond = TypeHelper.IsCompatibleWith(first, second); - bool secondIsCompatibleWithFirst = TypeHelper.IsCompatibleWith(second, first); + var firstIsCompatibleWithSecond = TypeHelper.IsCompatibleWith(first, second); + var secondIsCompatibleWithFirst = TypeHelper.IsCompatibleWith(second, first); if (firstIsCompatibleWithSecond && !secondIsCompatibleWithFirst) { diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/DynamicLinqTypeTest.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/DynamicLinqTypeTest.cs index bcbb435c..60f9f59f 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/DynamicLinqTypeTest.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/DynamicLinqTypeTest.cs @@ -28,19 +28,29 @@ public static string[] ConvertToArray(int a, params string[] values) return values.ToArray(); } - public static int IncrementMe(this int values) + public static int IncrementMe(this int value) { - return values + 1; + return value + 1; } - public static int IncrementMe(this int values, int y) + public static int IncrementMe(this int value, int y) { - return values + y; + return value + y; } - public static int IncrementMeAlso(this int values) + public static int IncrementMeAlso(this int value) { - return values + 1; + return value + 1; + } + + public static string EmptyIfNull(this string? s) + { + return s ?? string.Empty; + } + + public static string DefaultIfNull(this string? s, string defaultValue) + { + return s ?? defaultValue; } } @@ -161,7 +171,25 @@ public void ParamArray_Array() } [Fact] - public void ExtensionMethod_NoParameter() + public void ExtensionMethod_OnString_NoParameter() + { + var list = new[] { "a", "", null }.AsQueryable(); + var result = list.Select("it.EmptyIfNull()").ToDynamicList(); + + result.Should().Equal("a", "", ""); + } + + [Fact] + public void ExtensionMethod_OnString_OneParameter() + { + var list = new[] { "a", "", null }.AsQueryable(); + var result = list.Select("it.DefaultIfNull(\"x\")").ToDynamicList(); + + result.Should().Equal("a", "", "x"); + } + + [Fact] + public void ExtensionMethod_OnInt_NoParameter() { var list = new[] { new EntityValue { ValueInt = 1 }, new EntityValue { ValueInt = 2 } }.AsQueryable(); var result = list.Select("ValueInt.IncrementMe()").ToDynamicList(); @@ -172,7 +200,7 @@ public void ExtensionMethod_NoParameter() } [Fact] - public void ExtensionMethod_SingleParameter() + public void ExtensionMethod_OnInt_SingleParameter() { var list = new[] { new EntityValue { ValueInt = 1 }, new EntityValue { ValueInt = 2 } }.AsQueryable(); var result = list.Select("ValueInt.IncrementMe(5)").ToDynamicList();