如何使用Microsoft.CodeAnalysis替换不同类型的NameSyntax节点?
解决Roslyn替换不同类型语法节点的异常问题
问题场景
环境为.NET Framework 4.6.1(不可更改版本),在代码版本转换时,需将属性名LastBarIndexPainted替换为ChartBars.ToIndex,但调用CodeAnalysis.ReplaceNodes()时抛出异常:
Unable to cast object of type 'Microsoft.CodeAnalysis.CSharp.Syntax.QualifiedNameSyntax' to type 'Microsoft.CodeAnalysis.CSharp.Syntax.SimpleNameSyntax'
原因是ReplaceNodes()要求替换前后的语法节点类型必须一致:LastBarIndexPainted属于SimpleNameSyntax类型,而ChartBars.ToIndex是QualifiedNameSyntax类型,直接替换因类型不匹配触发报错。
当前替换字典
private Dictionary<string, string> _renamedProperties = new Dictionary<string, string> { { "CalculateOnBarClose", "Calculate" }, { "LastBarIndexPainted", "ChartBars.ToIndex" }, };
原转换函数
private SyntaxNode ReplacePropertyNames(SyntaxNode node, Dictionary<string, string> propertyNameLookup) { IEnumerable<NameSyntax> propertyIdentifiers = node .DescendantNodes() .OfType<NameSyntax>() .Where(n => propertyNameLookup.ContainsKey(n.ToString())); var replacements = new Dictionary<SyntaxNode, SyntaxNode>(); SyntaxNode newIdentifierName2 = null; foreach (NameSyntax identifierName in propertyIdentifiers) { string newName = propertyNameLookup[identifierName.ToString()]; SyntaxNode newIdentifierName = SyntaxFactory.ParseName(newName).WithTriviaFrom(identifierName); replacements.Add(identifierName, newIdentifierName); newIdentifierName2 = newIdentifierName; } return node.ReplaceNodes(replacements.Keys, (n, _) => replacements[n]); // 抛出异常处 }
解决方案
核心思路是不直接替换NameSyntax节点,而是定位属性名所在的父节点上下文,根据场景替换对应合法的语法节点,修改后的转换函数如下:
private SyntaxNode ReplacePropertyNames(SyntaxNode node, Dictionary<string, string> propertyNameLookup) { // 筛选所有需要替换的SimpleNameSyntax节点 var targetNodes = node .DescendantNodes() .OfType<SimpleNameSyntax>() .Where(s => propertyNameLookup.ContainsKey(s.Identifier.ValueText)); var replacements = new Dictionary<SyntaxNode, SyntaxNode>(); foreach (var simpleName in targetNodes) { string newName = propertyNameLookup[simpleName.Identifier.ValueText]; var newNameSyntax = SyntaxFactory.ParseName(newName).WithTriviaFrom(simpleName); // 根据父节点类型分场景处理 if (simpleName.Parent is MemberAccessExpressionSyntax memberAccess) { // 若属性是成员访问的一部分(如obj.LastBarIndexPainted),替换整个成员访问表达式 var newMemberAccess = memberAccess.WithName(newNameSyntax); replacements.Add(memberAccess, newMemberAccess); } else { // 若属性是单独标识符(如直接使用LastBarIndexPainted),直接替换节点 replacements.Add(simpleName, newNameSyntax); } } return node.ReplaceNodes(replacements.Keys, (original, _) => replacements[original]); }
修改说明
- 缩小目标范围:仅针对
SimpleNameSyntax节点筛选,避免匹配其他类型的NameSyntax。 - 父节点上下文适配:
- 当属性是成员访问表达式的一部分时,替换整个
MemberAccessExpressionSyntax节点,确保语法结构合法。 - 当属性是单独使用的标识符时,直接替换为新的限定名节点。
- 当属性是成员访问表达式的一部分时,替换整个
- 保留格式细节:通过
WithTriviaFrom保留原节点的注释、空格等格式信息。
内容的提问来源于stack exchange,提问作者marcelloptr
相关产品推荐
相关产品推荐

