反射是.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,这就你就不受限制了。
文章来源微信公众号