Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/LinqKit.Core/ExpressionExpander.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,28 @@ protected bool GetExpandLambda(MemberInfo memberInfo, out LambdaExpression expan
return false;
}

protected override Expression VisitUnary(UnaryExpression node)
{
// When Invoke is called via a method group (e.g. x => x.Any(filter.Invoke)), it will be wrapped in Convert(CreateDelegate(...)).
if (
node.NodeType == ExpressionType.Convert
&& node.Operand is MethodCallExpression methodCall
&& methodCall.Method.Name == "CreateDelegate"
&& methodCall.Method.DeclaringType == typeof(MethodInfo)
&& methodCall.Object is ConstantExpression methodInfoConst
&& methodCall.Arguments.Count == 2
&& methodInfoConst.Value is MethodInfo methodInfo
&& methodInfo.Name == nameof(ExtensionsCore.Invoke)
&& methodInfo.DeclaringType == typeof(ExtensionsCore))
{
Comment on lines +114 to +124

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just what I've observed and is clearly very specific, and does rely on implementation details.

At the same time, the test passes in all tested scenarios, and does address a real problem 🤷

screenshot of all tests passing

var target = methodCall.Arguments[1];

return Visit(target);
}

return base.VisitUnary(node);
}

protected override Expression VisitMethodCall(MethodCallExpression m)
{
if (m.Method.Name == nameof(ExtensionsCore.Invoke) && m.Method.DeclaringType == typeof(ExtensionsCore))
Expand Down
9 changes: 9 additions & 0 deletions tests/LinqKit.Tests.Net452/ExpressionCombinerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,15 @@ public void ExpandProcessesArguments()
nestedExpression.Expand().Expand().ToString());
}

[Fact]
public void ExpandProcessesInvokeAsMethodGroup()
{
Expression<Func<int, bool>> filter = p => p > 1000;
Expression<Func<int[], bool>> anyMeetsFilter = x => x.Any(filter.Invoke);

Assert.Equal("x => x.Any(p => (p > 1000))", anyMeetsFilter.Expand().ToString());

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without the new code, anyMeetsFilter.Expand().ToString() produces this:

x => x.Any(Convert(Boolean Invoke[Int32,Boolean](System.Linq.Expressions.Expression`1[System.Func`2[System.Int32,System.Boolean]], Int32).CreateDelegate(System.Func`2[System.Int32,System.Boolean], p => (p > 1000))))

}

private string ConstExpressionString<TResult>(Expression<Func<TResult>> expr)
{
return expr.ToString().Substring(6);
Expand Down