如何在没有 "not found to remove" 异常的情况下获取重命名 class 的语义模型
How to get semantic model for renamed class without "not found to remove" exception
这是一个脚本
- 使用语义模型
识别class的类型信息
- 通过替换其标识符标记重命名 class
- 使用其语义模型
识别重命名的 class 的类型信息
- 在重命名的 class 上运行 SyntaxRewriter - 重写器将所有出现的旧类型信息替换为新类型信息。
(我希望)尽可能少:
static void Main(string[] args)
{
// This class needs to be renamed. We want to use Roslyn and symbols,
// because string-based replacement would change things that it's not supposed to.
var someScript = "public class FooBar { public NotFooBar a; FooBar string b; public OtherNamespace.FooBar c; }";
// Minimal Roslyn boilerplate
var compilation = CSharpCompilation.Create("CodeGeneration")
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddSyntaxTrees(CSharpSyntaxTree.ParseText(someScript));
var tree = compilation.SyntaxTrees[0];
var classDeclaration = tree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().Single();
var originalType = compilation.GetSemanticModel(classDeclaration.SyntaxTree).GetDeclaredSymbol(classDeclaration);
var renamedClass = classDeclaration.ReplaceToken(
classDeclaration.Identifier,
SyntaxFactory.Identifier(classDeclaration.Identifier.Text.Replace("Bar", "Baz"))
);
var semanticModel = compilation.GetSemanticModel(renamedClass.SyntaxTree);
var renamedType = semanticModel.GetDeclaredSymbol(renamedClass);
var rewritten = new TestRewriter(semanticModel, originalType, renamedType).Visit(renamedClass);
Console.Write(rewritten.GetText());
}
class TestRewriter : CSharpSyntaxRewriter
{
private readonly SemanticModel _model;
private readonly ITypeSymbol _replace;
private readonly ITypeSymbol _replacement;
public TestRewriter(SemanticModel model, ITypeSymbol replace, ITypeSymbol replacement)
{
_model = model;
_replace = replace;
_replacement = replacement;
}
public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
{
return base.VisitIdentifierName(
_model.GetTypeInfo(node).Type.Equals(_replace)
? node.ReplaceToken(node.Identifier, SyntaxFactory.Identifier(_replacement.ToDisplayString()))
: node
);
}
}
当运行这个程序时,compilation.GetSemanticModel(renamedClass.SyntaxTree)
导致如下异常
System.ArgumentException
: SyntaxTree
public class FooBaz{ public NotFooBar a; FooBar string b; public OtherNamespace.FooBar c; }
not found to remove
如果不是从重命名的 class' 树中获取语义模型,而是使用原始 class' 树中的语义模型,那么 GetTypeInfo(node)
将(如预期的那样) ) 抛出异常,因为 node
不是模型树的一部分。
我错过了什么步骤可以防止 "not found to remove" 异常?
您收到的错误是由于您在 SyntaxTree 中搜索不包含所述元素的元素。
当您调用 classDeclaration.ReplaceToken 时,您实际上并没有替换现有 SyntaxTree 中的标记,而是生成了一个与现有结构无关的新结构。当您尝试从重命名的类中获取语义模型时,您使用的语法树不在编译中,这会导致代码抛出异常。
为了解决这个问题,您可以替换现有CompilationUnit中的节点,然后替换Compilation中的SyntaxTree。但是,您将无法再次找到 ClassDeclaration,因为它变成了一个与现有 "renamedClass" 对象不对应的新对象。
为了跟踪元素,您可以使用 ,它允许您向节点添加信息,以便稍后检索。这可能看起来像这样:
static void ReplaceClassName()
{
// This class needs to be renamed. We want to use Roslyn and symbols,
// because string-based replacement would change things that it's not supposed to.
var someScript = "public class FooBar { public NotFooBar a; FooBar b; public OtherNamespace.FooBar c; }";
// Minimal Roslyn boilerplate
var compilation = CSharpCompilation.Create("CodeGeneration")
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddSyntaxTrees(CSharpSyntaxTree.ParseText(someScript));
var oldSyntaxTree = compilation.SyntaxTrees[0];
var root = oldSyntaxTree.GetRoot();
var classDeclaration = root.DescendantNodes().OfType<ClassDeclarationSyntax>().Single();
var originalType = compilation.GetSemanticModel(classDeclaration.SyntaxTree).GetDeclaredSymbol(classDeclaration);
var renamedClass = classDeclaration.ReplaceToken(
classDeclaration.Identifier,
SyntaxFactory.Identifier(classDeclaration.Identifier.Text.Replace("Bar", "Baz"))
).WithAdditionalAnnotations(new SyntaxAnnotation("ReplacedClass"));
root = root.ReplaceNode(classDeclaration, renamedClass);
compilation = compilation.ReplaceSyntaxTree(oldSyntaxTree, renamedClass.SyntaxTree);
renamedClass = compilation.SyntaxTrees[0].GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault(curr => curr.GetAnnotations("ReplacedClass").Any());
var semanticModel = compilation.GetSemanticModel(compilation.SyntaxTrees[0]);
var renamedType = semanticModel.GetDeclaredSymbol(renamedClass);
var rewritten = new TestRewriter(semanticModel, originalType, renamedType).Visit(renamedClass);
Console.Write(rewritten.GetText());
}
遗憾的是,这并不能真正解决您的问题。虽然您现在有一个新的 SemanticModel 可用,但您将无法像在 SyntaxRewriter 中那样比较两个不同模型的 TypeInfo。
为了保留您的类型信息,我建议您使用 SyntaxRewriter 更改 IdentifierName 和 ClassDeclarationSyntax,因为 SyntaxRewriter 将首先访问节点,然后才替换它们。这可以按如下方式实现:
static void ReplaceClassName()
{
// This class needs to be renamed. We want to use Roslyn and symbols,
// because string-based replacement would change things that it's not supposed to.
var someScript = "public class FooBar { public NotFooBar a; FooBar b; public OtherNamespace.FooBar c; }";
// Minimal Roslyn boilerplate
var compilation = CSharpCompilation.Create("CodeGeneration")
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddSyntaxTrees(CSharpSyntaxTree.ParseText(someScript));
var oldSyntaxTree = compilation.SyntaxTrees[0];
var root = oldSyntaxTree.GetRoot();
var classDeclaration = root.DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
var semanticModel = compilation.GetSemanticModel(classDeclaration.SyntaxTree);
var originalType = semanticModel.GetDeclaredSymbol(classDeclaration);
var rewritten = new TestRewriter(semanticModel, originalType).Visit(root);
Console.Write(rewritten.GetText());
}
class TestRewriter : CSharpSyntaxRewriter
{
private readonly SemanticModel _model;
private readonly ITypeSymbol _replace;
public TestRewriter(SemanticModel model, ITypeSymbol replace)
{
_model = model;
_replace = replace;
}
public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
{
node = base.VisitIdentifierName(node) as IdentifierNameSyntax;
if (_model.GetTypeInfo(node).Type.Equals(_replace))
{
var oldNode = node;
node = node.ReplaceToken(
node.Identifier,
SyntaxFactory.Identifier(node.Identifier.Text.Replace("Bar", "Baz"))
).WithLeadingTrivia(oldNode.GetLeadingTrivia()).WithTrailingTrivia(oldNode.GetTrailingTrivia());
}
return node;
}
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
{
var oldNode = node;
node = base.VisitClassDeclaration(node) as ClassDeclarationSyntax;
if (_model.GetDeclaredSymbol(oldNode).Equals(_replace))
{
return node.WithIdentifier(SyntaxFactory.Identifier(node.Identifier.Text.Replace("Bar", "Baz")));
}
return node;
}
}
如果您确实有可用的解决方案,您可以使用 Renamer class,它应该会为您完成所有工作。
这是一个脚本
- 使用语义模型 识别class的类型信息
- 通过替换其标识符标记重命名 class
- 使用其语义模型 识别重命名的 class 的类型信息
- 在重命名的 class 上运行 SyntaxRewriter - 重写器将所有出现的旧类型信息替换为新类型信息。
(我希望)尽可能少:
static void Main(string[] args)
{
// This class needs to be renamed. We want to use Roslyn and symbols,
// because string-based replacement would change things that it's not supposed to.
var someScript = "public class FooBar { public NotFooBar a; FooBar string b; public OtherNamespace.FooBar c; }";
// Minimal Roslyn boilerplate
var compilation = CSharpCompilation.Create("CodeGeneration")
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddSyntaxTrees(CSharpSyntaxTree.ParseText(someScript));
var tree = compilation.SyntaxTrees[0];
var classDeclaration = tree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().Single();
var originalType = compilation.GetSemanticModel(classDeclaration.SyntaxTree).GetDeclaredSymbol(classDeclaration);
var renamedClass = classDeclaration.ReplaceToken(
classDeclaration.Identifier,
SyntaxFactory.Identifier(classDeclaration.Identifier.Text.Replace("Bar", "Baz"))
);
var semanticModel = compilation.GetSemanticModel(renamedClass.SyntaxTree);
var renamedType = semanticModel.GetDeclaredSymbol(renamedClass);
var rewritten = new TestRewriter(semanticModel, originalType, renamedType).Visit(renamedClass);
Console.Write(rewritten.GetText());
}
class TestRewriter : CSharpSyntaxRewriter
{
private readonly SemanticModel _model;
private readonly ITypeSymbol _replace;
private readonly ITypeSymbol _replacement;
public TestRewriter(SemanticModel model, ITypeSymbol replace, ITypeSymbol replacement)
{
_model = model;
_replace = replace;
_replacement = replacement;
}
public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
{
return base.VisitIdentifierName(
_model.GetTypeInfo(node).Type.Equals(_replace)
? node.ReplaceToken(node.Identifier, SyntaxFactory.Identifier(_replacement.ToDisplayString()))
: node
);
}
}
当运行这个程序时,compilation.GetSemanticModel(renamedClass.SyntaxTree)
导致如下异常
System.ArgumentException
:SyntaxTree
public class FooBaz{ public NotFooBar a; FooBar string b; public OtherNamespace.FooBar c; }
not found to remove
如果不是从重命名的 class' 树中获取语义模型,而是使用原始 class' 树中的语义模型,那么 GetTypeInfo(node)
将(如预期的那样) ) 抛出异常,因为 node
不是模型树的一部分。
我错过了什么步骤可以防止 "not found to remove" 异常?
您收到的错误是由于您在 SyntaxTree 中搜索不包含所述元素的元素。
当您调用 classDeclaration.ReplaceToken 时,您实际上并没有替换现有 SyntaxTree 中的标记,而是生成了一个与现有结构无关的新结构。当您尝试从重命名的类中获取语义模型时,您使用的语法树不在编译中,这会导致代码抛出异常。
为了解决这个问题,您可以替换现有CompilationUnit中的节点,然后替换Compilation中的SyntaxTree。但是,您将无法再次找到 ClassDeclaration,因为它变成了一个与现有 "renamedClass" 对象不对应的新对象。
为了跟踪元素,您可以使用
static void ReplaceClassName()
{
// This class needs to be renamed. We want to use Roslyn and symbols,
// because string-based replacement would change things that it's not supposed to.
var someScript = "public class FooBar { public NotFooBar a; FooBar b; public OtherNamespace.FooBar c; }";
// Minimal Roslyn boilerplate
var compilation = CSharpCompilation.Create("CodeGeneration")
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddSyntaxTrees(CSharpSyntaxTree.ParseText(someScript));
var oldSyntaxTree = compilation.SyntaxTrees[0];
var root = oldSyntaxTree.GetRoot();
var classDeclaration = root.DescendantNodes().OfType<ClassDeclarationSyntax>().Single();
var originalType = compilation.GetSemanticModel(classDeclaration.SyntaxTree).GetDeclaredSymbol(classDeclaration);
var renamedClass = classDeclaration.ReplaceToken(
classDeclaration.Identifier,
SyntaxFactory.Identifier(classDeclaration.Identifier.Text.Replace("Bar", "Baz"))
).WithAdditionalAnnotations(new SyntaxAnnotation("ReplacedClass"));
root = root.ReplaceNode(classDeclaration, renamedClass);
compilation = compilation.ReplaceSyntaxTree(oldSyntaxTree, renamedClass.SyntaxTree);
renamedClass = compilation.SyntaxTrees[0].GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault(curr => curr.GetAnnotations("ReplacedClass").Any());
var semanticModel = compilation.GetSemanticModel(compilation.SyntaxTrees[0]);
var renamedType = semanticModel.GetDeclaredSymbol(renamedClass);
var rewritten = new TestRewriter(semanticModel, originalType, renamedType).Visit(renamedClass);
Console.Write(rewritten.GetText());
}
遗憾的是,这并不能真正解决您的问题。虽然您现在有一个新的 SemanticModel 可用,但您将无法像在 SyntaxRewriter 中那样比较两个不同模型的 TypeInfo。
为了保留您的类型信息,我建议您使用 SyntaxRewriter 更改 IdentifierName 和 ClassDeclarationSyntax,因为 SyntaxRewriter 将首先访问节点,然后才替换它们。这可以按如下方式实现:
static void ReplaceClassName()
{
// This class needs to be renamed. We want to use Roslyn and symbols,
// because string-based replacement would change things that it's not supposed to.
var someScript = "public class FooBar { public NotFooBar a; FooBar b; public OtherNamespace.FooBar c; }";
// Minimal Roslyn boilerplate
var compilation = CSharpCompilation.Create("CodeGeneration")
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddSyntaxTrees(CSharpSyntaxTree.ParseText(someScript));
var oldSyntaxTree = compilation.SyntaxTrees[0];
var root = oldSyntaxTree.GetRoot();
var classDeclaration = root.DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
var semanticModel = compilation.GetSemanticModel(classDeclaration.SyntaxTree);
var originalType = semanticModel.GetDeclaredSymbol(classDeclaration);
var rewritten = new TestRewriter(semanticModel, originalType).Visit(root);
Console.Write(rewritten.GetText());
}
class TestRewriter : CSharpSyntaxRewriter
{
private readonly SemanticModel _model;
private readonly ITypeSymbol _replace;
public TestRewriter(SemanticModel model, ITypeSymbol replace)
{
_model = model;
_replace = replace;
}
public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
{
node = base.VisitIdentifierName(node) as IdentifierNameSyntax;
if (_model.GetTypeInfo(node).Type.Equals(_replace))
{
var oldNode = node;
node = node.ReplaceToken(
node.Identifier,
SyntaxFactory.Identifier(node.Identifier.Text.Replace("Bar", "Baz"))
).WithLeadingTrivia(oldNode.GetLeadingTrivia()).WithTrailingTrivia(oldNode.GetTrailingTrivia());
}
return node;
}
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
{
var oldNode = node;
node = base.VisitClassDeclaration(node) as ClassDeclarationSyntax;
if (_model.GetDeclaredSymbol(oldNode).Equals(_replace))
{
return node.WithIdentifier(SyntaxFactory.Identifier(node.Identifier.Text.Replace("Bar", "Baz")));
}
return node;
}
}
如果您确实有可用的解决方案,您可以使用 Renamer class,它应该会为您完成所有工作。