Что делает метод Lambda Expression Compile()?
Я пытаюсь понять AST на С#. Интересно, что именно делает метод Compile()
из этого примера.
// Some code skipped
Expression<Func<string, int, int, string>> data = Expression.Lambda<Func<string, int, int, string>>(
Expression.Call(s, typeof(string).GetMethod("Substring", new Type[] { typeof(int), typeof(int) }), a, b),
s, a, b
);
Func<string, int, int, string> fun = data.Compile();
Чтобы избежать недоразумений, я понимаю конструкции Expression.Lambda
и Expression.Call
. Меня интересует метод Compile()
. Это как-то порождает настоящую MSIL? Могу ли я увидеть MSIL?
Ответы
Ответ 1
Меня интересует метод Compile()
. Это как-то порождает настоящую MSIL?
Да. Метод Compile запускает посетителя над блоком тела лямбда и генерирует IL динамически для каждого подвыражения.
Если вам интересно узнать, как правильно наплевать IL, см. этот пример "Hello World" , как использовать легкий Codegen. (Я отмечаю, что если вы находитесь в неудачной позиции, связанной с использованием легкого кодека в частично доверенных приложениях, тогда все может стать немного странным в мире с ограниченной видимостью пропуска, см. Шон Фаркас статью по этому вопросу, если это вас интересует.)
Могу ли я увидеть MSIL?
Да, но вам нужен специальный "визуализатор". Визуализатор, который я использовал для отладки Compile()
, в то время как я реализовал свои части, можно скачать здесь:
http://blogs.msdn.com/b/haibo_luo/archive/2005/10/25/484861.aspx
Ответ 2
Выражение представляет структуру данных в виде дерева выражений - используя Compile()
, это дерево выражений может быть скомпилировано в исполняемый код в форме делегата (который является вызовом метода).
После компиляции вы обычно можете вызвать делегата - в вашем примере делегат является Func<string,int,int,string>
. Такой подход может потребоваться, если вы динамически создаете дерево выражений на основе данных, которые доступны только во время выполнения, с конечной целью создания и выполнения соответствующего делегата.
Вы не видите "код" для делегата. Само дерево выражений, на котором оно основано, наиболее близко к этому.
Ответ 3
Ответ на это теперь частично устарел, потому что теперь только иногда что происходит.
Компиляция выражений в IL требует Reflection.Emit, который не доступен все время, особенно с AOT. Поэтому в этих случаях вместо компиляции в IL выражение "компилируется" в список объектов, представляющих команды. Каждая из этих инструкций имеет метод Run
, который заставляет его выполнять соответствующее действие, работая над стеком значений, так как IL работает в стеке. Метод, который вызывает Run
для этих объектов, может быть возвращен в качестве делегата.
Обычно запуск такого делегата происходит медленнее, чем jitting IL, но это единственный вариант, когда компиляция в IL недоступна, а шаг компиляции часто выполняется быстрее, поэтому очень часто общее время компиляции + выполняется меньше с интерпретатора, чем с IL для одноразовых выражений.
По этой причине в .NET Core теперь имеется перегрузка Compile
, которая принимает логическую интерпретацию запроса, даже если компиляция в IL доступна.
Все это делает интересным сочетание языков; Выражения сами по себе - это язык, сборка написана на С#, он может скомпилировать IL, а интерпретируемые объекты команд составляют четвертый язык.