AvaloniaUI/Avalonia · error · ExpressionParseException

Invalid expression type in binding expression: {node.NodeTyp

Error message

Invalid expression type in binding expression: {node.NodeType}.

What it means

Thrown by BindingExpressionVisitor.VisitBinary when a BinaryExpression node other than ArrayIndex is encountered in a compiled binding lambda. The visitor only supports array indexing as a binary operation (it converts it to an IndexExpression internally); arithmetic operators (+, -, *, /), comparisons (==, <, >), logical operators (&&, ||), and all other binary expression types are rejected because Avalonia compiled bindings model a property path, not a computation.

Source

Thrown at src/Avalonia.Base/Data/Core/Parsers/BindingExpressionVisitor.cs:62

    /// <exception cref="ExpressionParseException">
    /// Thrown when the expression contains unsupported operations or invalid syntax for binding
    /// expressions.
    /// </exception>
    public static CompiledBindingPath BuildPath<TOut>(Expression<Func<TIn, TOut>> expression)
    {
        var visitor = new BindingExpressionVisitor<TIn>(expression);
        visitor.Visit(expression);
        return visitor._builder.Build();
    }

    protected override Expression VisitBinary(BinaryExpression node)
    {
        // Indexers require more work since the compiler doesn't generate IndexExpressions:
        // they weren't in System.Linq.Expressions v1 and so must be generated manually.
        if (node.NodeType == ExpressionType.ArrayIndex)
            return Visit(Expression.MakeIndex(node.Left, null, [node.Right]));

        throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
    }

    protected override Expression VisitIndex(IndexExpression node)
    {
        if (node.Indexer == BindingExpressionVisitorMembers.AvaloniaObjectIndexer)
        {
            var property = GetValue<AvaloniaProperty>(node.Arguments[0]);
            return Add(node.Object, node, x => x.Property(property, CreateAvaloniaPropertyAccessor));
        }
        else if (node.Object?.Type.IsArray == true)
        {
            var indexes = node.Arguments.Select(GetValue<int>).ToArray();
            return Add(node.Object, node, x => x.ArrayElement(indexes, node.Type));
        }
        else if (node.Indexer?.GetMethod is not null &&
            node.Arguments.Count == 1 &&
            node.Arguments[0].Type == typeof(int))
        {

View on GitHub (pinned to 11c5427268)

Solutions

  1. Move the computation into a read-only property on the view model and bind to that property instead.
  2. Use a multi-binding with an IMultiValueConverter if the computation involves multiple sources.
  3. For string formatting, use the StringFormat option on the binding rather than string concatenation in the lambda.

Example fix

// before: arithmetic in compiled binding
<TextBlock Text="{CompiledBinding A + B}" />

// after: bind to a computed property
<TextBlock Text="{CompiledBinding Sum}" />
// where the view model exposes: public int Sum => A + B;
Defensive patterns

Strategy: validation

Validate before calling

// Walk the expression tree to detect unsupported binary operators before building the binding
static bool IsValidBindingExpression<TIn, TOut>(Expression<Func<TIn, TOut>> expr)
{
    var unsupported = new HashSet<ExpressionType> {
        ExpressionType.Add, ExpressionType.Subtract, ExpressionType.Multiply,
        ExpressionType.Divide, ExpressionType.Equal, ExpressionType.NotEqual,
        ExpressionType.GreaterThan, ExpressionType.LessThan, ExpressionType.AndAlso,
        ExpressionType.OrElse, ExpressionType.Modulo
    };
    bool valid = true;
    expr.Body.Visit(b => {
        if (b is BinaryExpression bin && bin.NodeType != ExpressionType.ArrayIndex
            && unsupported.Contains(bin.NodeType))
            valid = false;
    });
    return valid;
}

Try / catch

try
{
    var path = BindingExpressionVisitor<TViewModel>.BuildPath<string>(expr);
}
catch (ExpressionParseException ex) when (ex.Message.Contains("Invalid expression type"))
{
    logger.LogError($"Compiled binding lambda contains an unsupported binary operator: {ex.Message}");
}

Prevention

When it happens

Trigger: Writing a compiled binding lambda that contains arithmetic: x => x.A + x.B; or a comparison: x => x.Value == 0; or any binary operator. The C# compiler accepts these, but the BindingExpressionVisitor rejects them when walking the expression tree to build the binding path.

Common situations: Developers accustomed to WPF value converters or calculated bindings attempting to do inline computation in a CompiledBinding lambda; migrating from string-based bindings that happened to work because Avalonia's string grammar also does not support arithmetic; expecting compiled bindings to support the same expressiveness as full LINQ.

Related errors


AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13). Data as JSON: /api/errors/44e455c072f7a9ee. Report an issue: GitHub.