Pull to refresh

Comments 6

Начиная с C# 9 есть встроенный синтаксис для calli - Function Pointers. Его можно использовать в том числе и для вызова managed кода, но в любом случае требуется unsafe контекст.

Было бы интересно послушать про кейсы, в которых использование Reflection.Emit предпочтительнее, чем, например, Expression Trees в контексте генерации одного метода.


Ну и листинг кода лучше бы текстом, а не картинкой...

С одной стороны, это скорее дело вкуса. Многие задачи можно решить как с помощью Expression Trees, так и с помощью DynamicMethod. Я, например, уже настолько привык к последним, что код с il.Emit(...) кажется более простым и понятным, особенно если нормально его написать. Но для кого-то скомпилировать лямбду будет проще, чтобы не сильно разбираться с IL и просто сделать, чтобы работало.

С другой стороны есть несколько причин, когда Expression Trees могут и не подойти:

  • Поскольку Expression Trees внутри себя всё равно используют DynamicMethod, то это всё таки более высокоуровневая штука, которая может не всё уметь. По крайней мере раньше нельзя было нормально сделать цикл, ветвление, объявление переменных (сейчас возможно что-то уже появилось).

  • Только DynamicMethod поддерживает режим skipVisibility, когда игнорируется приватность членов. Это часто используется во всяких мапперах (тот же EF это использует, насколько я помню).

  • Expression Trees, насколько я знаю, нельзя привязать к объекту, как DynamicMethod. Т.е. нельзя сделать делегат с состоянием.

Все это можно сделать. Например (листинг из LINQPad):


void Main()
{
    var f = new Foo { A = 1337 };
    var func = Expression.Lambda<Func<int>>(
        Expression.Property(Expression.Constant(f), "B")
    );
    func.Compile()().Dump();
}

class Foo
{
    public int A {get; set;}
    private int B => A * 2;
}

Тут решаются проблемы пунктов 2 и 3 — можно замкнуть произвольное значение "извне" внутри функции с помощью Expression.Constant (это не обязательно должна быть константа времени компиляции, может быть произвольный объект как в примере выше), и к приватному полю также можно обратиться без проблем.


Чтобы рулить потоком исполнения, есть LoopExpression, SwitchExpression, ConditionalExpression и даже GotoExpression.


А вот что, кажется, нельзя сделать — это написать unsafe-код. Сделать type punning тоже нельзя, хотя это имхо скорее плюс — случайные ошибки гораздо вероятнее чем то что это кому-то действительно нужно.

Круто! Это действительно работает, я не знал, что Expression Trees так умеют.

Я ради интереса проверил, что за код там генерится. Там что-то вроде такого:

ldarg.0
ldfld ...
ldc.i4.0
ldelem.ref
castclass ...
callvirt ...
ret

Я так понимаю, что он все объекты из Expression.Constant хранит в каком-то массиве, откуда их достаёт по индексу и кастит к нужному типу.

На том же динамическом методе это можно сделать проще:

ldarg.0
callvirt ...
ret

Тут уже вопрос в том, насколько вы хотите и любите упарываться с оптимизацией =)

По поводу листинга и картинок. Скажем так, это эксперимент =)

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

  • скрин из IDE с привычной расцветкой синтаксиса будет лучше читаться и восприниматься;

  • Можно сразу показать работу кода в дебаггере с результатом.

Но я учту это на будущее.

Sign up to leave a comment.

Articles