.Net 7 的Native AOT为啥不支持反射,你知道吗

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.Net 7 的Native AOT为啥不支持反射,你知道吗相关的知识,希望对你有一定的参考价值。

楔子

Native AOT为啥不支持反射呢,问题萦绕脑海?微软官方说不支持运行时生成的代码,这很难让人彻底理解它。
本篇从源码层面来解读下。

代码

上一段简单的代码例子

public class A
        
            public void FirstMethod()
            

            
       
        static void Main(string[] args)
        
            A a = new A();    
            Type type =a.GetType();
            MethodInfo[] methodinfo= type.GetMethods();
            foreach (MethodInfo method in methodinfo)
            
                Console.WriteLine(method.Name);
            
        

Debug

一:对上面的代码进行ILC调试的时候,ILC控制台直接报错,因为用到了反射,具体错误如下:

这个错误很明显是它告诉你调用了

System.Type.GetMethods()这个函数导致的。而这个函数刚好就是反射获取方法。那么这个错误从何而来呢?

:为了研究这个错误,可以整个ILC项目搜索下这个错误的字符串,其中有一个字符串:Trim analysis warning可以找到错误的结果。搜索它之后,很快在Logger.cs这个文件里面找到了相同的字符串。而这个字符串在Logger.cs类的一个方法名称叫做:ShouldSuppressAnalysisWarningsForRequires里面。直接在里面下断点。
果然断点跳转到了它里面去,如下图所示:

:更进一步,通过调试找到了类:

TrimAnalysisPatternStore。这个类里面有个方法:

MarkAndProduceDiagnostics,如下图所示:

可以看到,这个方法里MethodCallPatterns.Values 的循环, MethodCallPatterns.Values就是Program.Main方法里面所有调用的方法。其中就包括了反射System.Type.GetMethods。

:它循环到System.Type.GetMethods,然后进入到MarkAndProduceDiagnostics函数。这个函数经过一系列调用,进入到了HandleCallAction类,调用了里面的Invoke方法。在HandleCallAction类的1145行处,它会判断GetMethods这个方法是否是静态方法,如果不是,则调用

_requireDynamicallyAccessedMembersAction.invoke里面会判断方法。如下图所示:

:看一个函数

_requireDynamicallyAccessedMembersAction.invoke里面通过

Annotations.SourceHasRequiredAnnotations方法判断

GetMethods是否是被

DynamicallyAccessedMembersAttribute特性标记,如果标记了返回

true.最后调用_diagnosticContext.AddDiagnostic写入最上面报错的那段话。
Annotations.SourceHasRequiredAnnotations函数原型如下:

public static bool SourceHasRequiredAnnotations (
			DynamicallyAccessedMemberTypes sourceMemberTypes,
			DynamicallyAccessedMemberTypes targetMemberTypes,
			out string missingMemberTypesString)
		
			missingMemberTypesString = string.Empty;
                       //获取反射GetMethods的特性
			var missingMemberTypes = GetMissingMemberTypes (targetMemberTypes, sourceMemberTypes);
			//特性如果等于空,则返回true。由于System.Type.GetMethods()函数的特性不为空所以这里返回false,也刚好调用了上面的_diagnosticContext.AddDiagnostic,在控制台上报错。
			if (missingMemberTypes == DynamicallyAccessedMemberTypes.None)
				return true;

			missingMemberTypesString = GetMemberTypesString (missingMemberTypes);
			return false;
		

托管和非拖的反射

System.Type.GetMethods()方法原型如下:

[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
		public MethodInfo[] GetMethods()
		
			return this.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public);
		

		// Token: 0x060009AA RID: 2474
		[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)]
		public abstract MethodInfo[] GetMethods(BindingFlags bindingAttr);

可以看到它的特性DynamicallyAccessedMembers,跟上面分析相印证。

GetMethods()这个函数最后会调用

System.RuntimeType.GetMethods。其原型如下:

public override MethodInfo[] GetMethods(BindingFlags bindingAttr)
		
			return this.GetMethodCandidates(null, -1, bindingAttr, CallingConventions.Any, null, false).ToArray();
		

这个跟踪下去最后调用的是FCall/QCall。

结尾

总结下,就是ILC会判断这个方法是否被标记了DynamicallyAccessedMembersAttribute特性,并且是否调用了FCall/QCall。如果满足以上条件,则进行ILC代理,直接写入错误日志,导入到控制台上。

作者:江湖评谈(公众号同名)专研.Net 7的CLR和ILC核心技术。包括并不局限于:Roslyn,RyuJIT,二进制,汇编,C/C++。版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

以上是关于.Net 7 的Native AOT为啥不支持反射,你知道吗的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Angular AoT 不支持装饰器中的函数表达式?

.Net 7 AOT 彻底解析下(完结篇)

.Net 7 的AOT的程序比托管代码更容易破解?

V8 JS AOT化的探索与实践

.NET 7 AOT 的使用以及 .NET 与 Go 互相调用

.NET 8ASP.NET Core计划 - 支持更完善的AOT发布