.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控制台直接报错,因为用到了反射,具体错误如下:
.Net 7 的Native AOT为啥不支持反射,你知道吗

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

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

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

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

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

MarkAndProduceDiagnostics,如下图所示:
.Net 7 的Native AOT为啥不支持反射,你知道吗

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

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

_requireDynamicallyAccessedMembersAction.invoke里面会判断方法。如下图所示:
.Net 7 的Native AOT为啥不支持反射,你知道吗

:看一个函数

_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代理,直接写入错误日志,导入到控制台上。