фон:Roslyn - Как я могу заменить несколько узлов на несколько узлов?
Использования Рослин с C#, я пытаюсь расширить авто Реализуемого свойства, так что аксессоры тело может иметь код впрыскивается путем последующей обработки. Я использую StackExchange.Precompilation как компилятор, поэтому эти синтаксические преобразования происходят в конвейере сборки, а не как часть анализатора или рефакторинга.
Я хочу, чтобы превратить это:
[SpecialAttribute]
int AutoImplemented { get; set; }
в этом:
[SpecialAttribute]
int AutoImplemented {
get { return _autoImplemented; }
set { _autoImplemented = value; }
}
private int _autoImplemented;
Проблема:
Я был в состоянии получить простые преобразования рабочих, но Я застрял на авто-свойствах, а некоторые другие, похожие на некоторые w AYS. Проблема, с которой я столкнулась, заключается в правильном использовании методов расширения SyntaxNodeExtensions.ReplaceNode
и SyntaxNodeExtensions.ReplaceNodes
при замене нескольких узлов на дерево.
Я использую класс, расширяющий CSharpSyntaxRewriter
для преобразований. Я просто расскажу о соответствующих членах этого класса. Этот класс посещает каждое объявление class
и struct
, а затем заменяет любые объявления свойств, которые помечены как SpecialAttribute
.
private readonly SemanticModel model;
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) {
if (node == null) throw new ArgumentNullException(nameof(node));
node = VisitMembers(node);
return base.VisitClassDeclaration(node);
}
public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node) {
if (node == null) throw new ArgumentNullException(nameof(node));
node = VisitMembers(node);
return base.VisitStructDeclaration(node);
}
private TNode VisitMembers<TNode>(TNode node)
where TNode : SyntaxNode {
IEnumerable<PropertyDeclarationSyntax> markedProperties =
node.DescendantNodes()
.OfType<PropertyDeclarationSyntax>()
.Where(prop => prop.HasAttribute<SpecialAttribute>(model));
foreach (var prop in markedProperties) {
SyntaxList<SyntaxNode> expanded = ExpandProperty(prop);
//If I set a breakpoint here, I can see that 'expanded' will hold the correct value.
//ReplaceNode appears to not be replacing anything
node = node.ReplaceNode(prop, expanded);
}
return node;
}
private SyntaxList<SyntaxNode> ExpandProperty(PropertyDeclarationSyntax node) {
//Generates list of new syntax elements from original.
//This method will produce correct output.
}
HasAttribute<TAttribute>
является метод расширения я определил для PropertyDeclarationSyntax
, который проверяет, если это свойство имеет атрибут данного типа. Этот метод работает правильно.
Я считаю, что я просто не использую ReplaceNode
правильно. Есть три родственные методы:
TRoot ReplaceNode<TRoot>(
TRoot root,
SyntaxNode oldNode,
SyntaxNode newNode);
TRoot ReplaceNode<TRoot>(
TRoot root,
SyntaxNode oldNode,
IEnumerable<SyntaxNode> newNodes);
TRoot ReplaceNodes<TRoot, TNode>(
TRoot root,
IEnumerable<TNode> nodes,
Func<TNode, TNode, SyntaxNode> computeReplacementNode);
Я использую второй, потому что мне нужно заменить каждый узел свойств с обеих полей и свойств узлов. Мне нужно сделать это со многими узлами, но нет перегрузки ReplaceNodes
, что позволяет заменять узел «один ко многим». Единственный способ, с которым я столкнулся в этой перегрузке, - использовать цикл foreach
, который кажется очень «императивным» и против функционального ощущения API Roslyn.
Есть ли лучший способ выполнять пакетные преобразования, подобные этому?
Update: Я нашел большой серии блога на Рослин и дело с его неизменности. Я еще не нашел точного ответа, но это похоже на хорошее место для начала. https://joshvarty.wordpress.com/learn-roslyn-now/
Update: Так вот где я действительно путают. Я знаю, что API Roslyn основан на неизменяемых структурах данных, и проблема здесь заключается в тонкости того, как копирование структур используется для имитации изменчивости.Я думаю, проблема в том, что каждый раз, когда я заменяю узел в моем дереве, у меня тогда есть новое дерево, и поэтому, когда я вызываю ReplaceNode
, это дерево якобы не содержит мой первоначальный узел, который я хочу заменить.
Я понимаю, что способ копирования деревьев в Roslyn заключается в том, что при замене узла в дереве вы фактически создаете новое дерево, которое ссылается на все те же узлы исходного дерева, за исключением узла, который вы заменили, и все узлы непосредственно над этим. Узлы под замененным узлом могут быть удалены, если замещающий узел больше не ссылается на них, или новые ссылки могут быть добавлены, но все старые ссылки по-прежнему указывают на те же экземпляры узлов, что и раньше. Я вполне уверен, что это именно то, что Андерс Хейлсберг описывает в this interview на Roslyn (от 20 до 23 минут).
Не должен ли мой новый экземпляр node
содержать те же prop
экземпляры, которые были найдены в моей первоначальной последовательности?
Hacky решение для особых случаев:
я, наконец, смог получить эту конкретную проблему трансформации декларации собственности на работу, опираясь на идентификаторы собственности, которые не будут меняться в любом дереве преобразований. Тем не менее, мне все равно понравится общее решение для замены нескольких узлов на несколько узлов. Это решение действительно работает над API, а не через него.
Вот частный случай решения:
private TNode VisitMembers<TNode>(TNode node)
where TNode : SyntaxNode {
IEnumerable<PropertyDeclarationSyntax> markedPropertyNames =
node.DescendantNodes()
.OfType<PropertyDeclarationSyntax>()
.Where(prop => prop.HasAttribute<SpecialAttribute>(model))
.Select(prop => prop.Identifier.ValueText);
foreach (var prop in markedPropertyNames) {
var oldProp = node.DescendantNodes()
.OfType<PropertyDeclarationSyntax>()
.Single(p => p.Identifier.ValueText == prop.Name);
SyntaxList<SyntaxNode> newProp = ExpandProperty(oldProp);
node = node.ReplaceNode(oldProp, newProp);
}
return node;
}
Другая подобная проблема я работаю с модифицирует все return
заявления в способе вставки Постусловие проверки. Этот случай не может, очевидно, полагаться на любой уникальный идентификатор, такой как объявление свойства.
Кто является 'm', который вы отправляете на' ReplaceNode'? Вы уверены, что существует в 'node'? И почему вы не посещаете jusrt 'PropertyDeclarationSyntax'? –
К сожалению, это должно было быть 'prop', оно было названо' m' в моем исходном коде, но я изменил его на 'prop', чтобы быть более читаемым здесь. Причина, по которой я не посещаю объявления свойств, заключается в том, что мне нужно заменить объявления свойств как новым объявлением свойства, так и объявлением поля. Я не думаю, что вы можете заменить несколько узлов на один узел при посещении этого одного узла, вам нужно сделать это при посещении родительского узла (объявление типа). – JamesFaix
Я считаю, что ошибка в использовании методов «Заменить». Результат, полученный из 'Replace', не имеет никаких изменений. Я полагаю, что из-за копирования неизменяемых структур данных у меня есть ссылка на другое дерево, чем когда я создал перечисление «помеченные объекты». Я не могу понять, как сделать повторные замены таким образом, когда моя ссылка на дерево продолжает меняться. – JamesFaix