Evaluate an expression
Create an expression from its Expressif source, then pass the value to transform to Evaluate(...):
var normalizeName = Expression.Create("trim | upper");
var firstResult = normalizeName.Evaluate(" Nikola Tesla ");
var secondResult = normalizeName.Evaluate(" Ada Lovelace ");
Expression.Create(...) parses the source and returns an executable IExpression. Parsing happens only once; the same object can then transform any number of values. Here, firstResult is "NIKOLA TESLA" and secondResult is "ADA LOVELACE".
The two calls play different roles:
Expression.Create("trim | upper")defines what to do.Evaluate(" Nikola Tesla ")supplies the value on which to do it.
Evaluate(...) accepts any supported .NET value and returns object? because different expressions can produce different types. Check or cast that result when your C# code needs a specific type:
if (firstResult is string normalizedName)
Console.WriteLine(normalizedName);
See Expressions for the Expressif pipeline and function-call syntax.
Supply variables
Variables hold values that are not part of the input itself, such as a user preference or application setting. Create the expression first, then attach an EvaluationContext containing those values:
var expression = Expression.Create("suffix(@suffix)");
var context = new EvaluationContext(
new Dictionary<string, object?>
{
["suffix"] = " Nikola!"
}
);
var configuredExpression = expression.WithContext(context);
var result = configuredExpression.Evaluate("Hello");
result is "Hello Nikola!". The dictionary stores the name as suffix; the Expressif source refers to it as @suffix.
WithContext(...) returns a new expression. It does not modify the original one. This lets the application reuse one parsed expression with different immutable contexts:
var expression = Expression.Create("suffix(@suffix)");
var excited = expression.WithContext(new EvaluationContext(
new Dictionary<string, object?> { ["suffix"] = "!" }
));
var questioning = expression.WithContext(new EvaluationContext(
new Dictionary<string, object?> { ["suffix"] = "?" }
));
var first = excited.Evaluate("Really"); // "Really!"
var second = questioning.Evaluate("Really"); // "Really?"
EvaluationContext copies the supplied variables and exposes them as a read-only dictionary. An expression configured this way can be evaluated concurrently.
Evaluate structured .NET values
Pass a structured value directly to Evaluate(...). Expressif can read fields from dictionaries and supported .NET objects:
var formatName = Expression.Create(".name | trim | suffix(^.suffix)");
var input = new Dictionary<string, object?>
{
["name"] = "Ada Lovelace ",
["suffix"] = " (mathematician)"
};
var result = formatName.Evaluate(input);
result is "Ada Lovelace (mathematician)". For this outer expression, the input passed to Evaluate(...) is the expression root, so ^.suffix still refers to that record after .name changes the value flowing through the pipeline. When a function invokes a nested expression, the value passed to the nested expression becomes its own root.
Each call receives its own evaluation frame. The same expression can therefore process several inputs—including concurrent inputs—without one call replacing another call’s expression root.
Format structured results
ValueFormatter renders evaluation results with Expressif scalar and structured-value syntax. Compact output is the default. Request pretty output when inspecting nested values, including from the Visual Studio Watch or Immediate window:
var compact = ValueFormatter.Format(result);
var pretty = ValueFormatter.Format(result, ValueFormat.Pretty);
var fourSpaces = ValueFormatter.Format(result, ValueFormat.Pretty, " ");
Pretty output writes each structured element on its own line and uses two spaces per nesting level. Empty structures remain on one line. Formatting does not change the underlying result.
Use ValueFormattingOptions for hybrid pretty output. InlineValueTypes contains the exact runtime types that may remain inline, and PreferredLineWidth includes the indentation and other text already written on the current line:
var hybrid = ValueFormatter.Format(result, new ValueFormattingOptions
{
Format = ValueFormat.Pretty,
InlineValueTypes = new HashSet<Type> { typeof(TupleValue) },
PreferredLineWidth = 100,
});
For each eligible value, the formatter measures the complete compact subtree. If it fits, the subtree is emitted atomically on one line. If it does not, that value uses the normal pretty layout and eligible descendants are considered independently. Compact formatting ignores the inline policy. Leaving InlineValueTypes empty preserves the standard pretty output.
See References for field, variable, and expression-root syntax. See Advanced expressions for nested expressions and other language features.