dotnet 根据基线包版本实现库版本兼容


本文来告诉大家如何根据 基线包版本 的功能来实现自动在构建过程中,告诉开发者,当前版本是否存在不兼容旧版本的变更。其不兼容变更包括二进制中断变更和 API 不兼容变更和源代码中断变更。可以让库开发者花更少的精力在测试兼容性上

今天看到了队长推送的 .NET 6新特性试用 Nuget包验证 博客,才回忆起此功能。这个功能是给库和框架开发者使用的,用于处理多版本兼容性问题

背景

只有对一个库或框架准备对外发布且长期维护,以及期望给其他开发者使用时,才需要考虑库或框架的兼容性问题。越是开发底层的库,兼容性问题就越加重要。此重要性,只有自己参与开发,踩够坑之后,才能有所体会

换句话说,判断一位开发者是不是库或框架的老司机开发者,可以通过他的兼容性处理上来看出。哈哈,需要说明的是,不是所有老司机开发者都是库或框架开发方向的,这是判断有经验的开发者的充分不必要条件

开始之前,先聊聊什么是兼容性问题。兼容性可以分为以下不兼容变更:

  • 源代码中断变更和 API 不兼容变更:简单说 API 不兼容变更,就是更改了开放出去的 API 签名。对于使用了此库或框架的开发者来说,如果更新到新的版本,为了适配变更,就 必须 更改源代码
  • 二进制中断变更:尽管是不用更改源代码就能适配新版本,但是如果没有重新构建,提示替换 DLL 文件,那将会在运行程序时挂掉。例如给某个公开的函数加上了一个默认参数,尽管默认参数的添加,在源代码上是可以不做任何变更就可以用上新的版本,然而如果没有重新构建,只是将新版本的 DLL 或 EXE 替换过去,在运行的时候将提示找不到方法
  • 行为中断变更:某个行为被更改,执行逻辑和之前不兼容。例如原本一个方法能好好工作,现在调用了,进程就退出了等等

此外,还有更换了底层运行时框架的变更等,但这些就不在本文讨论范围了

更多请参阅官方文档的详细描述: 重大更改和 .NET 库 Microsoft Docs

对于使用库或框架的开发者来说,一方面又期望用上新版本的强大功能,另一方面又怕有不兼容的变更,需要花费大量的精力在更新上面。如果库或框架的开发者,可以保持好兼容性,那么升级版本是一个很轻松的事情

对于咱 dotnet 系的大部分库或框架开发者来说,在开发过程中,考虑兼容性是一个必备的选项。那如果真的需要变更 API 了呢?问题也不大,别忘了咱还有版本号规则

版本号规则

基本所有 dotnet 系上,正经的库和框架都会遵循约定的版本号规则,从而让开发者在使用任何库的时候,通过版本号都能明确其中的含义,决定自己是否应该升级到最新版本

无异议的版本规则是,版本号由四个部分组成,分为 主版本号.次版本号.构建号.修订号 四个部分。其中的 构建号 和 修订号 都可忽略不写。各个部分的含义如下

  • 主版本号: major version , 此版本如有变更,如从 1 升级到 2 的版本,代表着有重大更改。如存在不兼容的 API 或源代码更改,或者机制性,或者行为上的变更。大部分情况下,有主版本的变更就意味着需要在升级完成进行适配的工作
  • 次版本号: minor version,此版本如有变更,代表着有新增的 API 定义或者是较大的但是兼容的修订,如修大 Bug 等,大部分情况下是不需要进行任何的适配工作
  • 构建号: build number,此版本如有变更,代表着有小的更改,如修 Bug 等,不改变对外公开的约定的行为。升级新版本不需要进行任何的适配工作
  • 修订号: revision,此版本大部分情况是给构建工具链编写的,开发者人类是很少需要变更此。升级到此新版本,无须进行任何适配

此外,有一些库毕竟激进,需要发布预览版本等,可以考虑采用语义版本号的方法,请看 语义版本号(Semantic Versioning) - walterlv - cscode.net

通过如上的说明,可以了解到,如果不想刷主版本号,那就要求库或框架保持兼容旧版本。兼容旧版本需要在开发时,投入精力了解是否存在不兼容的更改,然而纯依靠手动去阅读代码了解是否存在不兼容的变更,当然是不靠谱的。本文将告诉大家如何使用 EnablePackageValidationPackageValidationBaselineVersion 功能,自动让构建工具告诉开发者当前的更改是否存在不兼容的更改,从而更好保持库或框架的兼容

使用方法

一如既往的简单,只需要在项目文件上,添加如下代码即可

    <EnablePackageValidation>true</EnablePackageValidation>
    <PackageValidationBaselineVersion>基于的版本号</PackageValidationBaselineVersion>

例如当前是 2.0.0 的版本,期望进行对 1.0.0 包版本的兼容性测试,可以将 PackageValidationBaselineVersion 的值更改为 1.0.0 版本,如下面代码

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <PackageVersion>2.0.0</PackageVersion>
    <EnablePackageValidation>true</EnablePackageValidation>
    <PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion>
  </PropertyGroup>

如此,在存在中断性(也就是不兼容,需要代码适配)变更时,在会在构建时给出提示,同时让构建不通过

例子

如何更好的使用此功能,还请让我用一个例子来告诉大家。此例子完全从 官方文档 抄的

在第一个版本时,作为 1.0.2 的版本的 NuGet 包,已对外发布。在进行 1.1.0 版本开发时,期望能做到完全的兼容第一个版本。利用 PackageValidationBaselineVersion 的功能,在 csproj 项目文件上,加上如下代码

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <PackageVersion>1.1.0</PackageVersion>
    <EnablePackageValidation>true</EnablePackageValidation>
    <PackageValidationBaselineVersion>1.0.2</PackageValidationBaselineVersion>
  </PropertyGroup>

</Project>

通过在 PackageValidationBaselineVersion 执行定了基线包版本为 1.0.2 即可采用此指定的版本进行基线包版本对比。例如几周后,你的任务是为库添加对连接超时的支持,代码的 Connect 方法目前如下所示:

public static HttpClient Connect(string url)
{
    // ...
}

由于连接超时是一个高级配置设置,因此你认为可以添加一个可选参数,更改如下:

public static HttpClient Connect(string url, TimeSpan timeout = default)
{
    // ...
}

更改之后,构建过程可以正常,但是在打包的时候,将会收到如下提示,打包失败

D:\demo>dotnet pack
Microsoft (R) Build Engine version 17.0.0-preview-21460-01+8f208e609 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
  You are using a preview version of .NET. See: https://aka.ms/dotnet-core-preview
  PackageValidationThrough -> D:\demo\bin\Debug\net6.0\PackageValidationThrough.dll
  Successfully created package 'D:\demo\bin\Debug\PackageValidationThrough.2.0.0.nupkg'.
C:\Program Files\dotnet\sdk\6.0.100-rc.1.21463.6\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Compatibility.Common.targets(32,5): error CP0002: Member 'A.B.Connect(string)' exists on [Baseline] lib/net6.0/PackageValidationThrough.dll but not on lib/net6.0/PackageValidationThrough.dll [D:\demo\PackageValidationThrough.csproj]

或者中文版本的提示如下

用于 .NET 的 Microsoft (R) 生成引擎版本 17.0.0-preview-21501-01+bbcce1dff
版权所有(C) Microsoft Corporation。保留所有权利。

  正在确定要还原的项目…
  所有项目均是最新的,无法还原。
  你正在使用 .NET 的预览版。请查看 https://aka.ms/dotnet-core-preview
  NallcearreyiHernareferkear -> C:\lindexi\NallcearreyiHernareferkear\NallcearreyiHernareferkear\bin\Debug\net6.0\NallcearreyiHernareferkear.dll
  已成功创建包“C:\lindexi\NallcearreyiHernareferkear\NallcearreyiHernareferkear\bin\Debug\NallcearreyiHernareferkear.2.0.0.nupkg”。
C:\Program Files\dotnet\sdk\6.0.100-rc.2.21505.57\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Compatibility.Common.targets(32,5): error CP0002: Member 'NallcearreyiHernareferkear.Foo.Connect(string)' exists on [Baseline] lib/net6.0/NallcearreyiHernareferkear.dll but not on lib/net6.0/NallcearreyiHernareferkear.dll [C:\lindexi\NallcearreyiHernareferkear\NallcearreyiHernareferkear\NallcearreyiHernareferkear.csproj]

如此通过打包失败,提示的 CP0002 失败,可以了解到,自己没有做到让当前版本对写入到 PackageValidationBaselineVersion 的兼容。此时要做的事情,要么是废弃掉对 PackageValidationBaselineVersion 的兼容,也就是删除此属性,同时升级主版本号,告诉其他开发者,当前版本存在不兼容。要么是更改 API 定义,更改到兼容

例如以上的代码,虽然加上了一个默认参数,可以实现到源代码兼容。但是大家都知道,这是二进制不兼容的,如果直接替换 DLL 文件,而不经过编译,将会在运行的过程中,因为找不到对应的方法而失败

什么情况下会遇到没有重新构建,只是替换 DLL 文件而已?在于是其他底层库的依赖引用,例如再有另一个库 C 也引用了此,而库 C 打出的 NuGet 包被最终项目所引用。当最终项目升级版本时,由于 Connect 方法被更改,从而让库 C 里面的对应逻辑找不到方法,而在运行时失败

因此为了做到这部分的兼容,可以考虑作为重载的方法更改,更改如下

public static HttpClient Connect(string url)
{
    return Connect(url, Timeout.InfiniteTimeSpan);
}

public static HttpClient Connect(string url, TimeSpan timeout)
{
    // ...
}

这样进行重新打包,即可看到打包成功,兼容 PackageValidationBaselineVersion 的 1.0.2 版本

原理

此功能是依托于 NuGet 包发布而拿到指定版本号规则的,和 使用基于 Roslyn 的 Microsoft.CodeAnalysis.PublicApiAnalyzers 来追踪项目的 API 改动,帮助保持库的 API 兼容性 - walterlv 的方法是完全不相同的

本文介绍的方法,是在 PackageValidationBaselineVersion 里面,声明的包版本,在构建过程中,通过 NuGet 去拉取对应的版本,接着通过 DLL 导出类型的对比,从而了解是否存在不兼容的变更

也就是说在 PackageValidationBaselineVersion 里面写入的版本号,要求是可以在 NuGet 源里面(无论是 nuget.org 源,还是你的私有的源,还是你的本机文件夹都可以)拉到对应的版本。由此版本里面的 DLL 执行具体的对比逻辑。这也就要求了此功能只能用在简单的 NuGet 上,对于很多上了黑科技的 NuGet 包是无法执行的。例如使用 SourceYard 打包的源代码包

本文介绍的方法,对比使用基于 Roslyn 的 Microsoft.CodeAnalysis.PublicApiAnalyzers 来追踪项目的 API 改动,帮助保持库的 API 兼容性 的方法来说,优势在于不需要带上 PublicAPI.Unshipped.txtPublicAPI.Shipped.txt 文件,此两个文件夹特别好在团队开发时进行冲突,而且需要进行手动管理。但是缺点在于本文介绍的方法功能单一,也依赖 NuGet 包版本

代码

本文以上的代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 95692dcaabfb0d143dffa8e31c0a1ad00e7c2e74

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

获取代码之后,进入 NallcearreyiHernareferkear 文件夹

 

版权声明:本文为开发框架文库发布内容,转载请附上原文出处连接
C/S框架网
上一篇:C#获取Windows10屏幕的缩放比例
下一篇:程序员在面试时,如何回答未来规划的问题
评论列表

发表评论

评论内容
昵称:
关联文章

dotnet 根据基线版本实现版本兼容
C#.NET C/S结构版本自动升级解决方案之升级实现
GitBlit 创建版本,VS创建本地存储并推送源码到远程版本
GoF设计模式:适配器模式(Adapter Pattern)—不兼容结构的协调
C/S框架新功能:自动检测升级并强制关闭应用程序进行版本升级
CSFramework软件版本自动升级程序 - 升级业务逻辑关系图
GitBlit - 使用克隆仓库方式创建、推送VS解决方案源码添加到版本
CSFramework.DB基于ADO.NET多数据库底层组件(MSSQL+Oracle+MySQL)
GitBlit - 创建、推送VS解决方案源码添加到版本
C/S自动升级软件之下载升级策略设计|C/S框架网
CSFramework.AutoUpgrader上传大文件升级测试报告
百度地图API应用 - 根据地址查询经纬度
CSFramework软件版本自动升级程序 - 升级策略接口说明
怎样对软件项目进行逻辑分层分割模块(类)?
C#.Net版本自动更新程序及3种策略实现
.NET Framework V4.0版本经典模式和集成模式的区别 - 阿里云虚拟主机.NET版本设置
开发框架多语言Language.dll (支持简/繁/英三种语言)
C#.Net局域网版本自动升级解决方案(原创)
运输管理模块 - 中转外单(中转单) - TMS - 物流运输管理系统
vs 托管兼容模式不支持“编辑并继续”

热门标签
.NET5 .NET6 .NET7 APP Auth-软件授权注册系统 Axios B/S B/S开发框架 Bug Bug记录 C#加密解密 C#源码 C/S CHATGPT CMS系统 CodeGenerator CSFramework.DB CSFramework.EF CSFrameworkV1学习版 CSFrameworkV2标准版 CSFrameworkV3高级版 CSFrameworkV4企业版 CSFrameworkV5旗舰版 CSFrameworkV6.0 DAL数据访问层 Database datalock DbFramework Demo教学 Demo下载 DevExpress教程 DOM EF框架 Element-UI EntityFramework ERP ES6 Excel FastReport GIT HR IDatabase IIS JavaScript LINQ MES MiniFramework MIS NavBarControl Node.JS NPM OMS ORM PaaS POS Promise API Redis SAP SEO SQL SQLConnector TMS系统 Token令牌 VS2022 VSCode VUE WCF WebApi WebApi NETCore WebApi框架 WEB开发框架 Windows服务 Winform 开发框架 Winform 开发平台 WinFramework Workflow工作流 Workflow流程引擎 版本区别 报表 踩坑日记 操作手册 代码生成器 迭代开发记录 基础资料窗体 架构设计 角色权限 开发sce 开发技巧 开发教程 开发框架 开发平台 开发指南 客户案例 快速搭站系统 快速开发平台 秘钥 密钥 权限设计 软件报价 软件测试报告 软件简介 软件开发框架 软件开发平台 软件开发文档 软件体系架构 软件下载 软著证书 三层架构 设计模式 生成代码 实用小技巧 收钱音箱 数据锁 数据同步 微信小程序 未解决问题 文档下载 喜鹊ERP 喜鹊软件 系统对接 详细设计说明书 行政区域数据库 需求分析 疑难杂症 蝇量级框架 蝇量框架 用户管理 用户开发手册 用户控件 在线支付 纸箱ERP 智能语音收款机 自定义窗体 自定义组件 自动升级程序