Roslyn分析器遇方法重载时,如何获取InvocationExpression对应的ISymbol?
我正在开发一个Roslyn代码分析器,用于在ASP.NET Core MVC(基于EF Core)应用中,检测使用同步数据库方法且存在异步替代版本的场景并发出警告——因为同步数据库调用会导致线程耗尽,而EF Core的IQueryable扩展方法遵循命名约定,比如First对应FirstAsync。
核心需求是先识别同步方法调用,再查找对应的异步替代方法。但遇到了问题:当找到同步方法调用的InvocationExpressionSyntax后,调用SemanticModel.GetSymbolInfo获取ISymbol时,只要方法存在重载,就会返回CandidateReason.OverloadResolutionFailure和CandidateSymbols数组,无法直接得到语法对应的符号。
我曾看到2019年的Gitter对话提到“语言本身没有这样的概念,得自己处理”,还有一段手动匹配参数推断重载的示例代码,但没能跑通。想知道:有没有办法让Roslyn直接返回语法对应的符号?如果没有,该如何手动实现?
更新1
当前的方法调用不存在歧义,调用表达式为:
context.Users.First(u => u.Id == 1)
候选符号有两个:
DbSet<User>.First() DbSet<User>.First(Expression<Func<User, bool>>)
更新2
测试源代码
using System.Linq.Expressions; public abstract class DbSet<TEntity> where TEntity : class { public TEntity First() { throw new Exception(); } public TEntity First(Expression<Func<TEntity, bool>> predicate) { throw new Exception(); } public Task<TEntity> FirstAsync(CancellationToken cancellationToken = default) { throw new Exception(); } public Task<TEntity> FirstAsync<TSource>(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) { throw new Exception(); } } public class User { public int Id { get; set; } } public class ApplicationDbContext { public DbSet<User> Users { get; set; } = null!; } public class Test { public void TestContext(ApplicationDbContext context) { var x = context.Users.First(); var y = context.Users.First(u => u.Id == 1); } }
分析器代码
private void AnalyzeSyncInvocationExpression(SyntaxNodeAnalysisContext context) { var invocationExpr = (InvocationExpressionSyntax)context.Node; // 只关注非异步等待的同步调用 if (invocationExpr.Parent is AwaitExpressionSyntax) { return; } // 排除调用链中的中间节点,只处理完整的调用表达式 if (invocationExpr.Parent is MemberAccessExpressionSyntax) { return; } // 如果返回值是IQueryable,说明是查询构建阶段,不触发警告 if (context.SemanticModel.GetTypeInfo(invocationExpr).Type is INamedTypeSymbol returnType && returnType.Name == nameof(IQueryable)) { return; } // 获取调用的方法符号 var invokedSymbol = context.SemanticModel.GetSymbolInfo(invocationExpr).Symbol; if (invokedSymbol == null) { return; // 无法确定符号时直接返回 } // 此处存在问题:重载场景下invokedSymbol为null }
解决方案
1. 修复测试代码中的方法定义问题
你的测试代码里,DbSet<TEntity>.FirstAsync<TSource>的泛型参数TSource是多余的——EF Core实际的异步方法不会包含这个额外泛型参数,这会干扰Roslyn的重载解析逻辑,导致无法直接返回正确符号。先修正异步方法定义:
public Task<TEntity> FirstAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) { throw new Exception(); }
修正后,GetSymbolInfo(invocationExpr).Symbol就能正确返回对应的重载符号。
2. 手动处理重载解析(通用方案)
如果需要处理复杂重载场景,或者Roslyn无法自动解析时,可以手动对比候选符号与调用表达式的参数,匹配正确的重载:
private IMethodSymbol? ResolveCorrectOverload(InvocationExpressionSyntax invocationExpr, SemanticModel semanticModel) { var symbolInfo = semanticModel.GetSymbolInfo(invocationExpr); if (symbolInfo.Symbol != null) { return symbolInfo.Symbol as IMethodSymbol; } var candidateMethods = symbolInfo.CandidateSymbols.OfType<IMethodSymbol>(); var arguments = invocationExpr.ArgumentList.Arguments; foreach (var method in candidateMethods) { var parameters = method.Parameters; // 匹配参数数量(含可选参数) if (arguments.Count < parameters.Count(p => !p.IsOptional) || arguments.Count > parameters.Count) { continue; } bool matches = true; for (int i = 0; i < arguments.Count; i++) { var arg = arguments[i]; var param = parameters[i]; var argType = semanticModel.GetTypeInfo(arg.Expression).Type; if (argType == null) { matches = false; break; } // 检查参数类型是否支持隐式转换 var conversion = semanticModel.ClassifyConversion(arg.Expression, param.Type); if (!conversion.IsImplicit) { matches = false; break; } } if (matches) { return method; } } return null; }
在分析器中替换原符号获取逻辑:
// 替换原来的invokedSymbol获取逻辑 var invokedSymbol = ResolveCorrectOverload(invocationExpr, context.SemanticModel); if (invokedSymbol == null) { return; }
3. EF Core场景额外优化
- 先过滤名称不含
Async的方法,再检查是否存在对应XxxAsync命名的方法,减少无效匹配 - 验证方法所属程序集是否为EF Core相关程序集,降低误判概率
内容的提问来源于stack exchange,提问作者ghosttie

