目录

二进制不兼容

害怕承诺

优点

缺点

强大的命名难题

二进制不兼容的类型

二进制兼容性和源代码兼容性

怎么不发疯

所有美好的事物都必须走到尽头


这是.NET库和向后兼容系列技术的第三篇文章:

  • 第1部分——简介和行为不兼容
  • 第2部分——源代码不兼容
  • 第三部分——这篇文章

本系列的第1部分和第2部分将讨论如何通过更改行为(行为不兼容)或导致编译错误(源代码不兼容)来以不会破坏客户应用程序的方式更新库的代码。行为上的不兼容是偷偷摸摸的,必须不惜一切代价避免。源代码的不兼容是客户要解决的难题,应将其最小化。

二进制不兼容

当用户通过将.dll文件放入应用程序文件夹而不重新编译应用程序本身来更新您的库时,会发生第三种不兼容性。该更新可以由应用程序的作者甚至最终用户执行。

害怕承诺

您需要做的第一件事是确定是否应允许这种形式的更新。允许和禁止它都有优点和缺点。您的选择将影响您编写库的方式和文档编制方式,因此您应尽早决定。

优点

  • 特别是对于安全补丁,最终用户可以更新您的库,而不必等待应用程序的作者发布新版本。
  • 如果您的库被广泛使用,则有人可以编写具有两个依赖关系的应用程序,这些依赖关系使用您库的不同版本。如果您不允许两个依赖项透明地使用较新版本,则会出现问题

缺点

  • 确保二进制兼容性非常困难,因此如果您不小心,很可能会违背诺言
  • 在客户更新您的库时,强迫客户重建他们的应用程序将导致他们运行测试,这些测试可能会捕获行为不兼容性。您甚至可以愿意引入源代码不兼容的情况,以迫使客户解决库行为的变化
  • 不保证二进制兼容性将使您在设计库的新版本时拥有更大的自由度,并且随着时间的推移可能会带来更好的用户体验

强大的命名难题

二进制兼容性仅在库的.dll文件可以用较新版本替换时才有用,否则,行为和源代码兼容性都是您所需要担心的!值得一提的是,对库的强命名可能会阻止用户用较新的版本替换它。

.NET的一个令人困惑的功能,包括使用密码密钥对程序集进行签名,该程序集根据其名称和版本为其分配一个唯一的标识。

足够奇怪的是,即使涉及加密,也不应依赖强命名来保证安全性。

点击这里查看微软的指导。

如果您不强命名您的库,那么您是清白的。只是知道,如果潜在客户想强行命名其程序集,将无法使用您的库。

如果要强命名库,通常的方法是保持程序集版本不变,除非您确实要进行重大更改。

<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>netstandard2.0</TargetFramework><Version>1.0.1</Version><FileVersion>1.0.1</FileVersion><!-- Don't increase the AssemblyVersion unless you are making breaking changes--><AssemblyVersion>1.0.0</AssemblyVersion><SignAssembly>true</SignAssembly><AssemblyOriginatorKeyFile>SNKey.pfx</AssemblyOriginatorKeyFile></PropertyGroup>
</Project>

即使程序集是强命名的,这也允许所有不同版本可以互换。Microsoft本身意识到这是令人困惑和麻烦的,并更改了.NET Core中严格的程序集版本加载,使其更加轻松。如果您的库以.NET Standard为目标,则将根据.NET Framework.NET Core使用程序集加载规则,具体取决于哪个应用程序使用它。

二进制不兼容的类型

二进制不兼容有两种类型:导致异常的二进制不兼容和导致行为更改的二进制不兼容。

由二进制不兼容引起的典型异常是TypeLoadExceptionMissingMethodException。它们特别难以捕获,因为在CLR首次尝试从您的库中访问受影响的类型或成员时会抛出它们,该时间早于首次引用该类型或成员的实际代码行。

与二进制不兼容相关的行为更改与常规行为不兼容有所不同,因为可以通过重新编译使用您的库的代码来解决这些问题。这可能会使用户感到困惑,因为用户可能会尝试在其应用程序的新编译调试版本上重现该问题,并且不会受到影响。

一个有趣的示例是重新排列枚举的条目。由于.NET会自动为枚举条目分配一个数值,并且在编译时将此值嵌入到使用的程序集中,因此对枚举重新排序既会导致由于二进制不兼容而导致的行为更改,以及在重新编译应用程序时发生的不同的行为变化!

以下代码

static void Main(string[] args)
{Console.WriteLine($"This is Enum1.a: '{Enum1.a}'. It's value is 0: '{(int)Enum1.a}'.");
}//This must be in a separate library
public enum Enum1
{a, b
}

通常会打印

This is Enum1.a: 'a'. It's value is 0: '0'.

如果我们将库中的枚举定义更改为

public enum Enum1
{b, a
}

该应用程序现在将打印

This is Enum1.a: 'b'. It's value is 0: '0'.

这是因为Enum1.a在应用程序的程序集中被编译为0。因此,当我们切换到新库而不进行重新编译时,将保留0值,但现在它对应于Enum1.b

如果我们重新编译应用程序,那么我们现在将有第三种不同的行为!

This is Enum1.a: 'a'. It's value is 0: '1'.

二进制兼容性和源代码兼容性

可能会认为所有二进制不兼容性,至少是导致TypeLoadExceptionMissingMethodException二进制不兼容也是源代码不兼容性。这不是真的。

以下是源代码兼容但二进制不兼容的代码更改列表。

之前

public class Class1
{public static void F(){Console.WriteLine("1");}
}

之后

public class Class1
{//Adding a parameter with a default//results in MissingMethodException.//Create an overloaded method instead.public static void F(int n = 1){Console.WriteLine(n);}
}

之前

public class Class1
{public int Number = 0;
}

之后

public class Class1
{//Changing a field into a property will//results in MissingFieldExceptionpublic int Number { get; set; }  = 0;
}

之前

public interface IFoo
{void F();
}

之后

public interface IFooBase
{void F();
}//Moving an interface member to a base
//interface results in
//MissingMethodException
public interface IFoo : IFooBase
{
}

大多数源代码不兼容也是二进制不兼容。很少有例外。

之前

public class Class1
{public static void F(int n){Console.WriteLine(n);}
}

之后

public class Class1
{//Changing parameter names break//compilation if your customer uses//named argumentspublic static void F(int x){Console.WriteLine(x);}
}

某些行为更改仅在重新编译时生效。

之前

public class Class1
{public static void F(int n = 1){Console.WriteLine(n);}
}

之后

public class Class1
{//Default values are embedded in the//calling assembly so this change//require recompilation to show an//effectpublic static void F(int n = 2){Console.WriteLine(n);}
}

之前

public class Class1
{public static void Print(object o){Console.WriteLine(o);}
}

之后

public class Class1
{public static void Print(object o){Console.WriteLine(o);}//The new overload would be used only//by an application compiled against//the new librarypublic static void Print(object[] o){Console.WriteLine(string.Join("; ", o));}
}

怎么不发疯

由于二进制兼容性和源代码兼容性之间的关系是如此复杂,因此我强烈建议您:

  1. 根本不保证二进制兼容性
  2. 或保证二进制和源代码兼容性。

现在是时候回去重新阅读文章开头的优点/缺点部分了。

好消息是,尽管永远不能完全保证源代码兼容性(请参阅第2部分),但实际上二进制文件兼容性是完全可以实现的。坏消息是,根本不知道什么是二进制兼容的,什么不是!

幸运的是,测试更改的类型是否向后兼容非常容易:

  1. 用两个项目创建一个解决方案:一个应用程序和一个类库
  2. 在应用程序项目中添加对类库的引用
  3. 尽可能减少代码量,以在库和应用程序中重现用例
  4. 生成解决方案,测试程序是否按预期工作,并备份应用程序的bin/Debug文件夹
  5. 进行更改以测试其兼容性
  6. 构建解决方案,测试程序是否正常运行
  7. 仅将类库.dll文件而不是应用程序的.exe复制到步骤4中创建的备份文件夹中。
  8. 从备份文件夹中运行该应用程序,并验证其是否仍然正常运行。

例如,通过测试以下内容,我们可以轻松地验证将方法移至基类是二进制兼容的(我不会猜到)。

之前

/In the application
class Program
{static void Main(string[] args){new Foo().DoSomething();Console.WriteLine("Press ENTER");Console.ReadLine();}
}
//In the class library
public class Foo
{public void DoSomething(){Console.WriteLine("Something");}
}

之后

//In the application
class Program
{static void Main(string[] args){new Foo().DoSomething();Console.WriteLine("Press ENTER");Console.ReadLine();}
}
//In the class library
public class Foo: FooBase
{
}
public class FooBase
{public void DoSomething(){Console.WriteLine("Something");}
}

所有美好的事物都必须走到尽头

好了,这就是这个冗长的系列的结尾。

在过去的几年中,保持库与数十万用户的向后兼容性一直是我的主要关注之一。我确信我还没有学到关于该主题的所有知识,但是我衷心希望这对其他.NET开发人员是有用的。

.NET库和向后兼容的技巧——第3部分相关推荐

  1. .NET库和向后兼容的技巧——第2部分

    目录 源代码不兼容 名称冲突 常见的源代码不兼容 反射 接口和抽象类 隐式类型转换 接下来是什么? 这是.NET库和向后兼容系列技术的第二篇文章: 第1部分--简介和行为不兼容 第2部分--这篇文章 ...

  2. .NET库和向后兼容的技巧——第1部分

    目录 行为不相容 继承剧本 Obsolescence是你的朋友 沟通是关键 文档和未定义的行为 接下来是什么? 这篇博客文章将重点讨论.NET库中的行为不兼容问题. 因此,您编写了一个.NET库,并将 ...

  3. wxWidgets:向后兼容

    wxWidgets:向后兼容 wxWidgets:向后兼容 版本编号方案 源级兼容性 库二进制兼容性 应用程序二进制兼容性 wxWidgets:向后兼容 wxWidgets 支持的许多 GUI 和平台 ...

  4. java向后兼容吗_Java向后不兼容历史的观察

    java向后兼容吗 在大多数情况下,Java是一个非常向后兼容的编程语言. 这样做的好处是,与大规模破坏兼容性相比,大型系统通常可以相对轻松的方式升级为使用Java的较新版本. 这样做的主要缺点是Ja ...

  5. protobuf 向前兼容向后兼容

    http://blog.163.com/jiang_tao_2010/blog/static/12112689020114305013458/ 不错的protobuf.. protobuf的编码方式: ...

  6. android版本向上兼容吗,Android 的向前兼容和向后兼容

    向后兼容和向前兼容的概念 向后兼容(Backwards compatibility):较高版本的程序能够处理较低版本程序产生的数据. 比如word2007版本的word软件可以打开word2003创建 ...

  7. sql2000 mysql 兼容_sql2005兼容2000 | 向后兼容组件

    这个包可以让你的SQLServer2005和SQLServer2008都兼容. 相关软件软件大小版本说明下载地址 sql2005兼容2000 | 向后兼容组件顾名思义嘛就是可以让你的SQL Serve ...

  8. PyTorch 2.0来了!100%向后兼容,一行代码训练提速76%

    编辑 | 机器之心 点击下方卡片,关注"自动驾驶之心"公众号 ADAS巨卷干货,即可获取 点击进入→自动驾驶之心[全栈算法]技术交流群 PyTorch 官方:我们这次的新特性太好用 ...

  9. java 向后兼容性_关于java:JDK“向上”还是“向后”兼容?

    向后二进制兼容性(或向下兼容性) - 使用旧版本库API构建的客户端在新版本(wiki)上运行的能力. 向上二进制兼容性(或向前兼容性) - 使用新版本的库API构建的客户端在旧版本(wiki)上运行 ...

最新文章

  1. 【已解决】关于SQL2008 “不允许保存更改。您所做的更改要求删除并重新创建以下表。您对无法重新创建的标进行了更改或者启用了‘阻止保存要求重新创建表的更改’” 解决方案
  2. 域名抢注之乱象:投资的暴利,管理的漏洞
  3. OpenCASCADE:Foundation Classes之异常
  4. android activity 显示无焦点_Android面试题集锦之fragemnt
  5. 宝塔无法安装php5,宝塔无法安装phpmyadmin怎么办
  6. python星空代码_用python画星空源代码是什么?
  7. Spring 基础 编写基本的控制器(系列号3)
  8. eclipse字体颜色设置
  9. 65寸的液晶电视是挂在墙上好还是放在电视柜上好?
  10. Nginx命令行控制
  11. EmEditor16免安装破解版
  12. 复习用vue写tabbar
  13. NFT开拓IP授权新模式
  14. visio常用快捷键_Visio实用快捷键+比较不错的总结
  15. linux手动安装rsync_在Linux/Unix上安装rsync并通过示例的方式介绍使用rsync命令
  16. GIT版本回退和修改历史版本问题
  17. 刘慈欣:元宇宙将是整个人类文明的一次内卷
  18. PTA7-1 厘米换算英尺英寸
  19. 修复克米3.5勋章中心空白不显示问题
  20. 网络安全专业属于对照表(一)

热门文章

  1. java接口构造函数_Java8自定义函数式编程接口和便捷的引用类的构造器及方法
  2. 承包你所有壁纸需求,高图网图片,美到窒息
  3. 手机应用UI设计示例+模板|为了在下一个应用程序设计项目找到灵感
  4. c++网络编程连接成功后回调onconnected_谈谈网络编程(基于C++)
  5. 华为p4支持鸿蒙功能吗_什么样的手机可以刷鸿蒙系统?看看你的手机支持吗?...
  6. [译转] eBPF 概念和基本原理
  7. Programming Protocol-independent Packet Processors (P4)
  8. FD.io VPP 20.09版本正式发布:往期VPP文章回顾+下载地址+相关链接
  9. Linux系统C语言获取所有CPU核心的利用率“/proc/stat”
  10. Linux curl命令使用代理、以及代理种类介绍(附:curl命令详解)