将 Windows 8.x 应用迁移到 .NET Native

.NET Native 为 Microsoft Store 或开发人员计算机上的应用提供静态编译。 这不同于实时 (JIT) 编译器或本地映像生成器 (Ngen.exe) 在该设备上为 Windows 8.x 应用(前称为“Microsoft Store 应用”)执行的动态编译。 虽然存在差异,但 .NET Native 尝试保持与 .NET for Windows 8.x 应用的兼容性。 大多数情况下,.NET for Windows 8.x 应用功能也适用于 .NET Native。 然而,在某些情况下,你可能会遇到行为变更。 本文档讨论了标准 .NET for Windows 8.x 和 .NET Native 在以下方面的差异:

常规运行时差异

  • 当应用在公共语言运行时 (CLR) 上运行时,由 JIT 编译器引发的异常(如 TypeLoadException)通常会导致 .NET Native 处理时出现编译时错误。

  • 请勿从一个应用的 UI 线程调用 GC.WaitForPendingFinalizers 方法。 这可能会导致 .NET Native 死锁。

  • 请勿依赖静态类构造函数调用排序。 在 .NET Native 中,调用顺序不同于标准运行时的顺序。 (即使可以使用标准运行时,你也不应依赖静态类构造函数的执行顺序。)

  • 不在任何线程上执行调用(例如, while(true);)的无线循环可能会使应用异常终止。 同样,长期或无限等待也可能使应用异常终止。

  • 在 .NET Native 中,某些泛型初始化循环不会引发异常。 例如,以下代码导致标准 CLR 上 TypeLoadException 发生了一个异常。 在 .NET Native 中则不会。

    using System;
    
    struct N<T> {}
    struct X { N<X> x; }
    
    public class Example
    {
       public static void Main()
       {
          N<int> n = new N<int>();
          X x = new X();
       }
    }
    
  • 在某些情况下,.NET Native 提供 .NET Framework 类库的不同实现。 从一个方法返回的对象总是实施返回类型的成员。 然而,由于它的后备实施是不同的,你可能无法像在其他 .NET Framework 平台上的操作一样将其转换到相同的类型集。 例如,在某些情况下,你可能无法将 IEnumerable<T>TypeInfo.DeclaredMembers 等方法返回的 TypeInfo.DeclaredProperties 界面对象转换为 T[]

  • 默认情况下,.NET for Windows 8.x 应用中不会启用 WinInet 缓存,但它会在 .NET Native 上启用。 这提高了性能但会影响工作集。 开发者无需执行任何操作。

动态编程差异

.NET Native 静态链接 .NET Framework 中的代码,使代码成为应用本地代码,以实现最大性能。 然而,由于二进制大小必须保持较小,这就使得整个 .NET Framework 无法进入。 .NET Native 编译器解决了此限制,通过使用一个删除了对未使用代码的引用的依赖项化简器实现。 但是,当不能在编译时静态推断信息,而是在运行时动态检索,.NET Native 可能无法维护或生成某些类型信息和代码。

.NET Native 支持反射和动态编程。 但是,并非所有类型都可以标记为反射,因为这会使生成的代码大小过大(尤其是因为支持 .NET Framework 中的公共 API 反射)。 .NET Native 编译器可以明智地选择哪些类型应支持反射,并且它将保留元数据并仅为这些类型生成代码。

例如,绑定数据需要一个应用能够将属性名映射到函数。 在 .NET for Windows 8.x 应用中,公共语言运行时自动使用反射为托管类型和公开可用的本机类型提供此功能。 在 .NET Native 中,编译器将自动包含要将数据绑定到的类型的元数据。

.NET Native 编译器还可以处理常用的泛型类型(如 List<T>Dictionary<TKey,TValue>),而无需任何提示或指令。 动态 关键字在某些限制内也受到支持。

注意

在将应用移植到 .NET Native 时,应彻底测试所有动态代码路径。

.NET Native 的默认配置对于大多数开发人员来说已经足够,但某些开发人员可能希望使用运行时指令 (.rd.xml) 文件来微调其配置。 此外,在某些情况下,.NET Native 编译器无法确定哪些元数据必须可用于反射,并依赖于提示,尤其是在以下情况下:

  • Type.MakeGenericTypeMethodInfo.MakeGenericMethod 等一些构造无法静态确定。

  • 因为编译器无法确定实例化,因此你想要反射到的泛型类型必须通过运行时指令来指定。 这并不仅是因为所有的代码必须包含在内,还因为在泛型类型上的反射会形成一个无限循环(例如,当某泛型方法在某泛型类型上调用时)。

注意

运行时指令是在运行时指令 (.rd.xml) 文件中定义的。 有关使用此文件的常规信息,请参阅入门。 有关运行时指令的信息,请参阅 Runtime Directives (rd.xml) Configuration File Reference

.NET Native 还包含分析工具,可帮助开发人员确定默认集外的哪些类型应支持反射。

.NET for Windows 8.x 应用和 .NET Native 之间还有许多其他与反射相关的行为差异。

在 .NET Native 中:

不受支持的方案和 API

以下部分列出了不受一般开发支持的方案和 API、互操作以及 HTTPClient 和 Windows Communication Foundation (WCF) 等技术:

常规开发差异

值类型

  • 如果你替代了一个值类型的 ValueType.EqualsValueType.GetHashCode 方法,请勿调用基类实施。 在 .NET for Windows 8.x 应用中,这些方法依赖于反射。 编译时,.NET Native 会生成一个不依赖运行时反射的实现。 这意味着,如果不重写这两种方法,则它们将按预期方式工作,因为 .NET Native 会在编译时生成实现。 然而,替代这些方法并调用基类实施会产生一个异常。

  • 不支持大于 1 兆字节的值类型。

  • 在 .NET Native 中,值类型不能有无参数的构造函数。 (C# 和 Visual Basic 禁止对值类型使用无参数构造函数。但是,可以在 IL.)

数组

泛型

  • 无限泛型类型扩展会导致编译器错误。 例如,该代码无法编译:

    class A<T> {}
    
    class B<T> : A<B<A<T>>>
    {}
    

指针

  • 指针阵列不受支持。

  • 你无法使用反射来获取或设置一个指针字段。

序列化

KnownTypeAttribute(String) 特性不受支持。 使用 KnownTypeAttribute(Type) 特性。

资源

不支持使用带 EventSource 类的本地化资源。 EventSourceAttribute.LocalizationResources 属性无法定义本地化资源。

委派

Delegate.BeginInvokeDelegate.EndInvoke 不受支持。

其他 API

  • 如果 GuidAttribute 属性未应用于该类型,TypeInfo.GUID 属性将引发 PlatformNotSupportedException 异常。 GUID 主要用于 COM 支持。

  • DateTime.Parse 方法会正确分析在 .NET Native 中包含短日期的字符串。 但是,它不会保持与日期和时间分析中某些更改的兼容性。

  • BigInteger.ToString("E")在 .NET Native 中正确舍入。 在某些版本的 CLR 中,结果字符串会缩短,而不是舍入。

HttpClient 差异

在 .NET Native 中,HttpClientHandler 类在内部使用 WinINet(通过 HttpBaseProtocolFilter 类),而不使用标准 .NET for Windows 8.x 应用中使用的 WebRequestWebResponse 类。 WinINet 并不支持 HttpClientHandler 类支持的所有配置选项。 因此:

  • HttpClientHandler 上的一些功能属性在 .NET Native 上返回 false,而这些属性在标准 .NET for Windows 8.x 应用中返回 true

  • 某些配置属性 get 访问器始终在 .NET Native 上返回一个固定值,此值不同于 .NET for Windows 8.x 应用中的默认可配置值。

某些额外的行为差异涵盖在以下子节中。

Proxy

HttpBaseProtocolFilter 类不支持基于每个请求配置或重写代理。 这意味着,.NET Native 上的所有请求都使用系统配置的代理服务器或无代理服务器,具体取决于 HttpClientHandler.UseProxy 属性的值。 在 .NET for Windows 8.x 应用中,代理服务器由 HttpClientHandler.Proxy 属性定义。 在 .NET Native 中,将 HttpClientHandler.Proxy 设置为除 null 以外的值将引发 PlatformNotSupportedException 异常。 HttpClientHandler.SupportsProxy 属性在 .NET Native 上返回 false,而在标准 .NET Framework for Windows 8.x 应用中返回 true

自动重定向

HttpBaseProtocolFilter 类不允许配置自动重新定向的最大数目。 默认情况下,在标准 .NET for Windows 8.x 应用中,HttpClientHandler.MaxAutomaticRedirections 属性的值为 50,并且可以修改。 在 .NET Native 上,此属性的值为 10,修改该值会引发 PlatformNotSupportedException 异常。 属性 HttpClientHandler.SupportsRedirectConfiguration 在 .NET Native 上返回 false,而在 .NET for Windows 8.x 应用中返回 true

自动解压缩

.NET for Windows 8.x 应用允许将 HttpClientHandler.AutomaticDecompression 属性设置为 DeflateGZip,同时设置为 DeflateGZip,或设置为 None。 .NET Native 仅支持 DeflateGZipNone。 尝试仅将 AutomaticDecompression 属性设置为 DeflateGZip 会自动将其设置为 DeflateGZip

Cookie

Cookie 处理由 HttpClient 和 WinINet 同时执行。 来自 CookieContainer 的 Cookies 与 WinINet cookie 缓存结合。 从 CookieContainer 删除 cookie 可防止 HttpClient 发送 cookie,但如果 cookie 已被 WinINet 检测到且 cookies 未被用户删除,则 WinINet 将会发送 cookie。 无法通过编程的方式使用 HttpClientHttpClientHandlerCookieContainer API 从 WinINet 中删除 cookie。 将 HttpClientHandler.UseCookies 属性设置为 false 只会导致 HttpClient 停止发送 cookies;WinINet 仍可能会在请求中包括 cookies。

凭据

在 .NET for Windows 8.x 应用中,HttpClientHandler.UseDefaultCredentialsHttpClientHandler.Credentials 属性独立工作。 此外, Credentials 属性接受实施 ICredentials 接口的任意对象。 在 .NET Native 中,将 UseDefaultCredentials 属性设置为 true 会导致 Credentials 属性变为 null。 此外, Credentials 属性只能设置为 nullDefaultCredentials或类型 NetworkCredential的对象。 将其他任意 ICredentials 对象(其中最常见的是 CredentialCache)分配至 Credentials 属性都会导致 PlatformNotSupportedException

其他不受支持或不可配置的功能

在 .NET Native 中:

互操作差异性

弃用的 API

多个带有托管代码的不常使用的互操作性 API 已弃用。 与 .NET Native一起使用时,这些 API 可能会引发 NotImplementedExceptionPlatformNotSupportedException 异常,或导致编译器错误。 在 .NET for Windows 8.x 中,这些 API 被标记为过时,虽然调用这些 API 会生成编译器警告,但不会生成编译器错误。

用于 VARIANT 封送的已弃用 API 包括:

UnmanagedType.Struct 受支持,但在某些情况下会引发异常,如与 IDispatchbyref 变量一起使用时。

用于 IDispatch 支持的已弃用 API 包括:

用于经典 COM 事件的已弃用 API 包括:

System.Runtime.InteropServices.ICustomQueryInterface 接口中已弃用的 API(在 .NET Native 中不受支持)包括:

其他不受支持的互操作功能包括:

很少使用的封送 API:

平台调用和 COM 互操作兼容性

大多数平台调用和 COM 互操作方案在 .NET Native 中仍受支持。 特别地,Windows Runtime (WinRT) API 的所有互操作和 Windows Runtime 所需的所有封送都受支持。 这包括针对以下内容的封送支持:

但是,.NET Native 不支持以下操作:

使用反射来调用平台调用方法不受支持。 你可以巧妙绕过这种限制,具体做法是将方法调用包装在另一种方法中,并使用反射调用包装方法。

.NET for Windows 8.x 应用 API 的其他差异

本部分列出了其他 API,这些 API 在 .NET Native 中不受支持。 不支持的 API 的最大集是 Windows Communication Foundation (WCF) API。

DataAnnotations (System.ComponentModel.DataAnnotations)

System.ComponentModel.DataAnnotationsSystem.ComponentModel.DataAnnotations.Schema* 命名空间中的类型在 .NET Native 中不受支持。 这些类型包括 .NET for Windows 8.x 应用中的以下类型:

Visual Basic

Visual Basic 当前不支持 .NET Native。 Microsoft.VisualBasicMicrosoft.VisualBasic.CompilerServices 命名空间中的以下类型在 .NET Native 中不可用:

反射上下文(System.Reflection.Context 命名空间)

System.Reflection.Context.CustomReflectionContext 类在 .NET Native 中不受支持。

RTC (System.Net.Http.Rtc)

System.Net.Http.RtcRequestFactory 类在 .NET Native 中不受支持。

Windows Communication Foundation (WCF) (System.ServiceModel.*)

System.ServiceModel.* 命名空间中的类型在 .NET Native 中不受支持。 其中包括以下类型:

序列化程序中的差异

DataContractSerializerDataContractJsonSerializerXmlSerializer 类的序列化和反序列化有关的以下差异:

Visual Studio 差异

异常和调试

运行在调试器中使用 .NET Native 编译的应用时,将针对以下异常类型启用第一机会异常:

生成应用

使用 Visual Studio 默认使用的 x86 构建工具。 我们不推荐使用 AMD64 MSBuild 工具(位置路径为 C:\Program Files (x86)\MSBuild\12.0\bin\amd64),因为这些工具会导致构建问题。

探查器

  • Visual Studio CPU 探查器和 XAML 内存探查器不会正确显示 Just-My-Code。

  • XAML 内存探查器不会精确显示托管的堆数据。

  • CPU 探查器不会正确区分模块和显示前缀的功能名。

单元测试库项目

不支持对 Windows 8.x 应用项目的单元测试库启用 .NET Native,这会导致项目无法生成。

另请参阅