Дерево выражений Linq, компилирующее нетривиальные константы объектов и как-то ссылающиеся на них - PullRequest
0 голосов
/ 10 декабря 2018

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

   public class A
        { public int mint = -1; }

 public static void Main(String[] pArgs)
        {
            //Run(pArgs);

            Action pact = Thing();

            pact();
        }

        public static Action Thing()
        {
            var a = new A();
            a.mint = -1;

            LambdaExpression p =
                Expression.Lambda<Action>(Expression.Assign(Expression.Field(Expression.Constant(a, typeof(A)), Strong.Instance<A>.Field<int>(b => b.mint)), Expression.Constant(3, typeof(int))));

            return ((Expression<Action>)p).Compile();

        }

не только компилируется, но и фактически работает!Если вы запустите скомпилированный метод в методе Thing (), вы сможете увидеть переменную, изменив ее поле с -1 на 3

. Я не понимаю, как это имеет смысл / возможно.Как метод может ссылаться на локальную переменную вне своей области (при проверке IL объекта Thing () переменная a является просто стандартной локальной переменной, а не в куче, как с замыканием).Есть ли какой-то скрытый контекст вокруг?Как pact может выполняться в Main, когда локальная переменная a предположительно удалена из стека!

Ответы [ 2 ]

0 голосов
/ 10 декабря 2018

Как метод может ссылаться на локальную переменную вне своей области видимости

Он не может и не может.

Может иногда ссылаются на объект, на который указывает локальная переменная.

Может ли это зависеть от того, каким образом выражение компилируется или используется иным образом.

Существует три способа выражения Expressions.скомпилирует выражение в метод:

  1. Компиляция с Compile() в IL в DynamicMethod.
  2. Компиляция в IL в CompileToMethod() (доступно не во всех версиях.
  3. Компиляция с Compile() в интерпретированный набор инструкций с делегатом thunk, который выполняет интерпретацию.

Первый используется, если доступна компиляция IL, если только trueпередается для предпочтения интерпретации (в тех версиях с такой перегрузкой), и интерпретация также недоступна. Здесь для закрытия используется массив, и он почти такой же, как закрытие по локальной переменной в делегате.

TВторой используется для записи в другую сборку и не может закрываться таким образом.По этой причине многие константы, которые будут работать с Compile(), не будут работать с CompileToMethod().

Третья используется, если компиляция IL недоступна или true была передана в тех версиях, которые имеют эту перегрузкупредпочесть интерпретацию.Здесь ссылка на объект помещается в массив «констант», на которые затем может ссылаться интерпретатор.

Другая возможность состоит в том, что что-то еще полностью интерпретирует выражение, например, при создании кода SQL.Обычно это не удастся с не примитивными константами, отличными от строки, хотя, но если обработчик запросов знает о типе константы (например, если он относится к типу сущности, о которой он знает), тогда создайте код, эквивалентный этому.сущность может быть произведена.

0 голосов
/ 10 декабря 2018

Только a является локальной переменной;фактический объект (из new A()) всегда был в куче.Когда вы использовали Expression.Constant(a, typeof(A)), вы указали не a как константу, а значение из a, то есть ссылку на объект.Итак, область действия a не имеет отношения к дереву.Это именно то, как захваченные переменные (замыкания) обычно реализуются компилятором (хотя обычно вы этого не видите, а компилятор выражений на основе C # не допускает операторов присваивания), поскольку это касается дерева выражений.: это обычный бизнес.

В качестве сопоставимого примера использования компилятора выражений C # см. здесь , где

public void M() {
    int mint = -1;
    Expression<Func<int>> lambda = () => mint;
}

компилируется в:

[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
    public int mint;
}

public void M()
{
    <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
    <>c__DisplayClass0_.mint = -1;
    Expression.Lambda<Func<int>>(Expression.Field(Expression.Constant(<>c__DisplayClass0_, typeof(<>c__DisplayClass0_0)), FieldInfo.GetFieldFromHandle((RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/)), Array.Empty<ParameterExpression>());
}
...