.NET нативный код падает на конструкторе?.Invoke() (распространение ноль)
Потратив голову на лучшую часть дня, я наткнулся на очень странную проблему с кодом.NET, скомпилированным с использованием.NET Native (используется для приложений Windows UWP).
Следующий код прекрасно работает в любой среде выполнения.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
В Windows UWP с компиляцией.NET Native код генерирует исключение типа 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
Это пахнет как ошибка в компиляторе или во время выполнения. Что тут происходит? Я что-то пропустил?
1 ответ
Оказывается, это ошибка.
Более подробная информация здесь: https://github.com/dotnet/corert/issues/3565
- Метод ConstructorInfo.Invoke(object[]) в справочной сборке System.Reflection (C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETPortable\v4.5\Profile\Profile78\System.Reflection.dll говорит, что метод Invoke не является виртуальным.
- Где-то кто-то решил, что метод должен быть виртуальным, и они изменили его в реализации. Ссылочная сборка, с которой компилируется код C#, осталась нетронутой.
- Обычно это не имеет большого значения, потому что C# почти всегда вызывает методы виртуально (даже если они не виртуальные), потому что для этого требуется побочный эффект виртуального вызова (сгенерируйте исключение NullReferenceException для null this).
- За исключением оператора распространения нуля, компилятор C# знает, что исключение NullReferenceException не может возникнуть, и он решает выдать обычную инструкцию вызова вместо callvirt, чтобы предотвратить ненужную проверку нуля. Выполнение обычного вызова метода ConstructorInfo.Invoke(object[]) приводит к тому, что мы получаем метод, который никогда не следует вызывать.
Хорошей новостью является то, что ConstructorInfo.Invoke(object[]) больше не является виртуальным в рамках усилий по совместимости с NetStandard 2.0 (предыдущая ссылка была на старый снимок). Эта версия.NET Native еще не поставляется. Единственный обходной путь на данный момент - это не позволить компилятору C# оптимизировать callvirt для вызова, избегая оператора.