• 如何在AOT项目中使用反射功能?
  • 发布于 2个月前
  • 147 热度
    0 评论
反射是.NET开发的利器,但对于AOT来说,因为Native编译,所以反射的功能基本在AOT编译的项目中失效。办法总比困难多,这么好的东西不能扔掉,下面是“尽量”可以使用反射的例子,为什么“尽量”,看完下面的案例我们再做说明。

在AOT项目中使用反射基本原理:利用源生成器,在build项目时,提前调用一下每个想要反射类型的GetMember。
1、首先创建项目AOTReflectionHelper,这个项目中只有一个类AOTRefectionAttribute,并且这个类是分部类。
using System;

namespace AOTReflectionHelper
{
    [AttributeUsage(AttributeTargets.Class)]
    public partial class AOTReflectionAttribute : Attribute
    {
        // 堆代码 duidaima.com
    }
}
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,这就你就不受限制了。
用户评论