在Visual Studio中新建项目的时候,除了.NET Framework和.NET Core之外,我们还会看到.NET Standard的身影,如图1所示。
在“类库”项目中,.NET Standard和.NET Core、.NET Framework具有同等地位,但是.NET Standard只在“类库”项目中出现过,在“控制台”“Web应用程序”等项目中都没有它的身影。那么.NET Standard到底是什么呢?
在.NET大家庭中有.NET Framework、.NET Core、Xamarin等具体的实现,在这些实现中,有一些其他实现所不具有的特性。比如,.NET Framework中有访问Windows注册表的类,很显然这是其他实现所不具备的;再如,Xamarin中有拨打电话的类,很显然这也是其他实现所不具备的。但是这些实现也有一些可以共享的类,比如读写文件的类、List集合类、字符串类等。假如每个.NET实现中,这些可以共享的类(也叫“基础库”)都有自己的一套做法,如图2所示,就有可能出现同样功能的类在不同的实现中各不相同的情况,比如在.NET Framework中操作文件的类叫FileStream,但是到了.NET Core中对应的类叫Storage。这样就会带来一个问题:如果我们想开发一个读写文件的代码库供.NET Framework、.NET Core等使用,代码编写起来就很麻烦了。
反之,如果微软为文件操作、集合等所有.NET实现中都具有的部分制定一个规范,无论是.NET Framework、.NET Core还是Xamarin都要遵守这个规范。比如这个规范规定操作文件的类必须叫FileStream,而且FileStream类必须要有Read、Write、Flush等方法,参数和返回值也必须统一。
这样编写通用库的时候就会简单很多了。这个“各个实现通用的基础库规范”叫作.NET Standard,如图3所示。
.NET Standard规定了一系列需要被所有.NET Core、.NET Framework及Xamarin等共同实现的API,包括有哪些类、有哪些方法、参数和返回值是什么等。需要说明的是,.NET Standard只是一个规范,不是一个框架。不要以为.NET Standard是一个被.NET Framework、.NET Core、Xamarin等共用的基础库,.NET Standard只是规定了需要被实现的规范,但是不负责具体实现。
对于.NET Standard类型的类库项目,当我们分别在.NET Core项目和.NET Framework项目中引用这个类库的时候,就可以看到它们执行时的差别。比如,编写一个.NET Standard类库项目,在其中创建一个DemoNetStandardClass类,并且在类中定义一个Test方法,使用Test方法输出FileStream类所在程序集的路径,如代码所示。
代码输出FileStream类所在程序集的路径
Console.WriteLine(typeof(FileStream).Assembly.Location);
在Visual Studio中,查看FileStream类的定义,发现它是定义在netstandard.dll程序集中的,如图4所示。
反编译.NET Standard的核心程序集netstandard.dll中的FileStream类,会发现它的所有方法都是空实现,如图5所示。
可以发现,netstandard.dll以及同文件夹下的其他DLL(dynamic linked library,动态链接库)文件中的代码都只有类和成员的定义,没有具体实现。这说明,netstandard.dll等.NET Standard中的程序集只是在开发时给Visual Studio用的,运行的时候它们是不会被调用的。那么在运行的时候,代码实际调用的是什么呢?
分别创建一个.NET Core和一个.NET Framework控制台项目,然后这两个项目都分别引用上面开发的.NET Standard类库并且调用DemoNetStandardClass.Test方法,执行结果如图6和图7所示。
可以看到,对于同样一个类库中的FileStream类,项目在.NET Framework和.NET Core中执行的时候加载的程序集不一样。也就是说,同样的.NET Standard代码在运行时对应不同的实现。
分别查看.NET Core和.NET Framework中FileStream类的BeginRead方法的代码,可以看到它们有很多区别,如图8和图9所示。
.NET Framework和.NET Core中的FileStream类定义的方法也有区别,请仔细查看图10和图11中圈出的部分。
虽然.NET Core和.NET Framework的类中方法的定义和方法的实现存在不同的地方,但是对于在.NET Standard中规定的类、方法,它们必须实现。因此.NET Standard相当于定义了.NET Core、.NET Framework、Xamarin的交集,只要是.NET Standard类库,都可以被.NET Core、.NET Framework、Xamarin等项目引用。
.NET Standard随着.NET技术的升级而升级,不同版本的.NET Core、.NET Framework等支持不同版本的.NET Standard,越高版本的.NET Core、.NET Framework等支持的.NET Standard版本越高。图12列出了.NET Standard版本和.NET Core、.NET Framework版本的对应关系,这里没有列出Xamarin、Unity等的版本,对之感兴趣的读者可以访问微软官方文档的.NET Standard部分查看详细情况。
如果一个类库遵守一个版本的.NET Standard规范,那么不低于对应这个版本的.NET Core、.NET Framework的项目都可以使用这个类库。比如一个类库遵守.NET Standard 2.0规范,那么不低于.NET Core 2.0或者不低于.NET Framework 4.6.1的项目就都可以使用这个类库。
如果我们要编写一个给公众使用的类库,为了让.NET Core、.NET Framework、Xamarin等开发人员都能使用这个类库,这个类库就应该是.NET Standard类库,并且.NET Standard的版本应尽可能低一些,这样低版本的.NET Core、.NET Framework、Xamarin的项目也能使用这个类库。.NET Standard版本越高,代码中能用的API也就越多。作者的建议是先把项目的.NETStandard版本选到最低,如果发现开发时用到的类在这个.NET Standard版本中不存在,再逐步提升项目的.NET Standard版本。和其他类型的项目一样,.NET Standard类库的目标版本可以在项目属性的“目标框架”中选择,如图13所示。
如果要开发项目内部使用的类库,并且这个类库只会被.NET Core项目引用,不会被.NET Framework、Xamarin等项目引用,作者还是建议直接建立.NET Core类库项目,因为这样可以省去很多麻烦,而且可以使用.NET Core中一些特有的类和方法。
最后需要注意的一点就是,因为.NET Standard只是规范,不是.NET Framework、.NET Core这样的实现,所以只能建立.NET Standard类库项目,不能建立.NET Standard控制台项目或者Web应用程序等可以直接运行的项目。
总而言之,.NET Standard是一个.NET平台下的规范,使得我们开发的类库可以被.NET Framework、.NET Core、Xamarin等使用,提高了代码的复用性。.NET Standard已经完成了它的历史使命。从.NET 5开始,微软将不再更新.NET Standard,而是会把.NET 5、.NET 6等视为单一的代码库,并会通过编译期和运行时的检查来解决不同平台下它们所支持的功能具有差异这一问题。