如何在C#源生成器中获取泛型方法调用的类型参数?
在C#源生成器中获取泛型方法调用的类型参数(适配Bevy风格ECS)
一、精准定位目标方法的调用站点
用ISyntaxReceiver定向收集目标调用,避免无差别扫描所有语法节点:
- 重写
OnVisitSyntaxNode,筛选所有InvocationExpressionSyntax - 结合
SemanticModel验证调用的方法是否为App.RunSystem<T>(Action<T>):- 获取调用的方法符号
IMethodSymbol - 校验方法名、所属类型(App类)、命名空间,确认是泛型方法且泛型参数数量为1
- 验证参数列表匹配(第一个参数为
Action<T>类型)
- 获取调用的方法符号
代码示例:
public class RunSystemInvocationReceiver : ISyntaxReceiver { public List<InvocationExpressionSyntax> Invocations { get; } = new(); public void OnVisitSyntaxNode(SyntaxNode node) { if (node is InvocationExpressionSyntax invocation && invocation.Expression is MemberAccessExpressionSyntax memberAccess && memberAccess.Name.Identifier.Text == "RunSystem") { Invocations.Add(invocation); } } } // 在生成器Execute方法中筛选有效调用 var semanticModel = context.Compilation.GetSemanticModel(invocation.SyntaxTree); var methodSymbol = semanticModel.GetSymbolInfo(invocation.Expression).Symbol as IMethodSymbol; if (methodSymbol != null && methodSymbol.Name == "RunSystem" && methodSymbol.ContainingType.Name == "App" && methodSymbol.ContainingNamespace.ToDisplayString() == "Your.Namespace" && methodSymbol.IsGenericMethod && methodSymbol.TypeParameters.Length == 1 && methodSymbol.Parameters[0].Type is INamedTypeSymbol actionType && actionType.Name == "Action" && actionType.TypeArguments.Length == 1) { // 确认是目标RunSystem<T>调用 }
二、获取泛型类型参数(显式+推断)
不管是显式指定还是类型推断,编译器已完成类型解析,直接从IMethodSymbol获取即可:
1. 显式指定的情况(如app.RunSystem<SomeType>(...))
无需手动解析语法节点的类型参数,直接用语义分析结果:
// 从方法符号获取已解析的类型参数 var systemType = methodSymbol.TypeArguments[0]; // 获取类型的完全限定名 var typeFullName = systemType.ToDisplayString();
2. 类型推断的情况(如app.RunSystem((SomeType t) => { }))
编译器完成类型推断后,methodSymbol.TypeArguments会自动包含推断出的类型,无需手动解析lambda参数:
// 直接取已推断的类型参数 var inferredType = methodSymbol.TypeArguments[0]; // 可选:验证推断类型与lambda参数类型一致 var lambdaArgSymbol = semanticModel.GetSymbolInfo(invocation.ArgumentList.Arguments[0].Expression).Symbol as IParameterSymbol; if (lambdaArgSymbol != null && lambdaArgSymbol.Type.Equals(inferredType, SymbolEqualityComparer.Default)) { // 类型推断结果符合预期 }
三、Bevy风格ECS的源生成器优化建议
针对AddSystem和Query场景,生成强类型代码彻底规避反射与DynamicInvoke,适配AOT平台:
- AddSystem场景:为每个System生成静态强类型扩展方法,固化泛型参数,比如生成
AppExtensions.AddSomeTypeSystem(this App app, Action<SomeType> action),调用时直接使用强类型方法 - Query场景:为每种组件组合生成强类型查询器,比如
Query<ComponentA, ComponentB>对应生成Query_ComponentA_ComponentB类,内置强类型实体迭代逻辑,避免运行时类型检查 - AOT适配:所有生成代码均为静态编译,无动态表达式或反射调用,完全符合AOT平台要求
生成代码示例:
// 自动生成的强类型扩展方法 public static class AppSystemExtensions { public static void AddSomeTypeSystem(this App app, Action<SomeType> action) { app.RegisterSystem(typeof(SomeType), action); } } // 自动生成的强类型Query public class Query_SomeType : IQuery { public void Iterate(World world, Action<SomeType> callback) { foreach (var entity in world.EntitiesWith<SomeType>()) { callback(entity.GetComponent<SomeType>()); } } }
内容的提问来源于stack exchange,提问作者Clonkex
相关产品推荐
相关产品推荐

