Reputation: 149
I'm trying to build an expression tree but it throws NotSupportedException when compiled:
TryExpression is not supported as an argument to method 'Boolean TryGetMemberValue(System.Object, System.String, System.Object ByRef)' because it has an argument with by-ref type. Construct the tree so the TryExpression is not nested inside of this expression.
I sort of understand what it says but I can't figure out what I need to change... here's the code:
static void Main()
{
//A.Child.Name
var child = CreateExpression("Child", proxyParameter);
var name = CreateExpression("Name", child);
var expr = Expression.Lambda<Func<Proxy, object>>(name, proxyParameter);
var func = expr.Compile();
}
class A
{
public A Child { get; set; }
public string Name { get; set; }
}
abstract class Proxy
{
public abstract bool TryGetMemberValue(object parent, string name, out object result);
}
static ParameterExpression proxyParameter = Expression.Parameter(typeof(Proxy), "proxy");
static Expression CreateExpression(string propertyName, Expression parent)
{
var tryGetMemberMethod = typeof(Proxy).GetMethod("TryGetMemberValue");
var result = Expression.Variable(typeof(object), "out result");
var returnTarget = Expression.Label(typeof(object));
var tryGetMember =
Expression.Block(
new[] { result },
Expression.IfThenElse(Expression.Equal(Expression.Call(proxyParameter, tryGetMemberMethod, parent, Expression.Constant(propertyName), result), Expression.Constant(true)),
Expression.Return(returnTarget, result),
Expression.Throw(Expression.Constant(new MissingMemberException(propertyName)))),
Expression.Label(returnTarget, Expression.Constant(null)));
return tryGetMember;
}
Which generates the following expression:
.Lambda #Lambda1<System.Func`2[Program+Proxy,System.Object]>(Program+Proxy $proxy) {
.Block(System.Object $'out result') {
.If (.Call $proxy.TryGetMemberValue(
.Block(System.Object $'out result') {
.If (.Call $proxy.TryGetMemberValue(
$proxy,
"Child",
$'out result') == True) {
.Return #Label1 { $'out result' }
} .Else {
.Throw .Constant<System.MissingMemberException>(System.MissingMemberException: Child)
};
.Label
null
.LabelTarget #Label1:
},
"Name",
$'out result') == True) {
.Return #Label2 { $'out result' }
} .Else {
.Throw .Constant<System.MissingMemberException>(System.MissingMemberException: Name)
};
.Label
null
.LabelTarget #Label2:
}
Any ideas?
/* edited */ interestingly enough this works fine:
var tryGetMember =
Expression.Block(
new[] { result },
Expression.Call(proxyParameter, tryGetMemberMethod, parent, Expression.Constant(propertyName), result),
result);
Upvotes: 2
Views: 2045
Reputation: 245008
While the exception message is far from clear, I think you just encountered a limitation of the Expression
compiler: you can't have complicated subexpressions under a call expression to a method with ref
parameters.
I think what you should do to fix this is basically the same thing you would do in C# code: use a local variable to keep the intermediate result:
var child = Expression.Variable(typeof(object), "child");
var body = Expression.Block(
new[] { child },
Expression.Assign(child, CreateExpression("Child", proxyParameter)),
CreateExpression("Name", child));
var expr = Expression.Lambda<Func<Proxy, object>>(body, proxyParameter);
With this change, the code no longer throws an exception.
Also, you don't need to use Label
and Return
in your tryGetMember
expression. Instead, you could replace IfThenElse
with Condition
(basically, ternary operator from C#). If you do this, you also need to specify the type of Throw
(even though it never actually returns):
var tryGetMember =
Expression.Block(
new[] { result },
Expression.Condition(
Expression.Equal(
Expression.Call(
proxyParameter, tryGetMemberMethod, parent,
Expression.Constant(propertyName), result),
Expression.Constant(true)),
result,
Expression.Throw(
Expression.Constant(new MissingMemberException(propertyName)),
typeof(object))));
Upvotes: 2