【问题标题】:Reliably compare type symbols (ITypeSymbol) with Roslyn可靠地将类型符号 (ITypeSymbol) 与 Roslyn 进行比较
【发布时间】:2015-12-12 17:47:58
【问题描述】:

我正在尝试在以下情况下可靠地比较 ITypeSymbol 的两个实例,这是最简单和最直接的方式(我在一个更大的项目中遇到了这些问题,并试图尽可能地简化它):

我已经用这个 SyntaxTree 进行了 CSharpCompilation:

  namespace MyAssembly
  {
    public class Foo
    {
      public Foo(Foo x)
      {
      }
    }
  }

我们正在使用CSharpSyntaxRewriter 遍历树,更改类并更新Compilation。在第一次运行中,我们记住了第一个构造函数参数的ITypeSymbol(在这种情况下是类本身的类型)。 更新编译后,我们再次调用相同的重写器并再次从构造函数参数中获取 ITypeSymbol。 之后,我比较了两个 ITypeSymbol,我希望它们代表相同的类型 MyAssembly.Foo

我的第一个比较方法只是调用 ITypeSymbol.Equals() 方法,但它返回了 false。它基本上返回false,因为我们更改了编译并同时得到了一个新的SemanticModel。如果我们不这样做,Equals() 方法实际上会返回 true。

比较DeclaringSyntaxReferences(这里也提到How to compare type symbols (ITypeSymbol) from different projects in Roslyn?)返回false,因为我们同时更改了Foo 类本身。如果构造函数参数的类型为Bar 并且我们重写Bar,则行为将相同。要验证这一点,只需取消注释该行

//RewriteBar(rewriter, compilation, resultTree); 

并将代码示例中的构造函数参数类型替换为Bar

结论: ITypeSymbol.Equals() 不适用于新的编译和语义模型,比较 DeclaringSyntaxReferences 不适用于我们同时更改的类型。 (我还使用一种外部程序集测试了该行为——在这种情况下,ITypeSymbol.Equals() 对我有用。)

所以我的问题是:

  • 在所述情况下比较类型的预期方法是什么?
  • 是否有一个包罗万象的解决方案,或者我必须有 混合/组合不同的方法来确定类型相等(也许 还采用完全限定名称的字符串表示 考虑)?

这是我可以重现问题的完整测试程序。只需复制、包含 Roslyn 引用并执行:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Demo.TypeSymbol
{
  class Program
  {
    static void Main(string[] args)
    {    
      var compilation = (CSharpCompilation) GetTestCompilation();

      var rewriter = new Rewriter(changeSomething: true);
      var tree = compilation.SyntaxTrees.First(); //first SyntaxTree is the one of class MyAssembly.Foo
      rewriter.Model = compilation.GetSemanticModel (tree);

      //first rewrite run
      var resultTree = rewriter.Visit (tree.GetRoot()).SyntaxTree;
      compilation = UpdateIfNecessary (compilation, rewriter, tree, resultTree);
      rewriter.Model = compilation.GetSemanticModel (resultTree);

      //just for demonstration; comment in to test behaviour when we are rewriting the class Bar -> in this case use Bar as constructor parameter in Foo
      //RewriteBar(rewriter, compilation, resultTree);

      //second rewrite run
      rewriter.Visit (resultTree.GetRoot());

      //now we want to compare the types...

      Console.WriteLine(rewriter.ParameterTypeFirstRun);
      Console.WriteLine(rewriter.ParameterTypeSecondRun);

      //=> types are *not* equal
      var typesAreEqual = rewriter.ParameterTypeFirstRun.Equals (rewriter.ParameterTypeSecondRun);
      Console.WriteLine("typesAreEqual:            " + typesAreEqual);

      //=> syntax references are not equal
      if(rewriter.ParameterTypeFirstRun.DeclaringSyntaxReferences.Any())
      {
        var syntaxReferencesAreEqual =
          rewriter.ParameterTypeFirstRun.DeclaringSyntaxReferences.First()
          .Equals(rewriter.ParameterTypeSecondRun.DeclaringSyntaxReferences.First());
        Console.WriteLine("syntaxReferencesAreEqual: " + syntaxReferencesAreEqual);
      }

      //==> other options??
    }

    private static CSharpCompilation UpdateIfNecessary(CSharpCompilation compilation, Rewriter rewriter, SyntaxTree oldTree, SyntaxTree newTree)
    {
      if (oldTree != newTree)
      {
        //update compilation as the syntaxTree changed
        compilation = compilation.ReplaceSyntaxTree(oldTree, newTree);
        rewriter.Model = compilation.GetSemanticModel(newTree);
      }
      return compilation;
    }

    /// <summary>
    /// rewrites the SyntaxTree of the class Bar, updates the compilation as well as the semantic model of the passed rewriter
    /// </summary>
    private static void RewriteBar(Rewriter rewriter, CSharpCompilation compilation, SyntaxTree firstSyntaxTree)
    {
      var otherRewriter = new Rewriter(true);
      var otherTree = compilation.SyntaxTrees.Last();
      otherRewriter.Model = compilation.GetSemanticModel(otherTree);
      var otherResultTree = otherRewriter.Visit(otherTree.GetRoot()).SyntaxTree;
      compilation = UpdateIfNecessary(compilation, otherRewriter, otherTree, otherResultTree);
      rewriter.Model = compilation.GetSemanticModel(firstSyntaxTree);
    }

    public class Rewriter : CSharpSyntaxRewriter
    {
      public SemanticModel Model { get; set; }
      private bool _firstRun = true;
      private bool _changeSomething;

      public ITypeSymbol ParameterTypeFirstRun { get; set; }
      public ITypeSymbol ParameterTypeSecondRun { get; set; }

      public Rewriter (bool changeSomething)
      {
        _changeSomething = changeSomething;
      }

      public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
      {
        node = (ClassDeclarationSyntax)base.VisitClassDeclaration(node);

        //remember the types of the parameter
        if (_firstRun)
          ParameterTypeFirstRun = GetTypeSymbol (node);
        else
          ParameterTypeSecondRun = GetTypeSymbol (node);

        _firstRun = false;

        //change something and return updated node
        if(_changeSomething)
          node = node.WithMembers(node.Members.Add(GetMethod()));
        return node;
      }

      /// <summary>
      /// Gets the type of the first parameter of the first method
      /// </summary>
      private ITypeSymbol GetTypeSymbol(ClassDeclarationSyntax classDeclaration)
      {
        var members = classDeclaration.Members;
        var methodSymbol = (IMethodSymbol) Model.GetDeclaredSymbol(members[0]);
        return methodSymbol.Parameters[0].Type;
      }

      private MethodDeclarationSyntax GetMethod()
      {
        return (MethodDeclarationSyntax)
          CSharpSyntaxTree.ParseText (@"public void SomeMethod(){ }").GetRoot().ChildNodes().First();
      }
    }

    private static SyntaxTree[] GetTrees()
    {
      var treeList = new List<SyntaxTree>();
      treeList.Add(CSharpSyntaxTree.ParseText(Source.Foo));
      treeList.Add(CSharpSyntaxTree.ParseText(Source.Bar));
      return treeList.ToArray();
    }

    private static Compilation GetTestCompilation()
    {
      var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
      var refs = new List<PortableExecutableReference> { mscorlib };

      // I used this to test it with a reference to an external assembly
      // var testAssembly = MetadataReference.CreateFromFile(@"../../../Demo.TypeSymbol.TestAssembly/bin/Debug/Demo.TypeSymbol.TestAssembly.dll");
      // refs.Add (testAssembly);

      return CSharpCompilation.Create("dummyAssembly", GetTrees(), refs);
    }
  }

  public static class Source
  {
    public static string Foo => @"

      // for test with external assembly
      //using Demo.TypeSymbol.TestAssembly;

      namespace MyAssembly
      {
        public class Foo
        {
          public Foo(Foo x)
          {
          }
        }
      }
    ";

    public static string Bar => @"
      namespace MyAssembly
      {
        public class Bar
        {
          public Bar(int i)
          {
          }       
        }
      }
    ";
  }
}

【问题讨论】:

  • FWIW,我使用符号的.ToString()。但这可能并不理想,取决于您对符号平等的看法。如果您在由两个项目共享的文档中定义了一个类型,Roslyn 不会将它们视为平等,但 .ToString() 方法会。
  • @JoshVarty 谢谢。我基本上考虑过使用字符串表示,但感觉不是正确的方法,我不确定它是否在所有情况下都能正常工作。但也许就是这么简单,要走的路是.ToString()。我只是期望最佳实践会有所不同。

标签: c# .net roslyn roslyn-code-analysis


【解决方案1】:

一种可能性是调用SymbolFinder.FindSimilarSymbols,它会在您的新解决方案中为您提供一个按名称和其他一些属性匹配的符号。从那里你可以在你的新编译中使用 Equals。

【讨论】:

  • 谢谢!到目前为止,这基本上似乎有效,但我想知道这种比较有多准确或“正确”,考虑到 FindSimilarSymbols 可以返回 多个相似 符号。那么理论上它不能也错误地返回一个符号,它实际上等于我的符号,但我没有在寻找它吗?文档说“如果存在不明确的匹配,可能会返回多个符号”,但我不确定在哪些情况下会实际发生这种情况以及该方法在后台如何工作。
  • 在后台,这只是在寻找与旧符号名称相同的东西。因此,如果他们的代码被破坏并且有两个具有完全相同名称的类等,您可能会得到重复。如果它们在源代码中定义的类与元数据中定义的名称完全相同,您也可能会得到重复。跨度>
猜你喜欢
  • 1970-01-01
  • 2023-03-10
  • 2010-12-30
  • 2017-09-07
  • 2012-11-15
  • 1970-01-01
  • 2015-01-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多