首页 > 编程语言 >AOT漫谈专题(第六篇): C# AOT 的泛型,序列化,反射问题

AOT漫谈专题(第六篇): C# AOT 的泛型,序列化,反射问题

时间:2024-10-23 12:59:50浏览次数:7  
标签:Console C# Person AOT var 序列化 public

一:背景

1. 讲故事

在 .NET AOT 编程中,难免会在 泛型,序列化,以及反射的问题上纠结和反复纠错尝试,这篇我们就来好好聊一聊相关的处理方案。

二:常见问题解决

1. 泛型问题

研究过泛型的朋友应该都知道,从开放类型上产下来的封闭类型往往会有单独的 MethodTable,并共用 EEClass,对于值类型的泛型相当于是不同的个体,如果在 AOT Compiler 的过程中没有单独产生这样的个体信息,自然在运行时就会报错,这么说可能有点懵,举一个简单的例子。


    internal class Program
    {
        static void Main(string[] args)
        {
            var type = Type.GetType(Console.ReadLine());

            try
            {
                var mylist = typeof(List<>).MakeGenericType(type);

                var instance = Activator.CreateInstance(mylist);
                int count = (int)mylist.GetProperty("Count").GetValue(instance);
                Console.WriteLine(count);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                Console.WriteLine(ex.Message);
            }
            Console.ReadLine();
        }
    }

    public class Location
    {
    }

从上图看直接抛了一个异常,主要原因在于 Location 被踢出了依赖图,那怎么办呢?很显然可以直接 new List<Location> 到依赖图中,但在代码中直接new是非常具有侵入性的操作,那如何让侵入性更小呢?自然就是借助 AOT 独有的 rd (Runtime Directives) 这种xml机制,具体可参见:https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/docs/rd-xml-format.md

rd机制非常强大,大概如下:

1)可以指定程序集,类型,方法作为编译图的根节点使用,和 ILLink 有部分融合。
2)可以手工的进行泛型初始化,也可以将泛型下的某方法作为根节点使用。
3)为Marshal和Delegate提供Pinvoke支持。

在 ilc 源码中是用 compilationRoots 来承载rd过去的根节点,可以一探究竟。


foreach (var rdXmlFilePath in Get(_command.RdXmlFilePaths))
{
    compilationRoots.Add(new RdXmlRootProvider(typeSystemContext, rdXmlFilePath));
}

有了这些知识就可以在 rd.xml 中实例化 List<Location> 了,参考如下:


<?xml version="1.0" encoding="utf-8" ?>
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
	<Application>
		<Assembly Name="Example_21_1">
			<Type Name="System.Collections.Generic.List`1[[Example_21_1.Location,Example_21_1]]" Dynamic="Required All" />
		</Assembly>
	</Application>
</Directives>

同时在 csproj 做一下引入即可。


<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<OutputType>Exe</OutputType>
		<TargetFramework>net8.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
		<PublishAot>true</PublishAot>
		<InvariantGlobalization>true</InvariantGlobalization>
	</PropertyGroup>
	<ItemGroup>
		<RdXmlFile Include="rd.xml" />
	</ItemGroup>
</Project>

执行之后如下,要注意一点的是 Dynamic="Required All" 它可以把 List<Location> 下的所有方法和字段都注入到了依赖图中,比如下图中的 Count 属性方法。

2. 序列化问题

序列化会涉及到大量的反射,而反射又需要得到大量的元数据支持,所以很多第三方的Json序列化无法实现,不过官方提供的Json序列化借助于 SourceGenerator 将原来 dll 中的元数据迁移到了硬编码中,从而变相的实现了AOT的Json序列化,参考代码如下:


namespace Example_21_1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var person = new Person()
            {
                Name = "john",
                Age = 30,
                BirthDate = new DateTime(1993, 5, 15),
                Gender = "Mail"
            };

            var jsonString = JsonSerializer.Serialize(person,
                                            SourceGenerationContext.Default.Person);

            Console.WriteLine(jsonString);
            Console.ReadLine();
        }
    }
}

[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(Person))]
internal partial class SourceGenerationContext : JsonSerializerContext { }

public class Person
{
    public int Age { get; set; }
    public string Name { get; set; }
    public DateTime BirthDate { get; set; }
    public string Gender { get; set; }
}

当用 VS 调试的时候,你会发现多了一个 SourceGenerationContext.Person.g.cs 文件,并且用 properties 数组承载了 Person 的元数据,截图如下:

3. 反射问题

反射其实也是一个比较纠结的问题,简单的反射AOT编译器能够轻松推测,但稍微需要上下文关联的就搞不定了,毕竟涉及到上下文关联需要大量的算力,而目前的AOT编译本身就比较慢了,所以暂时没有做支持,相信后续的版本会有所改进吧,接下来举一个例子演示下。


    internal class Program
    {
        static void Main(string[] args)
        {
            Invoke(typeof(Person));

            Console.ReadLine();
        }

        static void Invoke(Type type)
        {
            var props = type.GetProperties();

            foreach (var prop in props)
            {
                Console.WriteLine(prop);
            }
        }
    }

    public class Person
    {
        public int Age { get; set; }
        public string Name { get; set; }
        public DateTime BirthDate { get; set; }
        public string Gender { get; set; }
    }

这段代码在 AOT中是提取不出属性的,因为 Invoke(typeof(Person));type.GetProperties 之间隔了一个 Type type 参数,虽然我们肉眼能知道这个代码的意图,但 ilc 的深度优先它不知道你需要 Person中的什么,所以它只保留了 Person 本身,如果你想直面观测的话,可以这样做:

  1. <PublishAot>true</PublishAot> 改成 <PublishTrimmed>true</PublishTrimmed>
  2. 使用 dotnet publish 发布。
  3. 使用ILSPY观测。

截图如下,可以看到 Person 空空如也。

有了这个底子就比较简单了,为了让 Person 保留属性,可以傻乎乎的用 DynamicallyAccessedMembers 来告诉AOT我到底想要什么,比如 PublicProperties 就是所有的属性,当然也可以设置为 ALL。


        static void Invoke([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type)
        {
            var props = type.GetProperties();

            foreach (var prop in props)
            {
                Console.WriteLine(prop);
            }
        }

如果要想侵入性更小的话,可以使用 TrimmerRootDescriptor 这种外来的 xml 进行更高级别的定制,比如我不想要 Gender 字段 ,具体参考官方链接:https://github.com/dotnet/runtime/blob/main/docs/tools/illink/data-formats.md#xml-examples


<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<OutputType>Exe</OutputType>
		<TargetFramework>net8.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
		<PublishAot>true</PublishAot>
		<InvariantGlobalization>true</InvariantGlobalization>
		<IlcGenerateMapFile>true</IlcGenerateMapFile>
	</PropertyGroup>
	<ItemGroup>
		<TrimmerRootDescriptor Include="link.xml" />
	</ItemGroup>
</Project>

然后就是 xml 配置。


<?xml version="1.0" encoding="utf-8" ?>
<linker>
	<assembly fullname="Example_21_1">
		<type fullname="Example_21_1.Person">
			<property signature="System.Int32 Age" />
			<property signature="System.String Name" />
			<property signature="System.DateTime BirthDate" />
		</type>
	</assembly>
</linker>

从下图看,一切都是那么完美。

三:总结

在将程序发布成AOT的过程中,总会遇到这样或者那样的坑,这篇算是提供点理论基础给后来者吧,同时 Runtime Directives 这种无侵入的实例化方式,很值得关注哈。

图片名称

标签:Console,C#,Person,AOT,var,序列化,public
From: https://www.cnblogs.com/huangxincheng/p/18496142

相关文章

  • Yocto 介绍
    前言全局说明Yocto介绍一、说明占位二、2.1文件名:2.2文件名:三、3.1文件名:3.2文件名:四、4.1文件名:4.2文件名:免责声明:本号所涉及内容仅供安全研究与教学使用,如出现其他风险,后果自负。参考、来源:......
  • yocto-名词介绍
    前言全局说明yocto-名词介绍一、说明二、2.1文件名:2.2文件名:三、3.1文件名:3.2文件名:四、4.1文件名:4.2文件名:免责声明:本号所涉及内容仅供安全研究与教学使用,如出现其他风险,后果自负。参考、来源:......
  • 查看执行计划 autotrace 指标详解
    autotrace看执行计划,逻辑读,递归调用等sql执行信息。评估的执行计划非真实执行计划,即使是执行了,显示的均是explain的信息。执行输出示例:SQL>settimingonSQL>setautotraceonSQL>selectMGR,wm_concat(ENAME)fromemp2groupbyMGR;MGRWM_CONCAT(ENAME......
  • Cmake中“目标层级“的概念(target_include_directories和include_directories的区别)
    CMake中,“目标层级”(Target-Level)是一个核心概念,涉及到项目中构建的具体实体,如可执行文件、库等。理解目标层级对于有效地使用现代CMake功能、管理依赖关系以及配置构建过程至关重要。以下将详细解释什么是目标层级、其重要性以及如何在CMake中应用相关命令。什么是“......
  • cmake中link_directories()与link_libraried()全局性的理解
    在CMake中,link_directories()和link_libraries()是全局命令,这意味着它们的设置会影响之后在CMakeLists.txt文件中定义的所有目标(targets)。要深入理解这一点,下面将详细解释全局命令的作用范围、它们如何影响项目中的多个目标、以及为什么在现代CMake中更推荐使用......
  • CRC32爆破脚本 + [MoeCTF 2022]cccrrc 题解
    CRC32爆破原理介绍:CRC(循环冗余校验)是一种用于检测数据传输错误的技术。CRC算法生成一个校验值(校验和),这个值可以附加到数据后面,在数据接收方重新计算校验值并与附加的校验值进行比较,以此来确定数据是否在传输过程中发生了错误CRC32是一种常用的CRC算法,它的校验值长度固定为3......
  • 手把手Linux安装RocketMQ教程
    手把手Linux安装RocketMQ教程1.下载rocketmq安装包2.创建目录并将压缩包上传至服务器3.配置RocketMQ4.启动RocketMQ5.关闭RocketMQ6.测试RocketMQ7.mqadmin查看服务状态8.配置启动脚本1.namesrv脚本2.broker脚本3.单脚本启动4.单脚本停止待完善1.开启自启动配置2.安装ro......
  • JavaScript 第27章:构建工具与自动化
    在现代JavaScript开发中,构建工具、代码转换工具、代码质量和代码格式化工具对于提高开发效率、保持代码整洁以及确保代码质量有着至关重要的作用。下面将分别介绍Webpack、Babel、ESLint和Prettier的配置与使用,并给出一些示例。1.构建工具:Webpack配置与使用Webpack是一个......
  • 多品牌摄像机视频平台EasyCVR海康大华宇视视频平台如何接入多样化设备
    在实际的工程项目里,我们常常会面临这样的情况:项目管理者可能会决定使用多个品牌的视频监控摄像头,或者有需求将现有的、多种类型的监控系统进行整合。现在,让我们来探讨一下如何实现不同品牌摄像头的连接和使用。1、GB/T281协议GB/T28181的完整名称为《公共安全视频监控联网系统......
  • P7909 [CSP-J 2021] 分糖果
    结论题题面概括请在$[l,r]$中找出一个数$k$,使得$n$%$k$的值最大.思路当$n\le10^9$时,说明$\Theta(n)$的算法已经结束了.所以,接下来是结论推理.当$\left\lfloor\frac{l}{n}\right\rfloor=\left\lfloor\frac{r}{n}\right\rfloor$时,$r$%$n$的......