运行时生成的表达式不能更改字典的值
Runtime generated expression cannot change dictionary's values
我正在尝试在运行时创建一些表达式来更改给定字典的值。我创建了这个成功生成表达式并将其编译为 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."
}
好的。最后我找到了问题和解决方案。代码的断点位于内部 foreach
循环中的赋值,我试图将 Expression.Block
赋值给 IndexerExpression
。似乎 block
ing 表达式不会调用它。因此,我通过调用 Expression.Invoke
并传递 IndexerExpression
(名为 target
)将其更改为 InvokationExpression
,现在它就像一个魅力:
foreach (var ins in innerStatements) {
var assign = Expression.Assign(target, Expression.Invoke(ins, target));
cleanStatements.Add(assign);
}
我正在尝试在运行时创建一些表达式来更改给定字典的值。我创建了这个成功生成表达式并将其编译为 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."
}
好的。最后我找到了问题和解决方案。代码的断点位于内部 foreach
循环中的赋值,我试图将 Expression.Block
赋值给 IndexerExpression
。似乎 block
ing 表达式不会调用它。因此,我通过调用 Expression.Invoke
并传递 IndexerExpression
(名为 target
)将其更改为 InvokationExpression
,现在它就像一个魅力:
foreach (var ins in innerStatements) {
var assign = Expression.Assign(target, Expression.Invoke(ins, target));
cleanStatements.Add(assign);
}