l33t
l33t

Reputation: 19937

How to convert Expression<Func<T1, U>> to Expression<Func<T2, U>>?

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 problem

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.

Update

Basically, I want to mimic this:

string MyFooExpressionCompiledMethod(IBase b)
{
    if (b is IFoo foo)
    {
        return foo.Foo;
    }
    return null;
}

Upvotes: 0

Views: 137

Answers (1)

NetMage
NetMage

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

Related Questions