可靠地将类型符号 (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() 对我有用。)
所以我的问题是:
- 在所述情况下比较类型的预期方法是什么?
- 是否有一个包罗万象的解决方案,或者我必须
mix/combine 确定类型相等性的不同方法(也许
还采用完全限定名称的字符串表示形式
考虑在内)?
这是我可以重现问题的完整测试程序。只需复制,包括 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。
我试图可靠地比较 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
(如此处 Foo
本身。如果构造函数参数的类型为 Bar
并且我们重写了 Bar
,则行为将相同。要验证这一点,只需取消注释行
//RewriteBar(rewriter, compilation, resultTree);
并将代码示例中的构造函数参数类型替换为Bar
。
结论:
ITypeSymbol.Equals()
不适用于新的编译和语义模型,比较 DeclaringSyntaxReferences
不适用于我们同时更改的类型。
(我还用一种外部程序集测试了行为——在这种情况下 ITypeSymbol.Equals() 对我有用。)
所以我的问题是:
- 在所述情况下比较类型的预期方法是什么?
- 是否有一个包罗万象的解决方案,或者我必须 mix/combine 确定类型相等性的不同方法(也许 还采用完全限定名称的字符串表示形式 考虑在内)?
这是我可以重现问题的完整测试程序。只需复制,包括 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。