Skip to content

Instantly share code, notes, and snippets.

@forgetaboutit
Last active April 25, 2018 21:45
Show Gist options
  • Save forgetaboutit/469aeadd5ba69f6e10026a08f4049e58 to your computer and use it in GitHub Desktop.
Save forgetaboutit/469aeadd5ba69f6e10026a08f4049e58 to your computer and use it in GitHub Desktop.
LINQKit.Core vs. Custom Splice
// Code from the post: https://balefrost.github.io/expression_splicing.html
public static class ExpressionSplicer
{
public static TResult Inline<T1, TResult>(this Expression<Func<T1, TResult>> substitutionExpr, T1 value1)
{
throw new Exception(
"This method isn't meant to be called; it's meant to appear inside a template expression passed to Splice");
}
}
class ParameterReplacer : System.Linq.Expressions.ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, Expression> _formalParameterSubstitutions;
public ParameterReplacer(Dictionary<ParameterExpression, Expression> formalParameterSubstitutions)
{
_formalParameterSubstitutions = formalParameterSubstitutions;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (_formalParameterSubstitutions.TryGetValue(node, out var actualValue))
{
return actualValue;
}
return base.VisitParameter(node);
}
}
public static Expression<Func<T1, TResult>> Splice<T1, TResult>(
Expression<Func<T1, TResult>> templateExpr)
{
return new SpliceRewriter().VisitAndConvert(templateExpr, "Splice");
}
class SpliceRewriter : System.Linq.Expressions.ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType == typeof(ExpressionSplicer) && node.Method.Name == "Inline")
{
// `node.Arguments[0]` (i.e. the `this` parameter to `Inline` will be the substitution expression
// and the remainder of `node.Arguments` will be the arguments we want to provide to that
// substitution expression.
LambdaExpression expressionToInsert =
Expression.Lambda<Func<LambdaExpression>>(node.Arguments[0]).Compile()();
var substitutionArguments = node.Arguments.Skip(1);
var parameterArgumentPairs = expressionToInsert.Parameters
.Zip(substitutionArguments, (parameter, argument) => new { parameter, argument });
var substitutions = parameterArgumentPairs.ToDictionary(x => x.parameter, x => x.argument);
var parameterReplacer = new ParameterReplacer(substitutions);
return parameterReplacer.Visit(expressionToInsert.Body);
}
return base.VisitMethodCall(node);
}
}
Expression<Func<T, bool>> MakeContainsExpression<T>(
IQueryable<int> intermediateResults,
Expression<Func<T, int>> substitutionExpr)
{
return Splice((T x) => intermediateResults.Contains(substitutionExpr.Inline(x)));
}
// End of code from the post
// Expand-Equivalent:
// Please kindly include LinqKit.Core in your LINQPad-Script or paste
// the code of its ExpressionVisitor from Github here
Expression<Func<T, bool>> MakeContainsExpressionExpand<T>(
IQueryable<int> intermediateResults,
Expression<Func<T, int>> substitutionExpr)
{
Expression<Func<T, bool>> expr = (T x) => intermediateResults.Contains(substitutionExpr.Invoke(x));
return expr.Expand();
}
void Main()
{
var queryable =
Enumerable.Range(1, 10).
AsQueryable();
var containsExprSplice =
MakeContainsExpression<int>(
queryable,
i => i * 5);
var containsExprExpand =
MakeContainsExpressionExpand<int>(
queryable,
i => i * 5);
// In my LINQPad output, those result in the same expression
containsExprSplice.Dump();
containsExprExpand.Dump();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment