2016-12-07 106 views
1

背景:Roslyn - 我如何用多個節點替換多個節點?

使用羅斯林用C#,我試圖擴大自動實現的屬性,從而使訪問機構可以通過後處理注入的代碼。我使用StackExchange.Precompilation作爲編譯器鉤子,因此這些語法轉換髮生在構建管道中,而不是作爲分析器或重構的一部分。

我希望把這個:

[SpecialAttribute] 
int AutoImplemented { get; set; } 

成這樣:

[SpecialAttribute] 
int AutoImplemented { 
    get { return _autoImplemented; } 
    set { _autoImplemented = value; } 
} 

private int _autoImplemented; 

問題:

我已經能夠得到簡單的轉換工作,但我被卡在自動屬性,和一些其他一些w類似的其他人AYS。我遇到的麻煩是在替換樹中的多個節點時正確使用SyntaxNodeExtensions.ReplaceNodeSyntaxNodeExtensions.ReplaceNodes擴展方法。

我正在使用延伸CSharpSyntaxRewriter進行轉換的類。我會在這裏分享這個班的相關成員。本課程訪問每個classstruct聲明,然後替換標記爲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語句的方法插入後置條件的檢查工作。這種情況顯然不能依賴任何類型的唯一標識符,如屬性聲明。

+0

你發送給'ReplaceNode'的是誰?你確定在'node'中存在嗎?爲什麼你不訪問jus'rt PropertyDeclarationSyntax'? –

+0

對不起,這應該是'prop',它在我的原始源代碼中被稱爲'm',但我將其更改爲'prop'以便在此處更具可讀性。我沒有訪問屬性聲明的原因是因爲我需要用新的屬性聲明和字段聲明來替換屬性聲明。我不認爲您可以在訪問該節點時用一個節點替換多個節點,您需要在訪問父節點(類型聲明)時執行此操作。 – JamesFaix

+0

我相信這個錯誤是在'Replace'方法的使用中出現的。我從'Replace'得到的結果沒有任何更改。我相信由於複製了不可變的數據結構,我創建了一個與創建'markedProperties'枚舉時不同的樹的引用。當我的樹引用不斷變化時,我無法弄清楚如何做這樣的迭代替換。 – JamesFaix

回答

2

當你做到這一點:

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); 
} 

第一置換後,node(您class例如)不包含原始屬性了。

在Roslyn中,一切都是不可變的,所以第一個替換應該適合你,並且你有一個新的tree \ node。

爲了使它工作,你可以考慮以下之一:

  • 構建結果在你重寫類,不改變原有的樹,當你完成,全部更換一次。在你的情況下,它的意思是立即替換class紙幣。當我想要替換語句時(我在編寫代碼將linq查詢(理解)轉換爲流暢的語法時使用它)時,我認爲它是一個很好的選擇,但是對於所有類,也許它不是最優的。
  • 使用SyntaxAnnotaion \ TrackNodes查找樹已更改後的節點。使用這些選項,您可以根據需要更改樹,並且仍然可以跟蹤新樹中的舊節點。
  • 使用DocumentEditor它可讓您對文檔進行多項更改,然後返回新的文檔。

如果您需要其中一個示例,請告訴我。

+0

我在想你的第一個選項中的建設者。我是否會從空樹開始,然後從原始節點中添加節點(或已處理的節點),直到副本再次成爲完整樹爲止?我以前沒有聽說過'SyntaxAnnotation';看起來很有希望。至於'Document','StackExchange.Precompilation'鉤入編譯器的方式集中在'CSharpSyntaxRewriter'上。會使用'Document'需要更廣泛的範圍嗎? – JamesFaix

+0

我剛剛找到你的博客;非常有用的東西。我剛剛開始潛入Roslyn和Cecil。 – JamesFaix

+0

@JamesFaix謝謝:)你可以看到如何使用我用來轉換LINQ的第一種方法的例子。 SyntaxAnnotions和TrackNodes非常易於使用。我相信你可以在[Josh Varty博客](https://joshvarty.wordpress.com/learn-roslyn-now/)上找到有用的信息。如果您有任何問題,請在此處或在新問題中提問。 –