尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Core跨平台的奥秘[中篇]:复用之殇

Core跨平台的奥秘[中篇]:复用之殇
📅 发布时间:2026/7/1 20:26:48

在微软发布了第一个针对桌面和服务器平台的.NET Framework之后,它开始 “乐此不疲” 地对这个完整版的.NET Framework进行不同范围和层次的 “阉割” ,进而造就了像Windows Phone、Windows Store、Silverlight和.NET Micro Framework的压缩版的.NET Framework。从这个意义上讲,Mono和它们并没有本质的区别,唯一不同的是Mono真正突破了Windows平台的藩篱。包括Mono在内的这些分支促成了.NET的繁荣,但我们都知道这仅仅是一种虚假的繁荣而已。虽然都是.NET Framework的子集,但是由于它们采用完全独立的运行时和基础类库,这使我们很难开发一个支持多种设备的“可移植(Portable)”应用,这些分支反而成为制约.NET发展的一道道枷锁。至于为什么“可移植(Portable)”.NET应用的开发如此繁琐呢?

所谓由于目标框架的独立性,意味着不仅仅是作为虚拟机的Runtime是根据具体平台特性设计的,作为编程基础的BCL也不能跨平台共享,它为开发者带来的一个最大的问题就是:很难编写能够在各个目标框架复用的代码。比较极端的场景就是:当我们需要为一个现有的桌面应用提供针对移动设备的支持时,我们不得不从头到尾开发一个全新的应用,现有的代码难以被新的应用所复用用。 “代码复用”是软件设计一项最为根本的目标,在不考虑跨平台的前提下,我们可以应用相应的设计模式和编程技巧来实现代码的重用,但是平台之间的差异导致了跨平台代码重用确实具有不小的困难。虽然作得不算非常的理想,但是微软在这方面确实做出了很多尝试,我们不妨先来聊聊目前我们都有哪些跨平台代码复用的解决方案。

目录
一、源代码复用
源文件共享
文件链接
共享项目
二、程序集复用
程序集一致性
Retargetable程序集
类型的转移
三、可移植类库(PCL)

一、源代码复用

对于包括Mono在内的各个.NET Framework平台的BCL来说,虽然在API定义层面上存在一些共同之处,但是由于它们定义在不同的程序集之中,所以在PCL(Portal Class Library)推出之前,针对程序集的共享是不可能实现的,我们只能在源代码层面实现共享。源代码的共享通过在不同项目之间共享源文件的方式来实现,至于具体采用的方式,我们有三种不同的方案供你选择。

源文件共享

对于一个能够多个针对不同目标框架的项目共享的源文件,定义其中的代码也有不少是针对具体某个目标框架的。对于这种代码,我们需要按照如下的方式进行编写,相应的项目以添加编译的方式选择与自身平台相匹配的代码编译道生成的程序集中。

<span style="color:#000000"><span style="background-color:#ffffff"> 1: #if WINDOWS</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 2: <<针对Windows Desktop>></span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 3: #elif SILVERLIGHT</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 4: <<针对 Silverlight>></span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 5: #elif WINDOWS_PHONE</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 6: <<针对Windows Phone>></span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 7: #else</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 8: <<针对其他平台>></span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 9: #endif</span></span>

如果多个针对不同.NET Framework平台的项目文件存在于同一个物理目录下,存在于相同目录下的源文件可以同时包含到这些项目中以实现共享的目的。如下图所示,两个分别针对Silverlight和WPF的项目共享相同的目录,与两个项目文件同在一个目录下的C#文件Shared.cs可以同时被包含到这两个项目之中。

文件链接

当我们采用默认的方式将一个现有的文件添加到当前项目之中的时候,Visual Studio会将目标文件拷贝到项目本地的目录下,所以根本起不到共享的目的。但是针对现有文件的添加支持一种叫做“链接”的方式使添加到项目中的文件指向的依然是原来的地址,我们可以为多个项目添加针对同一个文件的链接以实现源文件跨项目共享。同样还是上面演示分别针对Silverlight和WPF的两个项目,不论项目文件和需要被共享的文件存在于哪个目录下面,我们都可以采用如下图所示的添加文件链接的方式分享这个Shared.cs文件。

共享项目(Shared Project)

普通项目的目的都是组织源文件和其他相关资源并将它们最终编译成一个可被部署的程序集。但是Shared Project这种项目类型则比较特别,它只有对源文件进行组织的功能,却不能通过编译生成程序集,它存在的目的就是为了实现源文件的共享。对于上面我们介绍的两种源代码的共享方式来说,它们都是针对某个单一文件的共享,而Shared Project则可以对多个源文件进行打包以实现批量共享。

如上图所示,我们可以创建一个Shared Project类型的项目Shared.shproj,并将需要共享的三个C#文件(Foo.cs、Bar.cs和Baz.cs)添加进来。我们将针对这个项目的引用同时添加到一个Silverlight项目(SilverlightApp.csproj)和Windows Phone项目(WinPhoneApp.csproj)之中,当我们对这两个项目实施编译的时候,包含在项目Shared.shproj中的三个C#文件会自动作为当前项目的源文件参与编译。

二、程序集复用

我们采用C#、VB.NET这样的编程语言编写的源文件经过编译会生成有IL代码和元数据构成的托管模块,一个或者多个托管模块合并生成一个程序集。程序集的文件名、版本、语言文化和签名的公钥令牌共同组成了它的唯一标识,我们将该标识称为程序集有效名称(Assembly Qualified Name)。除了包含必要的托管模块之外,我们还可以将其他文件作为资源内嵌到程序集中,程序集的文件构成一个“清单(Manifest)”文件来描述,这个清单文件包含在某个托管模块中。

除了作为描述程序集文件构造清单之外,描述程序集的元数据也包含在这个清单文件中。程序集使程序集成为一个自描述性(Self-Describing)的部署单元,除了描述定义在本程序集中所有类型之外,这些元数据还包括对引用自外部程序集的描述。包含在元数据中针对外部程序集的描述是由编译时引用的程序集决定的,引用程序集的名称(包含文件名、版本和签名的公钥令牌)会直接体现在当前程序集的元数据中。针对程序集引用的元数据采用如下的形式(“.assembly extern”)被记录在清单文件中,我们可以看出被记录下来的不仅包含被引用的程序集文件名(“Foo”和“Bar”),还包括程序集的版本,对于签名的程序集(“Foo”)来说,公钥令牌也一并包含其中。

<span style="color:#000000"><span style="background-color:#ffffff"> 1: .assembly extern Foo</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 2: {</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 3: .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) </span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 4: .ver 1:0:0:0</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 5: }</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 6: .assembly extern Bar</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 7: {</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 8: .ver 1:0:0:0</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 9: }</span></span>

包含在当前程序集清单文件中针对引用程序集的元数据是CLR加载目标程序集的依据。在默认的情况下,CLR要求加载与程序集引用元数据完全一致的程序集。具体来说,如果引用的是一个未签名的程序集(“Bar”),那么只要求被加载的程序集具有一致的文件名和版本;如果引用的是一个经过签名的程序集,那么还要求被加载的程序集具有一致的公钥令牌。

在回到《.NET Core跨平台的奥秘[上篇]:历史的枷锁》关于.NET多目标框架独立性的问题。虽然不同的目标框架的BCL在API层面具有很多交集,但是这些API实际上被定义在不同的程序集中,这就导致了在不同的目标框架下共享同一个程序集几乎成了不可能的事情。如果要使跨目标平台程序集复用成为现实,就必须要求CLR在加载程序集时放宽“完全匹配”的限制,因为针对当前程序集清单文件中描述的某个引用程序集来说,在不同的目标框架下可能指向不同的程序集。实际上确实存在这样的一些机制或者策略让CLR加载一个与引用元数据的描述不一致的程序集,我们现在就来聊聊这些策略。

程序集一致性

我们都知道.NET Framework是向后兼容的,也就是说原来针对低版本.NET Framework编译生成的程序集是可以直接在高版本CLR下运行的。我们试想一下这么一个问题:就一个针对.NET Framework 2.0编译生成的程序集自身来说,所有引用的基础程序集的版本在元数据描述中都应该是2.0,如果这个程序集在NET Framework 4.0环境下执行,CLR在决定加载它所依赖程序集的时候,应该选择2.0还是4.0呢?

我们不妨通过实验来获得这个问题的答案。我们利用Visual Studio创建一个针对.NET Framework 2.0的控制台应用(命名为App),并在作为程序入口的Main方法上编写如下一段代码。如下面代码片断所示,我们在控制台上输出了三个基本类型(Int32、XmlDocument和DataSet)所在程序集的全名。

<span style="color:#000000"><span style="background-color:#ffffff"> 1: class Program</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 2: {</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 3: static void Main()</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 4: {</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 5: Console.WriteLine(typeof(int).Assembly.FullName);</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 6: Console.WriteLine(typeof(XmlDocument).Assembly.FullName);</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 7: Console.WriteLine(typeof(DataSet).Assembly.FullName);</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 8: }</span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 9: }</span></span>

直接运行这段程序使之在默认版本的CLR(2.0)下运行会在控制台上输出如下的结果,我们会发现上述三个基本类型所在程序集的版本都是2.0.0.0。也就说在这种情况下,运行时加载的程序集和编译时引用的程序集是一致的。

现在我们在目录“\bin\debug”直接找到以Debug模式编译生成的程序集App.exe,并按照如下的形式修改对应的配置文件(App.exe.config),该配置的目的在于将启动应用时采用的运行时(CLR)版本从默认的2.0切换到4.0。

<span style="color:#000000"><span style="background-color:#ffffff"> 1: <configuration></span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 2: <startup></span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 3: <supportedRuntime&nbsp;version="<span style="color:#ff0000">v4.0</span>"/></span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 4: </startup></span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 5: </configuration></span></span>

或者:

<span style="color:#000000"><span style="background-color:#ffffff"> 1: <configuration></span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 2: <startup></span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 3: <requiredRuntime&nbsp;version="<span style="color:#ff0000">v4.0</span>"/></span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 4: </startup></span></span>
<span style="color:#000000"><span style="background-color:#ffffff"> 5: </configuration></span></span>

在无需重新编译(确保运行的依然是同一个程序集)直接运行App.exe,我们会在控制台上得到如下图所示的输出结果,可以看到三个程序集的版本全部变成了4.0.0.0,也就说真正被CLR加载的这些基础程序集是与当前CLR的版本相匹配的。

这个简单的实例体现了这么一个特征:运行过程中加载的.NET Framework程序集(承载FCL的程序集)是由当前运行时(CLR)决定的,这些程序集的版本总是与CLR的版本相匹配。包含在元数据中的程序集信息提供目标程序集的名称,而版本则由当前运行的CLR来决定,我们将这个重要的机制称为“程序集一致性(Assembly Unification)”,下图很清晰地揭示了这个特性。

相关新闻

  • openEuler/bigdata未来展望:大数据技术趋势与社区发展路线图
  • 百度网盘高速下载终极指南:告别限速的专业解决方案
  • SQL注入实战:从手工注入到sqlmap高级绕过与防御

最新新闻

  • 普陀 青浦 项目本地运行和线上部署注意点
  • 插拔式外部记忆层:为任意大模型添加可持久化工作记忆
  • RouteLLM:轻量开源的语义感知大模型路由系统
  • LongNet稀疏注意力原理与长上下文工程实践
  • 当你的输入法词库被困在不同平台时,这个工具能做什么?
  • Claude稳定性归零层:上下文感知推理如何从显式控制变为隐式内化

日新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号