背景:C#のでロスリンを使用してRoslyn - 複数のノードをそれぞれ複数のノードに置き換えるにはどうすればよいですか?
、私はアクセサ体がその後の処理によって挿入されたコードを持つことができるように、自動実装プロパティを拡張しようとしています。 StackExchange.Precompilationをコンパイラフックとして使用しているため、これらの構文変換はアナライザまたはリファクタリングの一部ではなく、ビルドパイプラインで行われます。
[SpecialAttribute]
int AutoImplemented {
get { return _autoImplemented; }
set { _autoImplemented = value; }
}
private int _autoImplemented;
問題:このに
[SpecialAttribute]
int AutoImplemented { get; set; }
:
私はこれをオンにしたい、私が働いて単純な変換を得ることができた
が、私は、自動プロパティに立ち往生しています。 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
を正しく使用していないと思います。関連する3つの方法があります。
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);
それぞれのプロパティノードをフィールドノードとプロパティノードの両方に置き換える必要があるため、2番目の方法を使用しています。私は多くのノードでこれを行う必要がありますが、1対多のノードの置換を可能にするReplaceNodes
の過負荷はありません。その過負荷の周りに私が見いだした唯一の方法は、非常に「不可欠」で、Roslyn APIの機能的な感じに見える、foreach
ループを使用していました。
このようなバッチ変換を実行するより良い方法はありますか?
更新: 私はロスリンに大きなブログのシリーズを発見し、その不変性に対処します。私はまだ正確な答えを見つけていないが、それは始めるのに適しているようだ。 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'に存在すると確信していますか?なぜあなたは 'PropertyDeclarationSyntax'を訪れていませんか? –
申し訳ありませんが、元のソースコードでは 'prop'と呼ばれていましたが、' m'と呼ばれましたが、ここで読みやすくするために 'prop'に変更しました。私がプロパティ宣言を訪れていないのは、プロパティ宣言を新しいプロパティ宣言とフィールド宣言の両方に置き換える必要があるからです。 1つのノードにアクセスしたときに1つのノードを複数のノードに置き換えることはできないと思います。親ノードにアクセスするときに行う必要があります(型宣言)。 – JamesFaix
私はこのエラーが 'Replace'メソッドの使用にあると信じています。 'Replace'から得た結果には、変更は適用されません。私は、不変のデータ構造をコピーするため、 'markedProperties'列挙を作成したときとは違うツリーへの参照があると思います。私は木の参照が変化し続けるとき、このような繰り返しの置換を行う方法を理解できません。 – JamesFaix