Files
qbtmud/Lantean.QBTMudBlade/ExpressionModifier.cs
2024-06-03 08:04:09 +01:00

154 lines
5.7 KiB
C#

using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
namespace Lantean.QBTMudBlade
{
internal static class ExpressionModifier
{
internal static Expression<Func<T, bool>> Modify<T>(this Expression firstExpression, Expression secondExpression)
{
var bodyIdentifier = new ExpressionBodyIdentifier();
var body = bodyIdentifier.Identify(firstExpression);
var parameterIdentifier = new ExpressionParameterIdentifier();
var parameter = (ParameterExpression)parameterIdentifier.Identify(firstExpression);
var body2 = bodyIdentifier.Identify(secondExpression);
var parameter2 = (ParameterExpression)parameterIdentifier.Identify(secondExpression);
var treeModifier = new ExpressionReplacer(parameter2, body);
return Expression.Lambda<Func<T, bool>>(treeModifier.Visit(body2), parameter);
}
internal static Expression ReplaceBinary(this Expression exp, ExpressionType from, ExpressionType to)
{
var binaryReplacer = new BinaryReplacer(from, to);
return binaryReplacer.Visit(exp);
}
public static Expression<Func<T, bool>> GenerateBinary<T>(this Expression expression, ExpressionType binaryOperation, object? value)
{
var bodyIdentifier = new ExpressionBodyIdentifier();
var body = bodyIdentifier.Identify(expression);
var parameterIdentifier = new ExpressionParameterIdentifier();
var parameter = (ParameterExpression)parameterIdentifier.Identify(expression);
BinaryExpression? binaryExpression;
if (Nullable.GetUnderlyingType(body.Type) is not null)
{
// property type is nullable...
binaryExpression = Expression.MakeBinary(binaryOperation, body, Expression.Convert(Expression.Constant(value), body.Type));
}
else
{
if (value is null)
{
// We can short circuit here because the value to be compared is null and the property type is not nullable.
return x => true;
}
binaryExpression = Expression.MakeBinary(binaryOperation, body, Expression.Convert(Expression.Constant(value), body.Type));
}
return Expression.Lambda<Func<T, bool>>(binaryExpression, parameter);
}
public static Expression<Func<T, U>> ChangeExpressionReturnType<T, U>(this Expression expression)
{
var bodyIdentifier = new ExpressionBodyIdentifier();
var body = bodyIdentifier.Identify(expression);
var parameterIdentifier = new ExpressionParameterIdentifier();
var parameter = (ParameterExpression)parameterIdentifier.Identify(expression);
if (body.Type is U)
{
// Expression already has the right type.
return Expression.Lambda<Func<T, U>>(body, parameter);
}
// Change parameter.
var converted = Expression.Convert(body, typeof(U));
return Expression.Lambda<Func<T, U>>(converted, parameter);
}
public static (Expression<Func<T, object?>>, Type) CreatePropertySelector<T>(string propertyName)
{
var type = typeof(T);
var propertyInfo = type.GetProperty(propertyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (propertyInfo is null)
{
throw new InvalidOperationException($"Unable to match property {propertyName} for {type.Name}");
}
var parameterExpression = Expression.Parameter(type);
var propertyExpression = Expression.Property(parameterExpression, propertyInfo);
var convertExpression = Expression.Convert(propertyExpression, typeof(object));
return (Expression.Lambda<Func<T, object?>>(convertExpression, parameterExpression), propertyInfo.PropertyType);
}
}
internal class ExpressionReplacer : ExpressionVisitor
{
private readonly Expression _from;
private readonly Expression _to;
public ExpressionReplacer(Expression from, Expression to)
{
_from = from;
_to = to;
}
[return: NotNullIfNotNull(nameof(node))]
public override Expression? Visit(Expression? node)
{
if (node == _from) return _to;
return base.Visit(node);
}
}
internal class ExpressionBodyIdentifier : ExpressionVisitor
{
public Expression Identify(Expression node)
{
return base.Visit(node);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
return node.Body;
}
}
internal class ExpressionParameterIdentifier : ExpressionVisitor
{
public Expression Identify(Expression node)
{
return base.Visit(node);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
return node.Parameters[0];
}
}
internal class BinaryReplacer : ExpressionVisitor
{
private readonly ExpressionType _from;
private readonly ExpressionType _to;
public BinaryReplacer(ExpressionType from, ExpressionType to)
{
_from = from;
_to = to;
}
protected override Expression VisitBinary(BinaryExpression node)
{
if (node.NodeType == _from)
{
return Expression.MakeBinary(_to, node.Left, node.Right);
}
return base.VisitBinary(node);
}
}
}