使用羅斯林用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);
我使用第二個,因爲我需要用field和property節點替換每個屬性節點。我需要用很多節點來做到這一點,但是沒有允許一對多節點替換的ReplaceNodes
超載。我發現有超負荷的唯一方法是使用foreach
循環,這看起來非常「緊迫」,並且與Roslyn API的功能性感覺相反。
有沒有更好的方法來執行這樣的批量轉換?
更新: 我發現了一個偉大的博客一系列羅斯林和處理其不變性。我還沒有找到確切的答案,但它看起來像一個開始的好地方。 https://joshvarty.wordpress.com/learn-roslyn-now/
更新: 所以這裏是我真的很困惑。我知道Roslyn API都基於不可變的數據結構,這裏的問題在於如何使用結構的複製來模仿可變性。我認爲問題在於,每次我替換樹中的一個節點時,都會有一棵新樹,所以當我調用ReplaceNode
時,該樹可能不包含我想要替換的原始節點。
我的理解是,在Roslyn中複製樹的方式是,當您替換樹中的節點時,實際上會創建一個引用原始樹的所有相同節點的新樹,但您替換的節點除外所有節點直接在該之上。替換節點下的節點可能會被刪除,如果替換節點不再引用它們,或者可能會添加新引用,但所有舊引用仍指向與之前相同的節點實例。我非常肯定,這正是安德斯海耶斯伯格在羅斯林的this interview中所描述的(20-23分鐘)。
那麼我的新node
實例是否仍然包含與我原始序列中發現的相同的prop
實例?
特殊情況哈克解決方案:
我終於能夠得到轉化財產申報這一特定問題依靠屬性標識符,它不會以任何樹轉換換個工作。但是,我仍然想要一個通用的解決方案來替換多個節點,每個節點都有多個節點。這個解決方案實際上並不是通過API來解決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'的是誰?你確定在'node'中存在嗎?爲什麼你不訪問jus'rt PropertyDeclarationSyntax'? –
對不起,這應該是'prop',它在我的原始源代碼中被稱爲'm',但我將其更改爲'prop'以便在此處更具可讀性。我沒有訪問屬性聲明的原因是因爲我需要用新的屬性聲明和字段聲明來替換屬性聲明。我不認爲您可以在訪問該節點時用一個節點替換多個節點,您需要在訪問父節點(類型聲明)時執行此操作。 – JamesFaix
我相信這個錯誤是在'Replace'方法的使用中出現的。我從'Replace'得到的結果沒有任何更改。我相信由於複製了不可變的數據結構,我創建了一個與創建'markedProperties'枚舉時不同的樹的引用。當我的樹引用不斷變化時,我無法弄清楚如何做這樣的迭代替換。 – JamesFaix