Reliably compare type characters (ITypeSymbol) with Roslyn

I am trying to reliably compare two instances of ITypeSymbol with the simplest and most direct way in the following situation (I ran into these problems in a larger project and tried to simplify it as much as possible):

I have CSharpCompilation with this SyntaxTree:

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

We walk the tree with CSharpSyntaxRewriter , change the class, and update Compilation . In the first run, we remember ITypeSymbol first constructor parameter (which is the type of the class itself in this case). After updating the compilation, we again call the same rewriting element and again get ITypeSymbol from the constructor parameter. After that, I compare two ITypeSymbols, which I assume are the same type of MyAssembly.Foo .

My first comparison method simply called the ITypeSymbol.Equals() method, but returned it false . It basically returns false because we changed the compilation and got a new SemanticModel in the meantime. If we do not, the Equals () method does return true.

Comparing DeclaringSyntaxReferences (as indicated here How to compare characters of type (ITypeSymbol) from different projects in Roslyn? ) Returns false because we changed the Foo class in this way. The behavior will be the same if the constructor parameter is of type Bar , and we rewrote Bar . To make sure of this, just uncomment the line

 //RewriteBar(rewriter, compilation, resultTree); 

and replace the constructor parameter type with Bar in the sample code.

Conclusion: ITypeSymbol.Equals() does not work with the new compilation and semantic model, and the DeclaringSyntaxReferences comparison DeclaringSyntaxReferences not work with the type that we changed during this time. (I also tested the behavior with the type of the external assembly - in this case ITypeSymbol.Equals () worked for me.

So my questions are:

  • What is the intended way to compare types in the described situation?
  • Is there any single catch solution or still mix / combine different approaches to determining type equality (possibly also taking a string representation of the full name)

This is a complete test program with which the problem reproduces for me. Just copy, enable Roslyn links and do:

 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) { } } } "; } } 
+7
c # roslyn
source share
1 answer

One possibility is to call SymbolFinder.FindSimilarSymbols , which will give you a symbol in your new solution, matching the name and several other properties. From there you can Equals in your new compilation.

+4
source share

All Articles