如何在Roslyn中获取无显式类型声明lambda的MemberAccess的TypeInfo
问题描述
例如我有如下代码:
using System.Linq; namespace MyNamespace { class Person { public string Name { get; set; } } class Program { static void Main() { string name = new Person().Name; // SemanticModel.GetTypeInfo() 返回string,结果符合预期 string[] names = new Person[] { new Person() }.Select(p => p.Name).ToArray(); // SemanticModel.GetTypeInfo() 返回ErrorTypeSymbol names = new Person[] { new Person() }.Select(Person p => p.Name).ToArray(); // SemanticModel.GetTypeInfo() 返回string,结果符合预期 } } }
我想要获取LINQ Select扩展方法的lambda表达式中p.Name对应的TypeInfo。我在自己的Roslyn分析器中编写了如下代码:
syntaxTree.GetRoot().DescendantNodes().OfType<MemberAccessExpressionSyntax>().ToArray();
执行后得到如下结果:
[0] = {MemberAccessExpressionSyntax} new Person().Name [1] = {MemberAccessExpressionSyntax} new Person[] { new Person() }.Select(p => p.Name).ToArray [2] = {MemberAccessExpressionSyntax} new Person[] { new Person() }.Select [3] = {MemberAccessExpressionSyntax} p.Name
随后我尝试获取第一个MemberAccessExpressionSyntax new Person().Name的TypeInfo:
context.Compilation.GetSemanticModel(syntaxTree).GetTypeInfo(syntaxTree.GetRoot().DescendantNodes().OfType<MemberAccessExpressionSyntax>().ToArray()[0]).Type.Name
执行后返回String,结果符合预期。
但当我尝试获取Select(p => p.Name)的lambda表达式中的MemberAccessExpression节点的类型时:
context.Compilation.GetSemanticModel(syntaxTree).GetTypeInfo(syntaxTree.GetRoot().DescendantNodes().OfType<MemberAccessExpressionSyntax>().ToArray()[3]).Type.Name
返回的是空字符串。
请问如何从SemanticModel中获取无显式类型声明的lambda表达式中的MemberAccessExpressionSyntax对应的TypeInfo?
解答
问题原因
隐式类型lambda(参数未显式声明类型)的参数类型需要依赖其绑定的目标委托类型推导。你直接访问lambda内部的p.Name节点时,语义模型尚未完成从上层Select方法调用到lambda的绑定推导,所以无法得到正确的类型信息,返回空或ErrorTypeSymbol。
解决方法
你需要先绑定上层Select方法调用的语义,再推导lambda内部成员的类型,步骤如下:
- 找到
p.Name所属的SimpleLambdaExpressionSyntax节点,再向上查找其父级的Select方法对应的InvocationExpressionSyntax节点 - 先获取Select方法调用的语义符号,完成上层调用的语义绑定
- 再获取lambda参数的声明符号,即可得到正确的参数类型,此时再访问内部成员的TypeInfo就能得到正确结果
实现代码
// 定位到目标p.Name节点 var targetMemberAccess = syntaxTree.GetRoot() .DescendantNodes() .OfType<MemberAccessExpressionSyntax>() .First(m => m.Name.Identifier.Text == "Name" && m.Expression is IdentifierNameSyntax identifier && identifier.Identifier.Text == "p"); var semanticModel = context.Compilation.GetSemanticModel(syntaxTree); // 找到所属的lambda表达式 var lambda = targetMemberAccess.FirstAncestorOrSelf<SimpleLambdaExpressionSyntax>(); // 获取lambda参数的声明符号,上层调用绑定完成后可直接拿到正确的Person类型 var lambdaParamSymbol = semanticModel.GetDeclaredSymbol(lambda.Parameter); // 此时再获取p.Name的TypeInfo就能得到正确的string类型 var nameTypeInfo = semanticModel.GetTypeInfo(targetMemberAccess); Console.WriteLine(nameTypeInfo.Type?.Name); // 输出String
备选方案
你也可以直接通过参数类型查找对应成员的类型,可靠性更高,不需要依赖成员访问节点的TypeInfo返回:
var personType = lambdaParamSymbol.Type as INamedTypeSymbol; var nameProperty = personType.GetMembers("Name") .OfType<IPropertySymbol>() .First(); Console.WriteLine(nameProperty.Type.Name); // 输出String
内容的提问来源于stack exchange,提问作者Scrappy Coco
相关产品推荐
相关产品推荐

