【发布时间】:2017-04-23 01:54:11
【问题描述】:
背景:
将 Roslyn 与 C# 结合使用,我正在尝试扩展自动实现的属性,以便访问器主体可以通过后续处理注入代码。我使用 StackExchange.Precompilation 作为编译器挂钩,因此这些语法转换发生在构建管道中,而不是作为分析器或重构的一部分。
我想转这个:
[SpecialAttribute]
int AutoImplemented { get; set; }
进入这个:
[SpecialAttribute]
int AutoImplemented {
get { return _autoImplemented; }
set { _autoImplemented = value; }
}
private int _autoImplemented;
问题:
我已经能够让简单的转换工作,但我被困在自动属性以及其他一些在某些方面相似的属性上。我遇到的麻烦是在替换树中的多个节点时正确使用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 循环,这似乎非常“必要”并且违背了 Roslyn API 的功能感觉。
有没有更好的方法来执行这样的批量转换?
更新: 我在 Roslyn 上找到了一个很棒的博客系列并处理了它的不变性。我还没有找到确切的答案,但它看起来是一个很好的起点。 https://joshvarty.wordpress.com/learn-roslyn-now/
更新:
所以这是我真的很困惑的地方。我知道 Roslyn API 都是基于不可变数据结构的,这里的问题在于如何使用结构的复制来模拟可变性。我认为问题是每次我替换树中的一个节点时,我都会有一个新树,所以当我调用ReplaceNode 时,该树应该不包含我想要替换的原始节点。
据我了解,在 Roslyn 中复制树的方式是,当您替换树中的一个节点时,您实际上创建了一个新树,该树引用了原始树的所有相同节点,除了您替换的节点和所有直接在那个节点之上的节点。如果替换节点不再引用被替换节点下面的节点,则可以删除它们,或者可以添加新引用,但所有旧引用仍然指向与以前相同的节点实例。我很确定这正是 Anders Hejlsberg 在 Roslyn 的 this interview 中所描述的(20 到 23 分钟)。
所以我的新node 实例不应该仍然包含在我的原始序列中找到的相同prop 实例吗?
特殊情况的解决方案:
我终于能够通过依赖属性标识符来解决转换属性声明的特殊问题,这在任何树转换中都不会改变。但是,我仍然想要一个用多个节点替换多个节点的通用解决方案。这个解决方案实际上是围绕 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 语句。这种情况显然不能依赖任何类型的唯一标识符,例如属性声明。
【问题讨论】:
-
您发送给
ReplaceNode的m是谁?你确定它存在于node中吗?为什么你不访问 jusrtPropertyDeclarationSyntax? -
对不起,这应该是
prop,在我的原始源代码中它被称为m,但我将其更改为prop,以便在此处更具可读性。我不访问属性声明的原因是因为我需要用新的属性声明和字段声明替换属性声明。我不认为您可以在访问一个节点时将多个节点替换为一个节点,您需要在访问父节点时这样做(类型声明)。 -
我相信
Replace方法的使用存在错误。我从Replace得到的结果没有应用任何更改。我相信由于复制了不可变的数据结构,我引用了与创建markedProperties枚举时不同的树。当我的树引用不断变化时,我无法弄清楚如何进行这样的迭代替换。
标签: c# functional-programming abstract-syntax-tree roslyn