DLR LambdaExpressions и объект System.Runtime.CompilerServices.Closure - PullRequest
4 голосов
/ 04 июня 2011

Я работаю над небольшим языком программирования для Microsoft DLR, и у меня возникла небольшая проблема с вызовом моих анонимных методов. В частности, код:

Delegate CompiledBody = Expression.Lambda(rt.Parser.ParseSingle(Body), parms).Compile();

Итак, parms - это массив, содержащий одно выражение ParameterExpression, а первый аргумент содержит соответствующие выражения для определения анонимной функции. Когда я пытаюсь вызвать моего делегата с помощью Expression.Call для CompiledBody.Method (MethodInfo), я получаю сообщение об ошибке:

Unhandled Exception: System.ArgumentException: Expression of type 'System.Object' 
cannot be used for parameter of type 'System.Runtime.CompilerServices.Closure' 
of method 'Shiro.Runtime.ShiroAtom lambda_method(System.Runtime.CompilerServices
.Closure, Shiro.Runtime.ShiroAtom)'

Теперь где-то по пути мой метод с одним аргументом получил второй аргумент, типа System.Runtime.CompilerServices.Closure (второй параметр типа ShiroAtom - мой параметр). Это имеет смысл, за исключением того, что (а) мне действительно все равно, находится ли метод в этом контексте в области действия Closure, и (b) я не могу создать даже пустую область действия Closure для передачи этого параметра.

Буду признателен за любую помощь! Заранее спасибо.

РЕДАКТИРОВАТЬ: Некоторая дополнительная информация, основанная на удивительном ответе ниже:

Где этот код происходит глубоко в недрах моего парсера. У меня есть поток токенов (на самом деле, атомы), которые переводятся в AST. Этот конкретный бит является процедурой анализа вызова функции. Он создал CompiledBody, затем пытается вызвать его, используя что-то вроде:

return Expression.Call(CompiledBody.Method, Expression.Constant("argument"));

Результирующая лямбда представляет функцию. Исходя из моей архитектуры, есть только несколько мест, которые я могу вызвать DynamicInvoke или просто вызвать скомпилированный делегат, и это не одно из них. Хотелось бы привести более существенный пример, но эта ситуация возникает в разгар парсера с ручным кодированием, и потребовалось бы слишком много кода, чтобы действительно сообщить почему ситуация такова, но я действительно нужен способ вызова скомпилированной лямбды через Expression.Call, как показано выше.

Суть проблемы в том, что моей скомпилированной лямбде требуется 1 дополнительный параметр к указанным мною, CompilerServices.Closure, и я не знаю, как его создать.

1 Ответ

3 голосов
/ 04 июня 2011

Было бы полезно, если бы вы могли поделиться телом, которое вы компилируете, так как оно будет содержать фактическое закрытие и то, как вы его вызываете.Я предполагаю, что вы пытаетесь вызвать результирующий делегат как-то «вручную», вместо того, чтобы удерживать объект делегата и просто генерировать выражение Invoke.Если вы хотите использовать замыкания DLR, это должно выглядеть так:

using System;
using System.Linq.Expressions;

class Program {
    static void Main(string[] args) {
        var outerParam = Expression.Parameter(typeof(int), "outerParam");

        var lambda =
            Expression.Lambda<Func<int, Action>>(
                Expression.Lambda<Action>(
                    Expression.Call(
                        typeof(Console).GetMethod("WriteLine", new Type[] { typeof(object) }),
                        Expression.Convert(outerParam, typeof(object))
                    )
                ),
                outerParam
            ).Compile();

        var actionParam = Expression.Parameter(typeof(Action), "action");
        var lambdaInvoker =
            Expression.Lambda<Action<Action>>(
                Expression.Invoke(actionParam),
                actionParam
            ).Compile();

        lambdaInvoker(lambda(100));
        lambdaInvoker(lambda(200));
        Console.ReadLine();
    }
}

Это создает 3 лямбды: 1-й содержит 2-ю внутреннюю лямбду, которая закрывается по параметру.Тип результирующего делегата замыкания - это тип, указанный при создании лямбда-выражения, даже если там есть дополнительный скрытый параметр.Третья лямбда показывает, как вы можете вызвать это из другой лямбды - то есть через вызов делегата.Наконец, мы объединяем делегатов, чтобы показать, как это работает.

Также следует помнить о том, что замыкания DLR на самом деле не так уж хороши сейчас из-за ограничений в CLR.Создание замыкания на самом деле является довольно медленным процессом, потому что для этого необходимо пройти рефлексию, вместо того чтобы иметь возможность создавать делегат напрямую.Если вы обеспокоены производительностью делегата, вам нужно отслеживать переменные и закрывать потоки по значениям через собственную структуру данных (это то, что делают и IronRuby, и IronPython).

...