Дерево выражений - как добраться до объявления экземпляра? - PullRequest
4 голосов
/ 13 января 2010

Я новичок, когда дело доходит до деревьев выражений, поэтому я не уверен, как задать этот вопрос или какую терминологию использовать. Вот чрезмерно упрощенная версия того, что я пытаюсь сделать:

Bar bar = new Bar();
Zap(() => bar.Foo);

public static void Zap<T>(Expression<Func<T>> source)
{
   // HELP HERE:
   // I want to get the bar instance and call bar.Zim() or some other method.
}

Как мне попасть в бар внутри метода Zap?

Ответы [ 3 ]

6 голосов
/ 13 января 2010

Поскольку выражение, переданное в ваш метод Zap, является деревом, вам просто нужно пройтись по дереву, используя Посетитель дерева выражений и найти первое ConstantExpression в выражении. Скорее всего, это будет в следующей последовательности:

(((source.Body as MemberExpression).Expression as MemberExpression).Expression as ConstantExpression).Value

Обратите внимание, что экземпляр bar захвачен замыканием, которое реализовано как внутренний класс с экземпляром в качестве члена, откуда и происходит второе выражение MemberExpression.

EDIT

Затем вы должны получить поле из сгенерированного замыкания следующим образом:

    static void Main(string[] args)
    {
        var bar = new Bar();
        bar.Foo = "Hello, Zap";
        Zap(() => bar.Foo);
    }

    private class Bar
    {
        public String Foo { get; set; }    
    }

    public static void Zap<T>(Expression<Func<T>> source)
    {
        var param = (((source.Body as MemberExpression).Expression as MemberExpression).Expression as ConstantExpression).Value;
        var type = param.GetType();
        // Note that the C# compiler creates the field of the closure class 
        // with the name of local variable that was captured in Main()
        var field = type.GetField("bar");
        var bar = field.GetValue(param) as Bar;
        Debug.Assert(bar != null);
        Console.WriteLine(bar.Foo);
    }
5 голосов
/ 14 января 2010

Если вы знаете тип «бара», вы можете сделать это (я повторно использую некоторые биты из ответа кодекайзена здесь):

    static void Main(string[] args)
    {
        var bar = new Bar();
        bar.Foo = "Hello, Zap";
        Zap(() => bar.Foo);

        Console.ReadLine();
    }

    private class Bar
    {
        public String Foo { get; set; }
    }

    public static void Zap<T>(Expression<Func<T>> source)
    {
        var body = source.Body as MemberExpression;
        Bar test = Expression.Lambda<Func<Bar>>(body.Expression).Compile()();
        Console.WriteLine(test.Foo);
    } 

В большинстве случаев вы можете найти выражение, представляющее ваш объект в дереве выражений, а затем скомпилировать и выполнить это выражение и получить объект (кстати, это не очень быстрая операция). Итак, бит, который вы пропустили, это метод Compile (). Вы можете найти немного больше информации здесь: Как: выполнить деревья выражений .

В этом коде я предполагаю, что вы всегда передаете выражение типа "() => object.Member". В реальном сценарии вам нужно будет либо проанализировать, что у вас есть нужное выражение (например, просто выдать исключение, если оно не является выражением MemberExpression). Или используйте ExpressionVisitor, что довольно сложно.

Я недавно ответил на очень похожий вопрос здесь: Как подписаться на событие объекта внутри дерева выражений?

1 голос
/ 11 мая 2013

Стоящий на плечах гигантов выше, мой последний метод расширения для извлечения экземпляра класса, который представлял источник выражения, выглядит следующим образом:

public static TIn GetSource<TIn, TOut>(this Expression<Func<TIn, TOut>> property)
        where TIn: class
{
    MemberExpression memberExpression = (MemberExpression)property.Body;
    TIn instance = Expression.Lambda<Func<TIn>>(memberExpression.Expression).Compile()();
    return instance;
}

Я построил все ответы выше, спасибо всем.

...