可靠地比较类型符号(ITypeSymbol)和Roslyn

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

我用这个SyntaxTree得到了一个CSharpCompilation:

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

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

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

比较DeclaringSyntaxReferences (这里还指出如何从罗斯林不同项目类型的符号(ITypeSymbol)比较?),因为我们改变了类返回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)
              {
              }       
            }
          }
        ";
      }
    }
    

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

    链接地址: http://www.djcxy.com/p/89187.html

    上一篇: Reliably compare type symbols (ITypeSymbol) with Roslyn

    下一篇: Apply a JavaScript function to all Array elements except the ith element