微软官方的 NuGet 包是如何做到同时兼容新旧框架的?例如 System.ValueTuple 是如何做到在新旧版本…

发布于 2020-05-24 16:21更新于 2020-05-24 08:27

不知你是否好奇,System.ValueTuple 是新框架(.NET Core 3.0)开始引入的类型,但可以通过 NuGet 包向旧框架提供这些类型的使用。并且,这些包即便安装到本来就有此类型的新框架上也能正常运行而不会出现多处类型定义的问题。

这些类型是如何做到框架内定义了,包里也定义了,却能像同一个类型一样作为参数和返回值传递?本文带你了解其中的奥秘。

示例项目

首先,我们需要有一个示例项目,用来观察 System.ValueTuple 在框架内和 NuGet 包内的一些行为。

创建一个 .NET Core 控制台项目。然后我们需要修改两个地方:

  1. Program.cs 文件
  2. 项目文件(*.csproj)文件
class Program
{
    static void Main()
    {
        var (a, b) = Foo();
        System.Console.WriteLine($"欢迎阅读{a}的博客 {b}");
    }

    static (string a, string b) Foo() => ("吕毅", "blog.walterlv.com");
}

  
    Exe
    net462;net48;netstandard2.0;netcoreapp2.0;netcoreapp3.1
  

  
    
  

接下来,我们的研究都将基于此项目。

研究开始

System.ValueTuple 对旧框架的支持体现在三个方面:

  1. 旧框架中也能写出新框架中的这种语法;
  2. 旧框架中也能正常使用此类型;
  3. 新框架中此类型不会与包中的类型冲突。

我们分别来看看这三个都是如何实现的。

语法支持

C# 从 7.0 开始支持元组类型的语法,即可以写出这样的代码:

var (a, b) = Foo();

关于此新增功能,可以前往这里查看:

C# 从 8.0 开始,各种原本需要实现特定接口才能写出的语法,现在也可以不用实现接口了,只要有对应的方法存在即可,比如:

  • IDisposable
  • IEnumerable
  • Deconstruct

另外,从 C# 5.0 开始引入的 async/await 也是如此,无需实现任何接口,有 GetAwaiter 方法就够了。也是一样的情况,详见:

也就是说,只要你的项目使用的 C# 版本在 7.0 以上,就可以使用元组解构这样的语法。即便在 C# 7.0 以下,也能使用 System.ValueTuple,只是不能使用此语法而已。

旧框架兼容

System.ValueTuple 对旧框架的兼容,单纯的就是通过 NuGet 包引入了这些类型,以及这些类型的实现而已。

我们在示例项目的 net462 的输出目录下找到 System.ValueTuple.dll 进行反编译可以看出来这一点:

新框架不冲突

我们再去新框架里面看看 System.ValueTuple 的情况。

例如先看看 net48 目录下的 System.ValueTuple.dll:

可以发现,net48 下的 System.ValueTuple 已经全部使用 TypeForwardedTo 特性转移到了 mscorelib 程序集。

.NET Core 3.1 版本和 .NET Standard 2.0 版本的输出目录里是没有 System.ValueTuple.dll 的,那么它们的依赖是如何决定的呢?

答案是——不需要依赖!

我们来拆开 System.ValueTuple 的 NuGet 包看看。可在这里下载: NuGet Gallery – System.ValueTuple 4.5.0

可发现它提供了这些不同框架的支持:

其中:

TypeForwardedTo
_._

原生支持 System.ValueTuple 的框架,其 NuGet 包中的框架内的文件是 _._ ,这个文件的出现仅仅是为了能让 zip 里面有一个对应框架的文件夹。而 zip 对空文件夹的支持并不好,所以加一个这样的文件可以避免文件夹消失,造成 NuGet 认为不支持这样的框架。

结论

TypeForwardedTo

额外的,我写过另一个通过此方式获得新旧框架兼容的包:

参考资料