写了这么多个 C# 项目,是否对项目文件 csproj 有一些了解呢?Visual Studio 是怎么让 csproj 中的内容正确显示出来的呢?更深入的,我能够自己扩展 csproj 的功能吗?
本文将直接从 csproj 文件格式的本质来看以上这些问题。
阅读本文,你将:
- 可以通读 csproj 文件,并说出其中每一行的含义
- 可以手工修改 csproj 文件,以实现你希望达到的高级功能(更高级的,可以开始写个工具自动完成这样的工作了)
- 理解新旧 csproj 文件的差异,不至于写工具解析和修改 csproj 文件的时候出现不兼容的错误
csproj 里面是什么?
总览 csproj 文件
相信你一定见过传统的 csproj 文件格式。就算你几乎从来没主动去看过里面的内容,在版本管理工具中解冲突时也在里面修改过内容。
不管你是新手还是老手,一定都会觉得这么长这么复杂的文件一定不是给人类阅读的。你说的是对的!传统 csproj 文件中有大量的重复或者相似内容,只为 msbuild 和 Visual Studio 能够识别整个项目的属性和结构,以便正确编译项目。
不过,既然这篇文章的目标是理解 csproj 文件格式的本质,那我当然不会把这么复杂的文件内容直接给你去阅读。
我已经将整个文件结构进行了极度简化,然后用思维导图进行了分割。总结成了下图,如果先不关注文件的细节,是不是更容易看懂了呢?
如果你此前也阅读过我的其他博客,会发现我一直在试图推荐使用新的 csproj 格式:
那么新格式和旧格式究竟有哪些不同使得新的格式如此简洁?
于是,我将新的 csproj 文件结构也进行简化,用思维导图进行了分割。总结成了下图:
比较两个思维导图之后,是不是发现其实两者本是相同的格式。如果忽略我在文字颜色上做的标记,其实两者的差异几乎只在文件开头是否有一个 xml 文件标记(<?xml version="1.0" encoding="utf-8"?>
)。我在文字颜色上的标记代表着这部分的部件是否是可选的,白色代表必须,灰色代表可选;而更接近背景色的灰色代表一般情况下都是不需要的。
我把两个思维导图放到一起方便比较:
会发现,传统格式中 xml 声明
、Project 节点
、Import (props)
、PropertyGroup
、ItemGroup
、Import (targets)
都是必要的,而新格式中只有 Project 节点
和 PropertyGroup
是必要的。
是什么导致了这样的差异?在了解 csproj 文件中各个部件的作用之前,这似乎很难回答。
了解 csproj 中的各个部件的作用
xml 声明部分完全没有在此解释的必要了,为兼容性提供了方便,详见:XML - Wikipedia。
接下来,我们不会依照部件出现的顺序安排描述的顺序,而是按照关注程度排序。
PropertyGroup
PropertyGroup
是用来存放属性的地方,这与它的名字非常契合。那么里面放什么属性呢?答案是——什么都能放!
在这里写属性就像在代码中定义属性或变量一样,只要写了,就会生成一个指定名称的属性。
比如,我们写:
<PropertyGroup>
<Foo>walterlv is a 逗比</Foo>
<PropertyGroup>
那么,就会生成一个 Foo
属性,值为字符串 walterlv is a 逗比
。至于这个属性有什么用,那就不归这里管了。
这些属性的含义完全是由外部来决定的,例如在旧的 csproj 格式中,编译过程中会使用 TargetFrameworkVersion
属性,以确定编译应该使用的 .NET Framework 目标框架的版本(是 v4.5 还是 v4.7)。在新的 csproj 格式中,编译过程会使用 TargetFrameworks
属性来决定编译应该使用的目标框架(是 net47 还是 netstandard2.0)。具体是编译过程中的哪个环节哪个组件使用了此属性,我们后面会说。
从这个角度来说,如果你没有任何地方用到了你定义的属性,那为什么还要定义它呢?是的——这只是浪费。
PropertyGroup
可以定义很多个,里面都可以同等地放属性。至于为什么会定义多个,原因无外乎两个:
- 为了可读性——将一组相关的属性放在一起,便于阅读和理解意图(旧的 csproj 谈不上什么可读性)
- 为了加条件——有的属性在 Debug 和 Release 下不一样(例如条件编译符
DefineConstants
)
额外说一下,Debug
和 Release
这两个值其实是在某处一个名为 Configuration
的属性定义的,它们其实只是普通的字符串而已,没什么特殊的意义,只是有很多的 PropertyGroup
加上了 Debug
Release
的判断条件才使得不同的 Configuration
具有不同的其他属性,最终表现为编译后的巨大差异。由于 Configuration
属性可以放任意字符串,所以甚至可以定义一个非 Debug
和 Release
的配置(例如用于性能专项测试)也是可以的。
ItemGroup
ItemGroup
是用来指定集合的地方,这与它的名字非常契合。那么这集合里面放什么项呢?答案是——什么都能放!
是不是觉得这句话跟前面的 PropertyGroup
句式一模一样?是的——就是一模一样!csproj 中的两个大头都这样不带语义,几乎可以说明 csproj 文件是不包含语义的,它能够用来