用方法替换C#代码中的所有变量

用方法替换C#代码中的所有变量,c#,code-analysis,roslyn,C#,Code Analysis,Roslyn,我需要解析用户编写的C代码片段,并用方法调用替换所有未在本地定义的变量。即 public class Foo { public dynamic Bar() { return Math.Min(x + width, maxWidth); } } 必须成为: public class Foo { public dynamic Bar() { return Math.Min(Resolve("x") + Resolve("width"), Resolve("

我需要解析用户编写的C代码片段,并用方法调用替换所有未在本地定义的变量。即

public class Foo
{
  public dynamic Bar()
  {
     return Math.Min(x + width, maxWidth);
  }
}
必须成为:

public class Foo
{
  public dynamic Bar()
  {
      return Math.Min(Resolve("x") + Resolve("width"), Resolve("maxWidth"));
  }
}
我正在使用Microsoft.CodeAnalysis.CSharp和CSharpSyntaxTree检查字符串,但它没有提供足够的信息来执行替换。如果真的有,我不知道去哪里找。我已经在下面粘贴了SyntaxTree布局。所有变量都作为IdentifierName节点出现,但我不知道如何区分不同的IdentifierName。从这里到哪里去

CompilationUnit[0..99) {
 code:  public class Foo\n{\n  public dynamic Bar()\n  {\n    return Math.Min(x + width, maxWidth);\n  }\n}
 tokens: EndOfFileToken[] 
 nodes{
  ClassDeclaration[0..99) {
   code:  public class Foo\n{\n  public dynamic Bar()\n  {\n    return Math.Min(x + width, maxWidth);\n  }\n}
   tokens: PublicKeyword[public ] ClassKeyword[class ] IdentifierToken[Foo\n] OpenBraceToken[{\n] CloseBraceToken[}] 
   nodes{
    MethodDeclaration[21..98) {
     code:    public dynamic Bar()\n  {\n    return Math.Min(x + width, maxWidth);\n  }\n
     tokens: PublicKeyword[  public ] IdentifierToken[Bar] 
     nodes{
      IdentifierName[30..38) {
       code:  dynamic 
       tokens: IdentifierToken[dynamic ] 
      }
      ParameterList[41..45) {
       code:  ()\n
       tokens: OpenParenToken[(] CloseParenToken[)\n] 
      }
      Block[45..98) {
       code:    {\n    return Math.Min(x + width, maxWidth);\n  }\n
       tokens: OpenBraceToken[  {\n] CloseBraceToken[  }\n] 
       nodes{
        ReturnStatement[50..93) {
         code:      return Math.Min(x + width, maxWidth);\n
         tokens: ReturnKeyword[    return ] SemicolonToken[;\n] 
         nodes{
          InvocationExpression[61..90) {
           code:  Math.Min(x + width, maxWidth)
           nodes{
            SimpleMemberAccessExpression[61..69) {
             code:  Math.Min
             tokens: DotToken[.] 
             nodes{
              IdentifierName[61..65) {
               code:  Math
               tokens: IdentifierToken[Math] 
              }
              IdentifierName[66..69) {
               code:  Min
               tokens: IdentifierToken[Min] 
              }
             }
            }
            ArgumentList[69..90) {
             code:  (x + width, maxWidth)
             tokens: OpenParenToken[(] CommaToken[, ] CloseParenToken[)] 
             nodes{
              Argument[70..79) {
               code:  x + width
               nodes{
                AddExpression[70..79) {
                 code:  x + width
                 tokens: PlusToken[+ ] 
                 nodes{
                  IdentifierName[70..72) {
                   code:  x 
                   tokens: IdentifierToken[x ] 
                  }
                  IdentifierName[74..79) {
                   code:  width
                   tokens: IdentifierToken[width] 
                  }
                 }
                }
               }
              }
              Argument[81..89) {
               code:  maxWidth
               nodes{
                IdentifierName[81..89) {
                 code:  maxWidth
                 tokens: IdentifierToken[maxWidth] 
                }
               }
              }
             }
            }
           }
          }
         }
        }
       }
      }
     }
    }
   }
  }
 }
}

也许您无意中粘贴了CSharpSyntaxTree代码,但没有声明“double width=10.0;”? 如果是这样,您将在CSharpSyntaxTree中获得这些附加声明


您只需扫描树,查找未在用户代码中声明的IdentifierToken,所有这些标记都具有替换变量acces代码到方法调用代码所必须使用的位置。

我认为您需要使用语义模型。下面是一个(非常基本的)示例,演示如何查找未解析的符号:

var tree = CSharpSyntaxTree.ParseFile(fileName);
var root = tree.GetRoot();
var refs = new MetadataReference[]
{
    new MetadataFileReference(@"C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.dll", new MetadataReferenceProperties(MetadataImageKind.Assembly))
};
var compilation = CSharpCompilation.Create("testRoslyn", new[] { tree }, refs);
var model = compilation.GetSemanticModel(tree);

var unknownSymbols =
    from node in root.DescendantNodes()
    where node.IsKind(SyntaxKind.IdentifierName)
    let symbolInfo = model.GetSymbolInfo(node)
    where symbolInfo.Symbol == null && !symbolInfo.CandidateSymbols.Any()
    select node;

从那里,你可以用
Resolve(name)

替换节点。这篇文章并没有真正为你的问题提供解决方案,而是提供了另一种可能更容易实现的方法,但为用户引入了一种改变。我在这里发布的只是一个想法

目标不是允许用户写入
x
,而是
Var.x
Var.maxWidth

然后,在解析C代码时,只需为类型为
CustomDynamicObject
的属性
Var
插入代码(或您想要给出的任何名称)

然后可以定义
CustomDynamicObject
inheriting,以便拦截对未定义方法/属性的所有调用


DynamicObject和使用.NET 4的动态功能只是拦截调用的一种方法,但您可以通过谷歌搜索其他技术。

语法树还不够,您需要查看语义模型以了解标识符表示的内容。您能否提供一个链接,我可以在其中阅读语义模型?现在就查找它。。。我已经有一段时间没看Roslyn了,自从上次这个链接提供了一些信息以来,API已经改变了很多,但是它已经有点过时了。。。谢谢托马斯,过时的Roslyn教程现在几乎是我生存的祸根:)如果我能帮上忙,我宁愿不让用户承担任何责任。这是一个有趣的想法,但最初我想解决它,而不必让用户代码与常规C#代码有所不同。是的,这就是为什么我将其作为“想法”发布的原因。如果您没有找到任何解决问题的方法,则可能是备份解决方案。但你完全正确,用户的压力必须尽可能小。我后来错误地更改了代码,以显示两个已定义的变量和未定义的变量,忘记了代码树将变得无效。我把它改回来了。好吧,一切都很好,但我仍然完全被困在如何替换节点上。关于Microsoft.CodeAnalysis背后的逻辑,我还不明白……你读过常见问题解答了吗?他们在那里讨论替换子表达式:我似乎无法下载SDK预览,它应该包含示例。这个链接把我带到了另一个地方,从那里开始一切都是下坡路。我已经在我的电脑上搜索了faq.cs和我能想到的任何东西,但没有乐趣。@DavidRutten,在你提到的页面上,只需单击“下载”链接不相信我,这些现在都不起作用。但我根据这篇文章中的建议找到了如何使用ReplaceNodes:
public (static?) CustomDynamicObject Var { get { /* create the object once and return */ }}