Reputation: 19937
Consider the sample project below. Given an expression of Expression<Func<T, U>>
I want to create an expression that can be called for any T
that implements the root interface (IBase
).
The expression converters below seem to work, but when invoked in more advanced expressions the converted Func
throws InvalidOperationException
. So far I have not been able to reproduce this. Though, looking at the resulting expression I do see that there are two parameters - t
and Param_0
- which I assume somehow causes a problem.
System.InvalidOperationException: 'variable 't' of type 'IBase' referenced from scope 'Name', but it is not defined'
The convert methods below must be improved somehow. Please advise!
namespace ExpressionTest
{
interface IBase
{
string Name { get; set; }
}
interface IFoo : IBase
{
string Foo { get; set; }
}
interface IBar : IBase
{
string Bar { get; set; }
}
class FooImpl : IFoo
{
public FooImpl()
{
Name = "Name";
Foo = "Foo";
var e1 = Test<IBase, string>(t => t.Name);
var e2 = Test<IFoo, string>(t => t.Foo);
var e3 = Test<IBar, string>(t => t.Bar);
var e4 = Test2<IBase, string>((b, v) => Stuff(b, v));
e4.Compile().Invoke(this, "new");
var name = e1.Compile().Invoke(this);
var foo = e2.Compile().Invoke(this);
// TODO: Use "as" operator instead of cast...
// var bar = e3.Compile().Invoke(this);
}
public void Stuff(IBase b, string v)
{
b.Name = v;
}
public string Name { get; set; }
public string Foo { get; set; }
private Expression<Func<IBase, TProperty>>
Test<T, TProperty>(Expression<Func<T, TProperty>> expr)
where T : IBase
{
var p = Expression.Parameter(typeof(IBase));
var convert = Expression.Convert(p, typeof(T));
var invoke = Expression.Invoke(expr, convert);
var lambda = Expression.Lambda<Func<IBase, TProperty>>(invoke, p);
return lambda;
}
private Expression<Action<IBase, TProperty>>
Test2<T, TProperty>(Expression<Action<T, TProperty>> expr)
where T : IBase
{
var p1 = expr.Parameters.Last();
var p2 = Expression.Parameter(typeof(IBase));
var convert = Expression.Convert(p2, typeof(T));
var invoke = Expression.Invoke(expr, convert, p1);
var lambda = Expression.Lambda<Action<IBase, TProperty>>(invoke, p2, p1);
return lambda;
}
}
class Program
{
static void Main(string[] args)
{
var foo = new FooImpl();
}
}
}
If the intermediate interface (e.g. IBar
) is not implemented, it should just return the default value - this is not implemented in my sample code.
Basically, I want to mimic this:
string MyFooExpressionCompiledMethod(IBase b)
{
if (b is IFoo foo)
{
return foo.Foo;
}
return null;
}
Upvotes: 0
Views: 137
Reputation: 26917
Without being able to replicate your error, it is hard to say what is going wrong. Using LINQPad with its Dump tool can help view your Expression creations and compare them.
However, it is not difficult to add the appropriate conditional logic to your generation.
For Test
:
var iif = Expression.Condition(Expression.TypeIs(p, typeof(T)), invoke, Expression.Constant(null, typeof(TProperty)));
var lambda = Expression.Lambda<Func<IBase, TProperty>>(iif, p);
For Test2
:
var iif = Expression.IfThen(Expression.TypeIs(p2, typeof(T)), invoke);
var lambda = Expression.Lambda<Action<IBase, TProperty>>(iif, p2, p1);
Upvotes: 1