6

在为一天的大部分时间挠头之后,我偶然发现了一个非常奇怪的问题,即使用 .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

但是,当删除空传播运算符时,它可以在 .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

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

4

1 回答 1

2

原来这是一个错误。

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

  • System.Reflection 参考程序集 (C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETPortable\v4.5\Profile\Profile78\System.Reflection.dll 中的 ConstructorInfo.Invoke(object[]) 方法说Invoke 方法不是虚拟的。
  • 某处有人决定该方法应该是虚拟的,他们在实现中对其进行了更改。C# 代码编译所针对的参考程序集保持不变。
  • 通常这没什么大不了的,因为 C# 几乎总是虚拟调用方法(即使它们不是虚拟的),因为它需要虚拟调用的副作用(在 null this 上抛出 NullReferenceException)。
  • 除了使用 null 传播运算符外,C# 编译器知道 NullReferenceException 不会发生,它决定发出正常的调用指令而不是 callvirt 以防止不必要的 null 检查。对 ConstructorInfo.Invoke(object[]) 方法进行正常调用会导致我们进入一个永远不应调用的方法。

好消息是,作为 NetStandard 2.0 兼容性工作的一部分,ConstructorInfo.Invoke(object[])不再是虚拟的(之前的链接指向旧快照)。该版本的 .NET Native 尚未发布。目前唯一的解决方法是不让 C# 编译器通过避免运算符来优化 callvirt 到调用。

于 2017-05-10T02:56:15.290 回答