.NET Native 代码在构造函数上崩溃?.Invoke()(空传播)

.NET Native code crashes on constructor?.Invoke() (null-propagation)

在绞尽脑汁绞尽脑汁后,我偶然发现了一个非常奇怪的问题,即使用 .NET Native(用于 Windows UWP 应用程序)编译的 .NET 代码。

以下代码在任何 .NET 运行时环境中都能正常工作,包括 Mono、Xamarin 等。:

public class ABC {}
// ...
var constr = typeof(ABC).GetTypeInfo().DeclaredConstructors.First();

var abc = (ABC) constr?.Invoke(new object[0]);
// abc now contains an instance of ABC

在使用 .NET Native 编译的 Windows UWP 上,代码抛出 NotImplementedException

类型的异常

但是,当删除 null 传播运算符时,它可以在 .NET Native 上完美运行:

public class ABC {}
// ...
var constr = typeof(ABC).GetTypeInfo().DeclaredConstructors.First();

var abc1 = (ABC) constr.Invoke(new object[0]);
// abc1 now contains an instance of ABC

// the following line throws an exception on .NET Native
// but it works fine on any other .NET runtime
var abc2 = (ABC) constr?.Invoke(new object[0]);

堆栈跟踪中发生异常的行是:

at System.Reflection.ConstructorInfo.Invoke(Object[] parameters) 
in f:\dd\ndp\fxcore\CoreRT\src\System.Private.Reflection\src\System\Reflection\ConstructorInfo.cs:line 41

这闻起来像是编译器或运行时中的错误。这里发生了什么?我错过了什么吗?

原来是个bug。

更多信息在这里:https://github.com/dotnet/corert/issues/3565

  • ConstructorInfo.Invoke(object[]) method in the System.Reflection reference assembly (C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETPortable\v4.5\Profile\Profile78\System.Reflection.dll says that the Invoke method is not virtual.
  • Somewhere someone decided that the method should be virtual and they changed it in the implementation. The reference assembly that the C# code compiles against was left untouched.
  • Normally this is not a big deal because C# pretty much always calls methods virtually (even if they're not virtual), because it needs the side effect of the virtual call (throw a NullReferenceException on null this).
  • Except with the null propagation operator the C# compiler knows that a NullReferenceException cannot occur and it decides to emit a normal call instruction instead of callvirt to prevent the unnecessary null check. Doing a normal call to the ConstructorInfo.Invoke(object[]) method results in us landing in a method that should never be called.

The good news is that ConstructorInfo.Invoke(object[]) is no longer virtual as part of the NetStandard 2.0 compatibility effort (the previous link was to an old snapshot). That version of .NET Native hasn't shipped yet. The only workaround for now is not to let C# compiler optimize the callvirt to a call by avoiding the operator.