Convert LINQ expression to readable string

Date: 2024-12-09
using System;
using System.Linq.Expressions;
using System.Text;

public static class ExpressionExtensions
{
    public static string ToReadableString(this Expression expression)
    {
        if (expression == null) return string.Empty;
        var builder = new StringBuilder();
        BuildReadableString(expression, builder);
        return builder.ToString();
    }

    private static void BuildReadableString(Expression expression, StringBuilder builder)
    {
        switch (expression)
        {
            case LambdaExpression lambda:
                BuildReadableString(lambda.Body, builder);
                break;

            case BinaryExpression binary:
                builder.Append("(");
                BuildReadableString(binary.Left, builder);
                builder.Append($" {GetOperator(binary.NodeType)} ");
                BuildReadableString(binary.Right, builder);
                builder.Append(")");
                break;

            case MemberExpression member:
                builder.Append(member.Member.Name);
                break;

            case ParameterExpression parameter:
                builder.Append(parameter.Name);
                break;

            case ConstantExpression constant:
                builder.Append(constant.Value);
                break;

            case MethodCallExpression methodCall:
                builder.Append(methodCall.Method.Name);
                builder.Append("(");
                for (int i = 0; i < methodCall.Arguments.Count; i++)
                {
                    BuildReadableString(methodCall.Arguments[i], builder);
                    if (i < methodCall.Arguments.Count - 1)
                        builder.Append(", ");
                }
                builder.Append(")");
                break;

            default:
                builder.Append(expression.ToString());
                break;
        }
    }

    private static string GetOperator(ExpressionType nodeType) =>
        nodeType switch
        {
            ExpressionType.Add => "+",
            ExpressionType.Subtract => "-",
            ExpressionType.Multiply => "*",
            ExpressionType.Divide => "/",
            ExpressionType.AndAlso => "&&",
            ExpressionType.OrElse => "||",
            ExpressionType.Equal => "==",
            ExpressionType.NotEqual => "!=",
            ExpressionType.LessThan => "<",
            ExpressionType.LessThanOrEqual => "<=",
            ExpressionType.GreaterThan => ">",
            ExpressionType.GreaterThanOrEqual => ">=",
            _ => nodeType.ToString()
        };
}

With expanded methods:

using System;
using System.Linq.Expressions;
using System.Text;

public static class ExpressionExtensions
{
    public static string ToReadableString(this Expression expression)
    {
        if (expression == null) return string.Empty;
        var builder = new StringBuilder();
        BuildReadableString(expression, builder);
        return builder.ToString();
    }

    private static void BuildReadableString(Expression expression, StringBuilder builder)
    {
        switch (expression)
        {
            case LambdaExpression lambda:
                BuildReadableString(lambda.Body, builder);
                break;

            case BinaryExpression binary:
                builder.Append("(");
                BuildReadableString(binary.Left, builder);
                builder.Append($" {GetOperator(binary.NodeType)} ");
                BuildReadableString(binary.Right, builder);
                builder.Append(")");
                break;

            case MemberExpression member:
                builder.Append(member.Member.Name);
                break;

            case ParameterExpression parameter:
                builder.Append(parameter.Name);
                break;

            case ConstantExpression constant:
                builder.Append(constant.Value);
                break;

            case MethodCallExpression methodCall:
                if (methodCall.Method.DeclaringType == typeof(Enumerable) ||
                    methodCall.Method.DeclaringType == typeof(Queryable))
                {
                    // LINQ-methoden zoals Where, Any, etc.
                    TranslateLinqMethod(methodCall, builder);
                }
                else
                {
                    // Andere method calls
                    builder.Append(methodCall.Method.Name);
                    builder.Append("(");
                    for (int i = 0; i < methodCall.Arguments.Count; i++)
                    {
                        BuildReadableString(methodCall.Arguments[i], builder);
                        if (i < methodCall.Arguments.Count - 1)
                            builder.Append(", ");
                    }
                    builder.Append(")");
                }
                break;

            default:
                builder.Append(expression.ToString());
                break;
        }
    }

    private static void TranslateLinqMethod(MethodCallExpression methodCall, StringBuilder builder)
    {
        switch (methodCall.Method.Name)
        {
            case "Where":
                builder.Append("Filter items where ");
                var lambda = (LambdaExpression)((UnaryExpression)methodCall.Arguments[1]).Operand;
                BuildReadableString(lambda.Body, builder);
                break;

            case "Any":
                builder.Append("Exists where ");
                var anyLambda = (LambdaExpression)((UnaryExpression)methodCall.Arguments[1]).Operand;
                BuildReadableString(anyLambda.Body, builder);
                break;

            case "All":
                builder.Append("All items satisfy ");
                var allLambda = (LambdaExpression)((UnaryExpression)methodCall.Arguments[1]).Operand;
                BuildReadableString(allLambda.Body, builder);
                break;

            case "Select":
                builder.Append("Select ");
                var selectLambda = (LambdaExpression)((UnaryExpression)methodCall.Arguments[1]).Operand;
                BuildReadableString(selectLambda.Body, builder);
                break;

            case "OrderBy":
                builder.Append("Order by ");
                var orderByLambda = (LambdaExpression)((UnaryExpression)methodCall.Arguments[1]).Operand;
                BuildReadableString(orderByLambda.Body, builder);
                break;

            case "OrderByDescending":
                builder.Append("Order by descending ");
                var orderByDescLambda = (LambdaExpression)((UnaryExpression)methodCall.Arguments[1]).Operand;
                BuildReadableString(orderByDescLambda.Body, builder);
                break;

            default:
                builder.Append(methodCall.Method.Name);
                builder.Append("(");
                for (int i = 0; i < methodCall.Arguments.Count; i++)
                {
                    BuildReadableString(methodCall.Arguments[i], builder);
                    if (i < methodCall.Arguments.Count - 1)
                        builder.Append(", ");
                }
                builder.Append(")");
                break;
        }
    }

    private static string GetOperator(ExpressionType nodeType) =>
        nodeType switch
        {
            ExpressionType.Add => "+",
            ExpressionType.Subtract => "-",
            ExpressionType.Multiply => "*",
            ExpressionType.Divide => "/",
            ExpressionType.AndAlso => "&&",
            ExpressionType.OrElse => "||",
            ExpressionType.Equal => "==",
            ExpressionType.NotEqual => "!=",
            ExpressionType.LessThan => "<",
            ExpressionType.LessThanOrEqual => "<=",
            ExpressionType.GreaterThan => ">",
            ExpressionType.GreaterThanOrEqual => ">=",
            _ => nodeType.ToString()
        };
}
91430cookie-checkConvert LINQ expression to readable string