Выражение, сгенерированное во время выполнения, не может изменить значения словаря

Я пытаюсь создать некоторое выражение во время выполнения, чтобы изменить значения данного словаря. Я создал этот фрагмент, который успешно генерирует выражение и компилирует его как Action. Но вызов действия не может изменить значение словаря, а также не вызывает никаких ошибок. Вот код:

public class ChangeDicValue {

    public void Change(IDictionary<string, object> dic) {
        var blocks = MakeCleaningBlock(dic);
        foreach (var block in blocks) 
            block.Invoke(dic);
    }

    private List<Action<IDictionary<string, Object>>> MakeCleaningBlock(IDictionary<string , object > dic) {

        var allKeys = dic.Keys.ToArray();

        var dicType = typeof(IDictionary<,>).MakeGenericType(typeof(string), typeof(object));

        var dicContainsMethod = dicType.GetMethod("ContainsKey", new[] {typeof(string)})
                                ?? throw new InvalidOperationException();

        var actions = new List<Action<IDictionary<string, Object>>>();

        ParameterExpression actionArguments =
            Expression.Parameter(dicType, "actionArguments");

        foreach (var k in allKeys) {

            Expression key = Expression.Constant(k, typeof(string));

            Expression target = Expression.Property(actionArguments, "Item", key);

            var innerStatements = new List<Expression>(Changers);

            var cleanStatements = new List<Expression>();

            foreach (var ins in innerStatements) {
                var assign = Expression.Assign(target, Expression.Block(ins, target));

                cleanStatements.Add(assign);
            }

            Expression body1 = Expression.Block(new List<Expression>(cleanStatements) {target});

            var callToContains = Expression.Call(actionArguments, dicContainsMethod, key);
            var ifThenBody     = Expression.IfThen(callToContains, body1);

            var cleanedValueBlock = Expression.Block(target, ifThenBody, target);

            var assignDic = Expression.Assign(target, cleanedValueBlock);
            // see the debug view of assignDic in UPDATE

            var lambda = Expression.Lambda<Action<IDictionary<string, Object>>>(assignDic, actionArguments);

            var method = lambda.Compile();

            actions.Add(method);
        }

        return actions;
    }


    private static readonly Expression<Func<object, string>>[] Changers
        = {
            s => s + " First changer added.", 
            s => s + " Second changer added."
        };

}

Как видите, это довольно простой код, не вызывающий ошибок. Ты хоть представляешь, что я пропустил?

ИЗМЕНИТЬ:

Представление отладки переменной assignDic для одного элемента в образце словаря:

$actionArguments.Item["a"] = .Block() {
    $actionArguments.Item["a"];
    .If (
        .Call $actionArguments.ContainsKey("a")
    ) {
        .Block() {
            $actionArguments.Item["a"] = .Block() {
                .Lambda #Lambda1<System.Func`2[System.Object,System.String]>;
                $actionArguments.Item["a"]
            };
            $actionArguments.Item["a"] = .Block() {
                .Lambda #Lambda2<System.Func`2[System.Object,System.String]>;
                $actionArguments.Item["a"]
            };
            $actionArguments.Item["a"]
        }
    } .Else {
        .Default(System.Void)
    };
    $actionArguments.Item["a"]
}

.Lambda #Lambda1<System.Func`2[System.Object,System.String]>(System.Object $s) {
    $s + " First changer added."
}

.Lambda #Lambda2<System.Func`2[System.Object,System.String]>(System.Object $s) {
    $s + " Second changer added."
}

person amiry jd    schedule 18.01.2020    source источник
comment
Почему вы создаете Expression и вызываете Invoke() вместо того, чтобы просто использовать объекты Func<>, которые вы определили в конце, или напрямую строить операторы Action? Что заставляет вас использовать сборку объекта Expression?   -  person Progman    schedule 18.01.2020
comment
@Progman Это часть более крупного кода, который заставляет меня создавать некоторые действия (как чейнджеры) во время выполнения на основе разных словарей.   -  person amiry jd    schedule 18.01.2020
comment
И вы не можете построить Action самостоятельно с чем-то вроде Action<...> action = dict => { foreach (var entry in dict) { if (something.ContainsKey(entry.Item)) { ... } } ... (в зависимости от того, что должен делать код)? Может быть проще читать/обслуживать, чем создавать Expression объектов.   -  person Progman    schedule 18.01.2020
comment
@Progman К сожалению, нет. Это невозможно.   -  person amiry jd    schedule 18.01.2020
comment
Можете ли вы отредактировать свой вопрос, включив в него DebugView выражения, которое вы создаете (как указано в stackoverflow.com/questions/34116591/ и stackoverflow.com/questions/51776624/)? Это может дать некоторое представление о том, как было построено выражение и имеет ли оно смысл.   -  person Progman    schedule 18.01.2020
comment
Код не очень простой, его трудно читать и понимать. Особенно Expression.Assign с правым операндом в виде блока. Скорее всего проблема в этом.   -  person Ivan Stoev    schedule 18.01.2020
comment
@Progman Я отредактировал вопрос и добавил представление отладки.   -  person amiry jd    schedule 18.01.2020
comment
@IvanStoev кажется мне простым. Ничего особенного в коде нет. Я согласен с вами, что выражение assign вызывает проблему. Но не могу понять, как решить.   -  person amiry jd    schedule 18.01.2020
comment
Было бы полезно, если бы вы показали в простом коде С#, что вы пытаетесь сгенерировать. например для каждого ключа в словаре вызывать несколько лямбда-выражений для значения словаря и сохранять его обратно?   -  person Ivan Stoev    schedule 18.01.2020
comment
@IvanStoev Спасибо за помощь и упоминания. Нашел проблему и решение. Смотрите ответ, который я разместил. еще раз спасибо.   -  person amiry jd    schedule 18.01.2020


Ответы (1)


ХОРОШО. Наконец я нашел проблему и решение. Точка останова кода была на присваивании во внутреннем цикле foreach, где я пытался присвоить Expression.Block IndexerExpression. Кажется, что blocking выражение не вызовет его. Итак, я изменил его на InvokationExpression, вызвав Expression.Invoke и передав IndexerExpression (с именем target), и теперь он работает как шарм:

foreach (var ins in innerStatements) {
    var assign = Expression.Assign(target, Expression.Invoke(ins, target));
    cleanStatements.Add(assign);
}
person amiry jd    schedule 18.01.2020