Last active
August 8, 2023 17:35
-
-
Save daniel-chenery/2f1ca39ef183fba787abfa6b01351e13 to your computer and use it in GitHub Desktop.
Expression Explainer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq.Expressions; | |
using System.Reflection; | |
using System.Text; | |
internal class Program | |
{ | |
static void Main() | |
{ | |
var defaultError = "Invalid string"; | |
var translator = new ExpressionTranslator(); | |
Expression<Func<string, string>> expression = (string str) => str.Length > 0 ? "String has content!" : defaultError; | |
Console.WriteLine(translator.Explain(expression)); | |
} | |
} | |
internal class ExpressionTranslator : ExpressionVisitor | |
{ | |
private readonly StringBuilder _builder; | |
public ExpressionTranslator() | |
{ | |
_builder = new StringBuilder(); | |
} | |
public string Explain(Expression expression) | |
{ | |
_builder.Clear(); | |
Visit(expression); | |
return _builder.ToString(); | |
} | |
protected override Expression VisitParameter(ParameterExpression node) | |
{ | |
_builder.Append(node.Name); | |
_builder.Space(); | |
return node; | |
} | |
protected override Expression VisitMember(MemberExpression node) | |
{ | |
_builder.Append("selecting "); | |
Visit(node.Expression); | |
_builder.TrimSpace(); | |
if (node.Member is FieldInfo fieldInfo && fieldInfo.IsStatic || node.Member is PropertyInfo propertyInfo && propertyInfo.GetGetMethod()?.IsStatic == true) | |
{ | |
_builder.Append(node.Member.ReflectedType?.Name); | |
} | |
_builder.Append('.'); | |
_builder.Append(node.Member.Name); | |
_builder.Space(); | |
return node; | |
} | |
protected override Expression VisitLambda<T>(Expression<T> node) | |
{ | |
_builder.Append($"{node.Name ?? "Lambda"} is invoked "); | |
if (node.Parameters.Count > 0) | |
{ | |
_builder.Append("with "); | |
VisitArguments(node.Parameters); | |
} | |
Visit(node.Body); | |
return node; | |
} | |
protected override Expression VisitMethodCall(MethodCallExpression node) | |
{ | |
_builder.Append("calling "); | |
_builder.Append('('); | |
if (node.Method.IsStatic) | |
{ | |
_builder.Append(node.Method.ReflectedType.Name); | |
} | |
else | |
{ | |
Visit(node.Object); | |
} | |
_builder.TrimSpace(); | |
_builder.Append('.'); | |
_builder.Append(node.Method.Name); | |
_builder.Space(); | |
if (node.Arguments.Count > 0) | |
{ | |
_builder.Append("with "); | |
VisitArguments(node.Arguments); | |
} | |
_builder.AppendFormat("returning type of '{0}'", node.Method.ReturnType.Name); | |
_builder.Space(); | |
_builder.TrimSpace(); | |
_builder.Append(')'); | |
_builder.Space(); | |
return node; | |
} | |
protected override Expression VisitConstant(ConstantExpression node) | |
{ | |
_builder.AppendFormat("{0}a constant value of '{1}'", _builder.Length == 0 ? "Is " : string.Empty, node.Value); | |
_builder.Space(); | |
return node; | |
} | |
protected override Expression VisitBinary(BinaryExpression node) | |
{ | |
Visit(node.Left); | |
_builder.AppendFormat("performing '{0}'", node.NodeType); | |
_builder.Space(); | |
Visit(node.Right); | |
return node; | |
} | |
protected override Expression VisitUnary(UnaryExpression node) | |
{ | |
_builder.AppendFormat("performing '{0}' to '{1}'", node.NodeType, node.Type.Name); | |
_builder.Space(); | |
_builder.Append('('); | |
Visit(node.Operand); | |
_builder.Length--; | |
_builder.Append(')'); | |
_builder.Space(); | |
return node; | |
} | |
private void VisitArguments(IReadOnlyCollection<Expression> arguments) | |
{ | |
if (arguments.Count > 0) | |
{ | |
foreach (var argument in arguments) | |
{ | |
Visit(argument); | |
_builder.AppendFormat("(of type '{0}')", argument.Type.Name); | |
_builder.Append(" and "); | |
} | |
_builder.Length -= 4; | |
} | |
} | |
protected override Expression VisitConditional(ConditionalExpression node) | |
{ | |
_builder.AppendFormat("checking {0}", node.NodeType); | |
_builder.Space(); | |
Visit(node.Test); | |
_builder.Append("then "); | |
Visit(node.IfTrue); | |
_builder.Append("otherwise "); | |
Visit(node.IfFalse); | |
return node; | |
} | |
protected override Expression VisitNew(NewExpression node) | |
{ | |
_builder.Append("creating "); | |
_builder.Append(node.Type.Name); | |
_builder.Space(); | |
if (node.Arguments.Count > 0) | |
{ | |
_builder.Append("with "); | |
VisitArguments(node.Arguments); | |
} | |
return node; | |
} | |
public override Expression Visit(Expression node) | |
{ | |
if (node == null) | |
{ | |
_builder.Space(); | |
return node; | |
} | |
return base.Visit(node); | |
} | |
} | |
internal static class Extensions | |
{ | |
public static StringBuilder Space(this StringBuilder stringBuilder) => stringBuilder.Append(' '); | |
public static StringBuilder TrimSpace(this StringBuilder stringBuilder) | |
{ | |
if (stringBuilder[stringBuilder.Length - 1] == ' ') | |
{ | |
stringBuilder.Length--; | |
} | |
return stringBuilder; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment