快乐地在AOT项目中用反射
反射是.NET开发的利器,但对于AOT来说,因为Native编译,所以反射的功能基本在AOT编译的项目中失效。办法总比困难多,这么好的东西不能扔掉,下面是“尽量”可以使用反射的例子,为什么“尽量”,看完下面的案例我们再做说明。
在AOT项目中使用反射基本原理:利用源生成器,在build项目时,提前调用一下每个想要反射类型的GetMember。
1、首先创建项目AOTReflectionHelper,这个项目中只有一个类AOTRefectionAttribute,并且这个类是分部类。
using System; namespace AOTReflectionHelper { [AttributeUsage(AttributeTargets.Class)] public partial class AOTReflectionAttribute : Attribute { } }
2、然后创建AOTReflectionGenerator项目,这是一个源生成器的项目,项目文件如下:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <LangVersion>12.0</LangVersion> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0-beta1.23525.2"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.0-2.final" /> </ItemGroup> </Project>
源生器的实现代码如下,基本思路就是要生成上面AOTRefectionAttribute类的分部类,并在构造函数中调用项目中所有上了这个特性的择射GetMembers方法,因为在构建时处用反射获以成员,这部分没有问题。
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Text; namespace AOTReflectionGenerator { [Generator] public class AOTReflectionGenerator : ISourceGenerator { public void Execute(GeneratorExecutionContext context) { var types = GetAOTReflectionAttributeTypeDeclarations(context); var source = BuildSourse(types); context.AddSource($"AOTReflectionGenerator.g.cs", source); } string BuildSourse(IEnumerable<(string NamespaceName, string ClassName)> types) { var codes = new StringBuilder(); foreach (var type in types) { codes.AppendLine($" typeof({(type.NamespaceName != "<global namespace>" ? type.NamespaceName + "." : "")}{type.ClassName}).GetMembers();"); } var source = $$""" using System; [AttributeUsage(AttributeTargets.Class)] public partial class AOTReflectionAttribute:Attribute { public AOTReflectionAttribute() { {{codes}} } } """; return source; } IEnumerable<(string NamespaceName, string ClassName)> GetAOTReflectionAttributeTypeDeclarations(GeneratorExecutionContext context) { var list = new List<(string, string)>(); foreach (var tree in context.Compilation.SyntaxTrees) { var semanticModel = context.Compilation.GetSemanticModel(tree); var root = tree.GetRoot(context.CancellationToken); var typeDecls = root.DescendantNodes().OfType<TypeDeclarationSyntax>(); foreach (var decl in typeDecls) { // 获取类型的语义模型 var symbol = semanticModel.GetDeclaredSymbol(decl); // 检查类型是否带有 AOTReflectionAttribute 特性 if (symbol?.GetAttributes().Any(attr => attr.AttributeClass?.Name == "AOTReflectionAttribute") == true) { // 处理带有 AOTReflectionAttribute 特性的类型 var className = decl.Identifier.ValueText; var namespaceName = symbol.ContainingNamespace?.ToDisplayString(); list.Add((namespaceName, className)); } } } return list; } public void Initialize(GeneratorInitializationContext context) { } } }
3、APITest项目是一个测试项目,项目文件如下,注意12行的源生器,要加两个属性。
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <InvariantGlobalization>true</InvariantGlobalization> <PublishAot>true</PublishAot> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\AOTReflectionGenerator\AOTReflectionGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> <ProjectReference Include="..\AOTReflectionHelper\AOTReflectionHelper.csproj" /> </ItemGroup> </Project>
下面是实现代码,只要给想要反射的类型加上[AOTReflection]即可,我们就能第25行的方法中使用GetProperties了,否则这个方法返回的是个空数组。
using System.Text.Json.Serialization; using System.Text; using APITest.Models; using AOTReflectionHelper; var builder = WebApplication.CreateSlimBuilder(args); builder.Services.ConfigureHttpJsonOptions(options => { options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); }); var app = builder.Build(); app.MapGet("/test", () => { var order = new Order { Name = "桂素伟", Age = 10, Birthday = DateTime.Now, Hobbies = new string[] { "足球", "代码" } }; InvockMethod(order); return GetString(order); }); app.MapPost("/test", (Person person) => { return GetString(person); }); string GetString<T>(T t) where T : Parent { var sb = new StringBuilder(); var pros = typeof(T)?.GetProperties(); foreach (var pro in pros) { if (pro != null) { if (pro.PropertyType.IsArray) { var arr = pro.GetValue(t) as string[]; sb.Append($"{pro?.Name}:{string.Join(",", arr)};"); } else { sb.Append($"{pro?.Name}:{pro?.GetValue(t)};"); } } } t.Print(sb.ToString()); return sb.ToString(); } void InvockMethod<T>(T t) { var method = typeof(T)?.GetMethod("Print"); method?.Invoke(t, new object[] { "用反射调用Print" }); } app.Run(); [JsonSerializable(typeof(Person))] [JsonSerializable(typeof(string[]))] public partial class AppJsonSerializerContext : JsonSerializerContext { } public partial class Parent { public void Print(string content) { Console.WriteLine($"反射类型名:{GetType().Name},Print参数:{content}"); } } [AOTReflection] public partial class Order : Parent { public string Name { get; set; } public int Age { get; set; } public DateTime Birthday { get; set; } public string[] Hobbies { get; set; } } namespace APITest.Models { [AOTReflection] public class Person : Parent { public string Name { get; set; } public int Age { get; set; } public DateTime Birthday { get; set; } public string[] Hobbies { get; set; } } }
下面是Get的结果:
下面是Post的结果:
如果你详细尝试了上面的代码,可能就会理解到“尽量”了,因为.NET的反射非常强大,而使用这个方法,只能解决自己定义的类型的反射,因为是通过硬编码的方式,给自定义类型添加[AOTReflection]来完成的。不过提供的这个思路,你可以找到你所反射类型的特征,来针对性的在源生器里调用GetMembers,这就你就不受限制了。
文章来源微信公众号
想要更快更方便的了解相关知识,可以关注微信公众号
****欢迎关注我的asp.net core系统课程****
《asp.net core精要讲解》 https://ke.qq.com/course/265696
《asp.net core 3.0》 https://ke.qq.com/course/437517
《asp.net core项目实战》 https://ke.qq.com/course/291868
《基于.net core微服务》 https://ke.qq.com/course/299524
《asp.net core精要讲解》 https://ke.qq.com/course/265696
《asp.net core 3.0》 https://ke.qq.com/course/437517
《asp.net core项目实战》 https://ke.qq.com/course/291868
《基于.net core微服务》 https://ke.qq.com/course/299524
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
2022-02-21 .net LTS3.1升5.0和LTS6.0隐蔽的坑
2022-02-21 应用内moniter