可靠地将类型符号 (ITypeSymbol) 与 Roslyn 进行比较

Reliably compare type symbols (ITypeSymbol) with Roslyn

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

我有一个 CSharpCompilation 和这个 SyntaxTree:

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

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

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

比较 DeclaringSyntaxReferences(如此处 所述)returns 错误,因为我们同时更改了 class 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)
          {
          }       
        }
      }
    ";
  }
}

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