Немного странноватая фича. Выглядит как инструкция — в теле метода в такой-то строке подменить вызов метода. Для каждого вида изменений в коде по отдельной такой фиче что-ли делать будут. Не проще ли было разрешить в SG подменять все тело метода (все определение).
Более универсальный подход бы был такой:
Если метод помечен определенным атрибутом, то он становится dummy, не попадает в сборку(отбрасывается на последнем этапе кодогенерации). SG на основе этого dummy генерируют настоящий код метода.
Этому dummy методу можно было бы и вручную дать другое имя(чтобы не совпадало с тем что создаст SG), например приписать "_". Хотя лучше бы сам атрибут на методе переименовывал нормальное имя в dummy.
Они раньше отказались от трюков с подменами кода — сказали сложно контролировать побочные эффекты — когда каждый "макрос" создает свои изменения, трудно контролировать порядок вызовов "макросов". Но описанным выше способом никаких проблем ведь не будет.
[MyGenerator]void MyMethod1{/*...*/}
Нужна только возможность объявить свой атрибут MyGenerator, чтобы он на ранней стадии компиляции переименовывал метод в MyMethod1__ItIsDummyName, и чтобы этот метод не попал в assembly, поэтому запрещал ссылки на него. Остальное бы писатели SG получше сделали и без таких костылей как interceptors.
Оно и сейчас без interceptors все прекрасно подменяется, только dummy мусор остается в assembly и неудобно вручную имена маркировать.
Здравствуйте, Silver_S, Вы писали:
S_S>Оно и сейчас без interceptors все прекрасно подменяется, только dummy мусор остается в assembly и неудобно вручную имена маркировать.
Ну и сейчас можно помечать метод через partial но без генерации.
Данная фича как раз нужна когда нужно уже действующий код изменить для каких то сценариев. То есть код он не мусор он рабочий, но для каких то задач его нужно подменить в определенном месте.
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, Silver_S, Вы писали:
S>>> Теперь и существующий код можно подменять! S>>>https://github.com/dotnet/roslyn/blob/main/docs/features/interceptors.md
S_S>>Немного странноватая фича.
IT>Ага, не ппонятно какие тут могут быть сценарии использования.
Например compiletime кодогенерация вместо разбора Expression Tree в рантайме.
Здравствуйте, Serginio1, Вы писали:
S> Ну и сейчас можно помечать метод через partial но без генерации.
partial — это способ описать метод без тела, если оно не нужно, чтобы компилятор не ругался.
S>Данная фича как раз нужна когда нужно уже действующий код изменить для каких то сценариев.
Проще код целиком менять в SG (все тело метода), а не такими interceptor инструкциями. Т.к. есть сотни вариантов — что может понадобится изменить, а не только вызовы методов.
Эта фича ведь только для писателей SG нужна(в compile time)? Или есть сценарии использования не в SG, т.е. в текст программы прямо вручную вставлять эти interceptor? Для такого использования будет не надежно — вставишь пустую строку, номера строк изменятся и interceptor промахнется, не то заменит, будешь долго искать баг.
S>То есть код он не мусор он рабочий, но для каких то задач его нужно подменить в определенном месте.
Я под мусором имел ввиду старую версию метода, до изменений.
Я сейчас так подменяю рабочий код:
//Тело этого метода будет изменено через SG, но он останется в assembly как мусор:
[MyGenerator] void MyMethod1_(){/*...*/}
//Генератор изменяет тело MyMethod1_, и добавляет его как новый метод, под тем же именем но без '_'void MyMethod1(){/*...*/}
Здесь тоже код рабочий, если убрать атрибут [MyGenerator] и символ '_' после имени, то будет работать старая версия метода.
S>>То есть код он не мусор он рабочий, но для каких то задач его нужно подменить в определенном месте.
S_S>Я под мусором имел ввиду старую версию метода, до изменений. S_S>Я сейчас так подменяю рабочий код:
S_S>
S_S>//Тело этого метода будет изменено через SG, но он останется в assembly как мусор:
S_S>[MyGenerator] void MyMethod1_(){/*...*/}
S_S>//Генератор изменяет тело MyMethod1_, и добавляет его как новый метод, под тем же именем но без '_'
S_S>void MyMethod1(){/*...*/}
S_S>
S_S>Здесь тоже код рабочий, если убрать атрибут [MyGenerator] и символ '_' после имени, то будет работать старая версия метода.
А partial void MyMethod1 без подчеркивания не надо объявлять?
interceptor это подмена вызова. Мы подменяем вызов. А вызов как раз и может помечаться атрибутом.
То есть реально может быть несколько вариантов вызова, одного и того же названия метода.
Здравствуйте, gandjustas, Вы писали: G>Например compiletime кодогенерация вместо разбора Expression Tree в рантайме.
Пытаюсь сообразить, как это применять. Пока что-то не выходит.
Если я правильно понял, то изменить типы нельзя.
Если в исходном коде написано:
var q = q.Where(e=>e.Name == "Peter"); // binds to Queryable.Where(q, e=>e.Name == "Peter")
То есть — разбора дерева может и не будет, но построение ET по-прежнему будет выполнено.
Опять же, не вполне ясно, можно ли заменять не "обычные" вызовы, а linq comprehensions.
Ну, вот в моём любимом linq2d как раз разбор и порождение кода трансформации занимает существенное время.
Но как использовать сию фичу для того, чтобы это обойти — ума не приложу. Оба этапа мне непонятны:
1. Как мне получить нужный Expression tree? В рантайме я работаю с экземплярами LambdaExpression. А в компайл-тайме?
2. Ну, допустим, я сумел разобраться и найти в проекте все места где вызываются все из сотен перегрузок нужных мне методов. Я сгенерировал нужный мне код метода Transform().
Что на что я подменю в этом коде?
var r = (from s in sample
from r in Result.InitWith(0)
select s + r[-1, 0]).ToArray();
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
S_S>>//Тело этого метода будет изменено через SG, но он останется в assembly как мусор:
S_S>>[MyGenerator] void MyMethod1_(){/*...*/}
S_S>>//Генератор изменяет тело MyMethod1_, и добавляет его как новый метод, под тем же именем но без '_'
S_S>>void MyMethod1(){/*...*/}
S_S>>
S>А partial void MyMethod1 без подчеркивания не надо объявлять?
Здесь partial не обязателен. Оно в студии так работает:
Как только к методу приписывается атрибут [MyGenerator], мгновенно без ручной перекомпиляции (в редакторе свой компилятор) появляется сгенерированный метод без подчеркивания(в автоматическом файле, который контролирует сама студия). Этот метод появляется и в intellisense и из кода на него можно сразу ссылаться. Работает прозрачно, для юзера как замена метода на лету. Если генератору не нужно тело метода, то придется атрибут ставить на partial заголовок метода без тела.
Хотя были косяки после некоторых обновлений студии (или Resharper). В редакторе что-то отваливалось — сгенерированные методы и property показывались красным, что их нету, хотя все компилируется и запускается. Но MS фиксили косяки.
S> interceptor это подмена вызова. Мы подменяем вызов. А вызов как раз и может помечаться атрибутом. S>То есть реально может быть несколько вариантов вызова, одного и того же названия метода.
S>Как правильно подметил gandjustas S>
S>Например compiletime кодогенерация вместо разбора Expression Tree в рантайме.
Тут конкретные сценарии бы рассмотреть.
Допустим, пользователь пометил метод атрибутом, чтобы генератор в его теле заменил вызовы какие надо. Для писателя генератора есть 2 варианта как это сделать:
1) Само тело метода не трогать. Сгенерировать код interceptor. И внутренние механизмы компилятора по этому коду interceptor проведут замены.
2) Самостоятельно сгенерировать новый код, заменить в нем все что надо (не только вызовы). И подменить старый код новым (все тело метода).
Не понятны сценарии когда через interceptor это удобнее. (Если для пользователя компилятора результат одинаковый, то вопрос только про удобство для писателей генераторов.)
Х.з., может когда во всем solution надо подменить все вызовы какого-то метода. Генератор находит все что подменить, а внутрениие механизмы interceptor как-то эффективнее это сделают.
Такой interceptor слишком частный случай. Доработали бы более общий механизм — замены всего кода метода. Тем более, что это уже работает, осталось только мелкие недостатки устранить.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, gandjustas, Вы писали: G>>Например compiletime кодогенерация вместо разбора Expression Tree в рантайме. S>Пытаюсь сообразить, как это применять. Пока что-то не выходит.
Я предполагаю рассматривается сценарий аля compiled query
Как это сейчас (из документации)
private static readonly Func<BloggingContext, int, IAsyncEnumerable<Blog>> _compiledQuery
= EF.CompileAsyncQuery(
(BloggingContext context, int length) => context.Blogs.Where(b => b.Url.StartsWith("http://") && b.Url.Length == length));
await foreach (var blog in _compiledQuery(context, 8))
{
// Do something with the results
}
Естественно никакой AOT с этим не сработает, так как метаданных в рантайме нет и ET не построится
С генераторами и интерсепторами можно переписать так:
[CompiledQuery]
public static IAsyncEnumerable<Blog> GetBlogs(this BloggingContext context, int length)
{
var query = from b in context.Blogs
where b.Url.StartsWith("http://")
where b.Url.Length == length
select b;
return query.AsAsyncEnumerable()
}
await foreach (var blog in context.GetBlogs(8))
{
// Do something with the results
}
Генератор соберет нужный метод-инерсептор и подставить его вместо вызова GetBlogs. Тогда с этим прекрасно сможет работать AOT.
S>Если я правильно понял, то изменить типы нельзя.
Нельзя
S>Если в исходном коде написано: S>
S>То есть — разбора дерева может и не будет, но построение ET по-прежнему будет выполнено.
Думаю да, построение будет выполнено. Но на этом уровне вряд ли будут использоваться интерсепторы в таком виде.
S>Опять же, не вполне ясно, можно ли заменять не "обычные" вызовы, а linq comprehensions.
не похоже
S>Что на что я подменю в этом коде? S>
S>var r = (from s in sample
S> from r in Result.InitWith(0)
S> select s + r[-1, 0]).ToArray();
S>
В данном примере надо подменять .ToArray() на ToArrayGUID(), в котором сгенерировать код для linq выражения, идущего ранее.
Но, имхо, лучше сделать по принципу как для compiled query
Здравствуйте, gandjustas, Вы писали:
G>С генераторами и интерсепторами можно переписать так: G>
G>[CompiledQuery]
G>public static IAsyncEnumerable<Blog> GetBlogs(this BloggingContext context, int length)
G>
G>Генератор соберет нужный метод-инерсептор и подставить его вместо вызова GetBlogs. Тогда с этим прекрасно сможет работать AOT.
Только непонятно — зачем здесь перехватывать вызовы старого GetBlogs и перенаправлять, если можно напрямую вызывать сразу правильный метод GetBlogs созданный генератором.
Тем более что генераторы создают методы даже не в compile-time, а раньше, в edit-time, как только навесишь атрибут сразу появится исправленный метод.
Здравствуйте, Silver_S, Вы писали:
S_S>Тем более что генераторы создают методы даже не в compile-time, а раньше, в edit-time, как только навесишь атрибут сразу появится исправленный метод.
Это магия VS, а не компилятора, увы.
Сам компилятор получает на вход текст и на выходе дает IL.