NET Standard中配置TargetFrameworks输出多版本类库
在.NET Standard/.NET Core技术出现之前,编写一个类库项目(暂且称为基础通用类库PA)且需要支持不同 .NET Framework 版本,那么可行的办法就是创建多个不同版本的项目(暂且称为PB1、PB2、PB3 ... PBn)。PB1、PB2、PB3 ... PBn项目分别执行下面操作:【添加】--【现有项】--【添加为链接的方式】,将PA项目代码文件添加到各自项目中,如果代码不同,则需要使用#if #else #endif 等标签来判断 .NET Framework 版本。而在.NET Standard/.NET Core技术出现之后,可以通过配置SDK 样式项目中的目标框架来支持一套代码同时输出多版本类库。
下面以Visual Studio 2019 来演示整个操作过程。
1、新建一个 .NET Standard 类库。
2、填写项目名称
3、创建完成后,查看“解决方案资源管理器”,项目下面多了一个“依赖项”节点,子节点是SDK,孙子节点是 NETStandard.Library(2.0.3)。
项目组织方式与传统类库项目的组织方式不同
4、项目,右键【属性】-->【应用程序】--> “目标框架”默认是 .NET Standard 2.0。
也可以修改为其他版本
5、编译项目,查看bin --> debug。生成了 netstandard2.0目录
目录里面生成的DLL,这与传统.NET Framework 类型的类库项目生成结果相同。
6、项目,右键 --> “编辑项目文件”
可以看到当前类库默认为 netstandard2.0,而此时其xml标签为 TargetFramework。
如果要支持多版本,则需要做调整,将 TargetFramework 节点修改为 TargetFrameworks,再添加目标版本。
7、配置多目标框架
关于如何指定多目标框架,请参考博客《.NET Standard SDK 样式项目中的目标框架》
我做的BIMFACE二次开发的接口的目标是支持 .NET Framework4.0、.NET Framework4.5 以及 .NET Core3.1。所以配置了选下3个目标版本
<PropertyGroup>
<TargetFrameworks>net40;net45;netstandard2.0;</TargetFrameworks> <!--输出多版本类库-->
</PropertyGroup>
修改后并保存,Visual Studio 会弹出黄色背景的提示信息。
这里一定要点击【重新加载项目】按钮。重新加载后,依赖项中出现了如下图所示的3个项
展开每个项查看, 每个版本的程序集对应一个单独的依赖项节点。
8、项目,右键【属性】-->【应用程序】--> “目标框架”被禁用,因为单个项目支持多版本类库,无法一次呈现多个,这是正确的。
9、重新编译项目,查看bin --> debug,生成了3种不同版本的目标程序集。
通过上面的步骤我们已经实现了多版本输出,但是在实际的企业级业务系统开发时情况比较复杂,还需要解决以下几个问题:
1、条件编译
2、引用本地程序集
3、NuGet方式引用程序集
4、XML文档输出
5、编码与DEBUG 调试
6、自动生成内部版本号
7、文件复制
下面逐步讲解如何解决以上问题。

这是VS中默认的编译输出目录。
如果需要配置不同的类库输出到不同的位置,也可以自定义配置输出路径实现。
查看项目属性,【生成】-->“输出”-->“输出路径”中输入自定义目录或者点击【浏览】按钮选择一个目录。
填写后,保存项目。项目右键,【编辑项目文件】,csproj文件中自动增加了如下配置,其中 Condition 后面的表达式即是编译条件。OutputPath即是自定义输出目录。
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net40|AnyCPU'">
<OutputPath>bin\Debug\</OutputPath>
</PropertyGroup>
按照以上方式再复制2份,分别配置 net45 与 netstandard2.0版。完整配置如下:
<!--条件编译-->
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|Release|net40|AnyCPU'">
<OutputPath>bin\Debug\</OutputPath><!--编译后的文件输出目录-->
</PropertyGroup> <!--条件编译-->
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|Release|net45|AnyCPU'">
<OutputPath>bin\Debug\</OutputPath>
</PropertyGroup> <!--条件编译-->
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|Release|netstandard2.0|AnyCPU'">
<OutputPath>bin\Debug\</OutputPath>
</PropertyGroup>
bin\Debug\ 是我自己定义的输出目录,大家可以根据实际需求填写其他目录。
$(Configuration) 的条件值有:Debug、Release。
$(TargetFramework)的条件为 <TargetFrameworks>节点中配置的值。
$(Platform) 的条件值有:
在下图中可以看出由于3个不同的输出类库中所引用的程序集是不同的,那么当编译时,一定是每个类库进行单独编译,这时就就需要通过某种方式告诉编译器当前编译的类库版本是什么,然后添加针对具体版本的第三方程序集引用。
.NET Standard 指定多个目标框架时,可有条件地为每个目标框架引用程序集。
以下库项目面向 .NET Standard (netstandard1.4
) 和 .NET Framework(net40
和 net45
)的 API。 将复数形式的 TargetFrameworks 元素与多个目标框架一起使用。 为两个 .NET Framework TFM 编译库时,Condition
属性包括特定于实现的包:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup>
<TargetFrameworks>netstandard2.0;net40;net45</TargetFrameworks>
</PropertyGroup> <!-- 有条件地获取.NET Framework 4.0 目标的引用 -->
<ItemGroup Condition=" '$(TargetFramework)' == 'net40' ">
<Reference Include="System.Net" />
</ItemGroup> <!-- 有条件地获取.NET Framework 4.5 目标引用 -->
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<Reference Include="System.Net.Http" />
<Reference Include="System.Threading.Tasks" />
</ItemGroup> </Project>
下面开始添加引用,点击项目子节点【依赖项】-->【添加程序集引用】
打开如下界面。默认加载的目标框架显示为 .NET Framework 4。
如何才能添加 net45 或者 netstandard2.1 的引用呢?正常来说应该在VS的“引用管理器”界面上提供目标框架的下拉选择框,可以自由切换选择不同的目标框架,但是到目前为止VS没有此功能,我的VS版本信息如下
希望微软在后续VS版本中能增加此功能。
回到csproj编辑界面,可以看到 TargetFrameworks 值第一个为 net40,估计与这个有关系。
通过取巧的方式调整 TargetFrameworks 里的版本先后顺序,保存后,重启VS(我的VS2019是这种情况,需要重启才生效。不知道其他小伙伴们的VS是不是保存后可以自动切换呢?)
再次添加程序集引用,此时加载了 .NET Framework 4.5
添加一个“System.Net.dll”引用来测试一下
添加后,如下图所示
.NET Framework 4.5 项目中多了“System.Net.dll”引用。但是 .NET Standard 2.0 前面显示黄色警告符合。展开所有依赖项,.NET Framework 4.0 与 .NET Framework 4.5 都已经正确引用。
.NET Standard 2.0 程序及引用有警告。这表示 netstandard2.0 并不知道 System.Net.dll 是什么。
查看.csproj文件
红色框内的配置,表示net40、.net45 和 netstand2.0 都需要“System.Net”引用(即统一配置),而实际只有 net40、.net45 才需要该引用,所以这里我们要使用 Condition 条件,修改如下:
这样只有 .net40 与 .net45 条件下才引用“System.Net.dll”。保存后,发现 netstand2.0 下面的警告标示消失了。
下面演示添加一个多版本都支持的第三方类库,NLog 日志组件,目前最新版本为4.7.5。通过 NuGet 方式添加引用
下图可以看出该组件同时支持 .NET4.0、.NET4.5 以及 .NET Standard 2.0
点击【安装】
点击【确定】,安装完成后,每一个类库均添加了引用
查看.csproj文件,添加了如下配置
注意这里是 PackageReference,而之前程序集的是 Reference,而且我们也会发现在VS解决方案管理器中并没有出现 packages.config 文件。默认在 sln 文件的同级也没有创建一个 packages 文件夹。
而是将dll下载到了C:\Users\当前登录用户\.nuget目录下,这与java的Maven管理方式类似。我的本地路径为:C:\Users\Savion\.nuget\packages
下面再添加一个 netstandard 专有的 nuget 引用 Microsoft.Extensions.DependencyInjection.dll
点击【安装】
点击【确定】
点击【我接受】。
添加完后解决方案中仅有 .NET Standard2.0 中增加了引用。.net40 与 .net45 中没有引用。
添加完后 csproj文件 会多出如下配置
NuGet 很智能,自动把 Condition 给加好了。
选择项目,点击 属性-->生成,勾选 “XML 文档文件”。默认生成的xml文件名称包含绝对路径,这个名称不是很友好,一般修改为程序集的名称即可
点击菜单栏上的【保存】按钮。查看.csproj文件新增了如下配置:
这表示 net40 会生成 xml 文件,将该配置信息复制两份,然后修改 Platform 以及输出路径为 net45 与 netstandard2.0。完整配置如下:
<!--条件编译-->
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|Release|net40|AnyCPU'">
<DocumentationFile>ZCN.NET.BIMFace.SDK.xml</DocumentationFile><!--xml文档,输出类库中方法与参数的注释等信息-->
<OutputPath>bin\Debug\</OutputPath><!--编译后的文件输出目录-->
</PropertyGroup> <!--条件编译-->
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|Release|net45|AnyCPU'">
<DocumentationFile>ZCN.NET.BIMFace.SDK.xml</DocumentationFile>
<OutputPath>bin\Debug\</OutputPath>
</PropertyGroup> <!--条件编译-->
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|Release|netstandard2.0|AnyCPU'">
<DocumentationFile>ZCN.NET.BIMFace.SDK.xml</DocumentationFile>
<OutputPath>bin\Debug\</OutputPath>
</PropertyGroup>
重新编译项目,查看输出目录里面的内容
其中ZCN.NET.BIMFace.SDK.xml 内容如下
.netstandard2.0 中多了一个 ZCN.NET.BIMFace.SDK.deps.josn 文件,里面包含了运行时环境以及依赖项等信息
/// <summary>
/// 判断字符串是否为null、空或者空白
/// </summary>
/// <param name="str">待判断的字符串</param>
/// <returns></returns>
public static bool IsNullOrWhiteSpace(this string str)
{
return string.IsNullOrEmpty(str.Trim());
}
在.NET4.0及以上框架下使用下面的方式实现
/// <summary>
/// 判断字符串是否为null、空或者空白
/// </summary>
/// <param name="str">待判断的字符串</param>
/// <returns></returns>
public static bool IsNullOrWhiteSpace(this string str)
{
return string.IsNullOrWhiteSpace(str);
}
2种框架下实现的逻辑方式不同,为了只编写一套代码(该情况为一个方法),此时就需要使用预处理指令编写条件指令。
在库或应用中,使用预处理器指令编写条件代码,针对每个目标框架进行编译。关于预处理指令请参考《C# 预处理器指令》
使用预处理指令编写条件代码的实现方式如下:
/// <summary>
/// 判断字符串是否为null、空或者空白
/// </summary>
/// <param name="str">待判断的字符串</param>
/// <returns></returns>
public static bool IsNullOrWhiteSpace(this string str)
{
#if NET35
return string.IsNullOrEmpty(str.Trim());
#else
return string.IsNullOrWhiteSpace(str);
#endif
}
上面的实现方式是在一个方法内进行条件区分,下面介绍在同一个类中(方法之外),使用条件区分不同逻辑的实现方式
#if NET35 || NET40 || NET45
/// <summary>
/// 对URL字符串进行编码
/// <para>注意:.NET Core 转义后字母为大写</para>
/// </summary>
/// <param name="url">有效的url字符串</param>
/// <param name="encoding">编码,默认为 UTF8</param>
/// <returns></returns>
public static string UrlEncode(this string url, Encoding encoding = null)
{
encoding = encoding ?? Encoding.UTF8;
return System.Web.HttpUtility.UrlEncode(url, encoding);
}
#else
/// <summary>
/// 对URL字符串进行编码
/// <para>注意:.NET Core 转义后字母为大写</para>
/// </summary>
/// <param name="url">有效的url字符串</param>
/// <returns></returns>
public static string UrlEncode(this string url)
{
return WebUtility.UrlEncode(url);//转义后字母为大写
}
#endif
上面两段代码中的预处理符号 NET35、NET40、NET45 是.NET目标框架中预定义的预处理符号。
使用 SDK 样式项目时,生成系统可识别预处理器符号,这些符号表示支持的目标框架版本表中所示的目标框架。 使用表示 .NET Standard、.NET Core 或 .NET 5 TFM 的符号时,请用下划线替换点和连字符,并将小写字母更改为大写字母(例如,netstandard1.4
的符号为 NETSTANDARD1_4
)。
.NET 目标框架的预处理器符号的完整列表如下:
除此之外,开发者可以通过配置自定义常量的方式达到与.NET目标框架中预定义的预处理符号相同的功能。
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>TRACE;RELEASE</DefineConstants> <!--统一定义的常量-->
</PropertyGroup>
上述代码片段通过 <DefineConstants> 节点 定义了2个常量(多个常量之间使用分号分隔)TRACE 与 RELEASE。
在编写C#代码时能够自动智能感知到自定义的常量
上面是定义的统一的全局变量,也可以在每个条件编译分组中自定义常量
<!--条件编译-->
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net40|AnyCPU'">
<DocumentationFile>ZCN.NET.BIMFace.SDK.xml</DocumentationFile><!--xml文档,输出类库中方法与参数的注释等信息-->
<OutputPath>bin\Debug\</OutputPath><!--编译后的文件输出目录-->
<DefineConstants>NET_FULL</DefineConstants><!--独立定义的常量-->
</PropertyGroup>
PropertyGroup,是包含一组用户定义的 Property 元素。 MSBuild 项目中使用的每个 Property 元素必须是 PropertyGroup 元素的子元素。其包含如下的子元素
更加完整详细的信息请参考微软官方文档《PropertyGroup 元素 (MSBuild)》
- 以前的写法是在/Properties/AssemblyInfo.cs里通过
[assembly: AssemblyVersion("2.3.*")]
这样的形式生成,但是现在默认关闭这个功能了,如果我们直接指定<AssemblyVersion>9.8.*</AssemblyVersion>
会警告错误,加上<Deterministic>False</Deterministic>
即可 为什么默认关闭?请了解下Roslyn中的确定性构建
其它生成方式、汇编内部版本号后面两位的生成规则,请看使用Visual Studio时是否可以自动增加文件构建版本、Visual Studio 2017中的自动版本控制(.NET Core)、如何有一个自动递增版本号(Visual Studio)
msbuildtasks也了解一下,如果要兼容以前的内部版本号生成规则,可自己动手
NuGet包相关
- 静态文件如何指定复制行为等,或许会发现安装NuGet之后希望能编辑的文件仅仅只是一个链接而已,如何让它包含在项目里面呢,请参考微软官方文档 NuGet ContentFiles揭秘,带回解决方案级包的讨论
- PackageReference 方式作为包管理格式,安装时不支持执行install.ps1等powershell相关脚本,init.ps1在解决方案第一次安装时可用。vs2017中,已不支持此功能,NuGet 3 - 什么和为什么-Powershell安装和卸载脚本
- 关于nuget包安装的相关行为估计都可以通过msbuild属性或者任务来搞定,这一切都是可以通过命令行来执行的,方便跨平台使用吧
- msbuildtasks也了解一下,可以代替ps1脚本完成想做的事
【已更新最新开发文章,点击查看详细】
NET Standard中配置TargetFrameworks输出多版本类库的更多相关文章
- 配置使用TargetFrameworks输出多版本类库
1.类库右键 2.修改配置 修改前: <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <Targe ...
- php中Standard中配置选项,在TargetFrameworks环境下如何输出库存
在.NET Standard/.NET Core技术出现之前,编写一个类库项目(暂且称为基础通用类库PA)且需要支持不同 .NET Framework 版本,那么可行的办法就是创建多个不同版本的项目( ...
- 在tomcat中配置jdk的不同版本
在tomcat中配置jdk的不同版本---------------------------------------------------------------------------------- ...
- VS_QT中配置qDebug输出
在使用qt_create时可以使用qDebug进行调试输出.在VS中也可以使用.但需要配置.配置过程如下图所示: 一.首先右击工程名,选择最后一个选项“Properties” 二.然后选择Linker ...
- Visual Studio中配置Beyond Compare为版本比较工具
VS自带的合并工具并不理想,个人比较习惯Beyond Compare,这里替换成Beyond Compare,因为并不想改变所有的VS项目设置,这里以单个仓库项目为例,源代码管理器使用GIT 找到.g ...
- 在 Android studio 中 配置Gradle 做到 “根据命令行提示符生成指定versionCode, versionName,指定apk的打包输出路径”
需求: 1. 使用 Android studio ,使用 gradle 进行构建 2. 在实际开发中,我们需要使用jenkins进行打包.就需要配置我们的 gradle 脚本以支持参数化的方式. 3. ...
- mybatis的mapper代理,SqlMapConfig.xml中配置,输入和输出映射使用案例
public class User { private int id; private String username;// 用户姓名 private String sex;// 性别 private ...
- VS Code中配置python版本以及Python多版本
VS Code中配置python版本VS Code十分方便配置python的版本:可以选在在本地setting.json或者全局setting.json文件中配置:python.pythonPath在 ...
- Windows Server2008 下用于.NET Framework3.0版本的问题无法在IIS7中配置.NET Framework4.0节点的问题
Windows Server 2008中,功能列表安装的为.NET Framework3.0. 试了N种方法未升级为.NET Framework4.0(哪位如果可以直接升级为4.0或3.5希望能够分享 ...
随机推荐
- nginx server_name 多个
nginx server_name 多个 nginx server_name 多个的话,空格隔开就行 server_name baidu.com baidu.me; 如果很多的话可以用正则,我的需求, ...
- 微信小程序(1)
微信小程序 什么是微信小程序? 微信小程序优点 跨平台 打开速度比h5快 不用下载 开发目录结构介绍 1. 小程序Pages目录说明 2. 工具文件夹 3. 全局文件 用法1 全局APP.json文件 ...
- [Failed]Tomcat cluster方案共享session配置出错,sigh....
后继发展:https://www.cnblogs.com/xiandedanteng/p/12134300.html 参考网文一:多个Tomcat之间实现Session共享 参考网文二:Tomcat官 ...
- Python多行缩进反向缩进快捷键
1.Python增加缩进快捷键:Ctrl+Alt+] 或tab键或shift+tab键 2.Python减少缩进快捷键:Ctrl+Alt+[
- Java使用数据库连接池连接Oracle数据库
第一步:导入tomcat\lib 下的一个tomcat-dbcp.jar包第二步:在web\META-INF下新建一个context.xml文件,文件内容如下: <?xml version=&q ...
- Apache和分布式部署
1.tomcat分布式部署 1.1.要配置几个tomcat,就部署几个相同程序名的tomcat 1.2.配置每个tomcat下server.xml中ajp端口,以及后面的jvmRoute,第几个就配置 ...
- softmax交叉熵损失函数求导
来源:https://www.jianshu.com/p/c02a1fbffad6 简单易懂的softmax交叉熵损失函数求导 来写一个softmax求导的推导过程,不仅可以给自己理清思路,还可以造福 ...
- [LeetCode]415. 字符串相加、43. 字符串相乘
题目 415. 字符串相加 给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和. 题解 维护一个temp表示当前两数相加+上一个进位的和. 每次更新结果的一位. 注意终止条件. 最后将 ...
- 【深入理解Linux内核架构】3.2 (N)UMA模型中的内存组织
内核对一致和非一致内存访问系统使用相同的数据结构.在UMA系统上,只使用一个NUMA结点来管理整个系统内存.而内存管理的其他部分则相信他们是在处理一个伪NUMA系统. 3.2.1 概述 内存划分为结点 ...
- Linux中逻辑卷(LV)的创建、增大和减小
首先说一下在缩小逻辑卷的时候要注意的问题:第一步使用resize2fs命令更改文件系统的容量:第二步使用lvreduce命令减小逻辑卷的容量.这两个顺序千万不要搞反了,而且要保证缩减后的逻辑卷容量大于 ...