Как преобразовать выражение <Func <T1, U >> в выражение <Func <T2, U >>? - PullRequest
0 голосов
/ 19 июня 2019

Рассмотрим пример проекта ниже. Учитывая выражение Expression<Func<T, U>>, я хочу создать выражение, которое можно вызывать для любого T, который реализует корневой интерфейс (IBase).

Проблема

Преобразователи выражений ниже кажутся работающими, но при вызове в более сложных выражениях преобразованный Func генерирует InvalidOperationException. До сих пор я не смог воспроизвести это. Хотя, глядя на полученное выражение, я вижу, что есть два параметра - t и Param_0 - которые, как я полагаю, как-то вызывают проблемы.

System.InvalidOperationException: переменная 't' типа 'IBase' ссылка из области «Имя», но она не определена

Приведенные ниже методы конвертации должны быть улучшены. Пожалуйста, сообщите!

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();
        }
    }
}

Если промежуточный интерфейс (например, IBar) не реализован, он должен просто вернуть значение по умолчанию - это не реализовано в моем примере кода.

Обновление

По сути, я хочу подражать этому:

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

1 Ответ

1 голос
/ 20 июня 2019

Не имея возможности воспроизвести вашу ошибку, трудно сказать, что происходит не так.Использование LINQPad с его инструментом Dump может помочь вам просмотреть ваши творения Expression и сравнить их.

Однако, нетрудно добавить соответствующую условную логику в ваше поколение.

Для 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);

Для Test2:

var iif = Expression.IfThen(Expression.TypeIs(p2, typeof(T)), invoke);
var lambda = Expression.Lambda<Action<IBase, TProperty>>(iif, p2, p1);
...