dotnet/efcore · error · NotImplementedException

Compound assignment of private property not yet supported

Error message

Compound assignment of private property not yet supported

What it means

Thrown by LinqToCSharpSyntaxTranslator.TranslateNonPublicMemberAssignment when the lvalue is a private/internal PropertyInfo (accessed via UnsafeAccessor) and the assignment is not a simple SyntaxKind.SimpleAssignmentExpression — i.e. a compound assignment such as +=, -=, *=, etc. The property path only supports direct setter invocation; compound assignments would require read-modify-write across getter and setter, which is not implemented.

Solutions

  1. Rewrite the compound assignment as an explicit read-then-write: read via the getter, combine with the right operand, then assign via the setter using simple assignment.
  2. Expose a public method that performs the compound operation and call it instead.
  3. Make the property public so the normal assignment path applies (where compound operators are supported).

Example fix

// before — compound assign to a private property
var prop = typeof(Item).GetProperty("Count",
    BindingFlags.NonPublic | BindingFlags.Instance)!;
var assign = Expression.AddAssign(
    Expression.Property(itemParam, prop),
    Expression.Constant(1));

// after — expand to getter + setter with simple assignment
var read = Expression.Property(itemParam, prop);
var assign = Expression.Assign(
    Expression.Property(itemParam, prop),
    Expression.Add(read, Expression.Constant(1)));
Defensive patterns

Strategy: validation

Validate before calling

// Detect compound assignments targeting non-public properties.
using System.Linq.Expressions;
using System.Reflection;

static readonly HashSet<ExpressionType> CompoundAssignTypes = new()
{
    ExpressionType.AddAssign, ExpressionType.AddAssignChecked,
    ExpressionType.SubtractAssign, ExpressionType.SubtractAssignChecked,
    ExpressionType.MultiplyAssign, ExpressionType.MultiplyAssignChecked,
    ExpressionType.DivideAssign, ExpressionType.ModuloAssign,
    ExpressionType.AndAssign, ExpressionType.OrAssign,
    ExpressionType.LeftShiftAssign, ExpressionType.RightShiftAssign,
    ExpressionType.ExclusiveOrAssign, ExpressionType.PowerAssign,
};

bool HasCompoundAssignOnPrivateProperty(Expression e)
{
    bool bad = false;
    new CompoundChecker(b =>
    {
        if (CompoundAssignTypes.Contains(b.NodeType)
            && b.Left is MemberExpression { Member: PropertyInfo pi }
            && (pi.SetMethod?.IsPublic ?? false) == false)
            bad = true;
    }).Visit(e);
    return bad;
}

class CompoundChecker(Action<BinaryExpression> onBinary) : ExpressionVisitor
{
    protected override Expression VisitBinary(BinaryExpression b) { onBinary(b); return base.VisitBinary(b); }
}

Prevention

When it happens

Trigger: An expression tree performs a compound assignment (AddAssign, SubtractAssign, MultiplyAssign, ...) whose left side is a MemberExpression over a non-public property, reaching TranslateNonPublicMemberAssignment with assignmentKind != SimpleAssignmentExpression.

Common situations: Reflection-built expression trees that mutate private property state incrementally; provider/test code that does Count++ on a private property; converting C# compound-assignment code into expression trees that target private state.

Related errors


AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11). Data as JSON: /api/errors/c35755977cede226. Report an issue: GitHub.

Appendix: source

Thrown at src/EFCore.Design/Query/Internal/LinqToCSharpSyntaxTranslator.cs:1736

                : memberExpression.Member,
            forWrite: true);

        // The unsafe accessor declaration has been created; invoke it.
        Result = memberExpression.Member switch
        {
            FieldInfo => AssignmentExpression(
                assignmentKind,
                (ExpressionSyntax)_g.InvocationExpression(
                    _g.IdentifierName(unsafeAccessorDeclaration.Identifier.Text),
                    Translate<ExpressionSyntax>(memberExpression.Expression)),
                Translate<ExpressionSyntax>(value)),

            PropertyInfo =>
                _g.InvocationExpression(
                    _g.IdentifierName(unsafeAccessorDeclaration.Identifier.Text), Translate<ExpressionSyntax>(memberExpression.Expression),
                    assignmentKind is SyntaxKind.SimpleAssignmentExpression
                        ? Translate<ExpressionSyntax>(value)
                        : throw new NotImplementedException("Compound assignment of private property not yet supported")),

            _ => throw new UnreachableException()
        };
    }

    private MethodDeclarationSyntax GetUnsafeAccessorDeclaration(MemberInfo member, bool forWrite = false)
    {
        MethodDeclarationSyntax? unsafeAccessorDeclaration;

        switch (member)
        {
            case FieldInfo field:
            {
                // Note that we generate two accessors for fields (get/set), since the get accessor needs to be used in expression trees,
                // which don't support ref return
                if (_fieldUnsafeAccessors.TryGetValue((field, forWrite), out unsafeAccessorDeclaration))
                {
                    return unsafeAccessorDeclaration;

View on GitHub (pinned to 3a2006ef56)