反射是.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,这就你就不受限制了。