项目与解决方案(csproj/NuGet)
本教程共 100 篇 · 第 97 篇 · 更新于 2026-07-31 · 约 11 分钟阅读
97. 项目与解决方案(csproj/NuGet)
本节目标:学完能用
dotnet命令行建项目、组解决方案、改 .csproj,并用 NuGet 把第三方库加进工程。
前面 96 章,你写的代码都活在「一个文件」里。真实工程不是这样:几十上百个文件、好几个项目、还要引用别人写好的库。这一章带你从「单文件」迈步到「真实工程」——认识项目文件、解决方案和 NuGet 依赖。
一、一个 .NET 项目是什么
在 .NET 里,一个「项目(project)」就是能编译成一个程序集(dll 或 exe)的代码集合。它的核心是一个 XML 文件,扩展名 .csproj。
现代 .NET 用的是 SDK 风格(SDK-style)的 csproj,非常精简。你几乎不需要手写,用命令行或 IDE 生成即可。项目的全部设定——目标框架、输出类型、引用了哪些包——都记在这里。
Note早期 .NET Framework 的 csproj 里要列出每一个
.cs文件,又长又啰嗦。SDK 风格改成「约定优于配置」:默认把目录下所有.cs文件都编进来,你只写差异。
二、dotnet new:从模板建项目
dotnet new 是创建项目的入口。它背后是一套模板(template),你指定模板名就能生成对应骨架。
# 建一个控制台项目,文件夹名为 MyApp
dotnet new console -o MyApp
# 建一个类库(只能被引用,不能单独运行)
dotnet new classlib -o MyLib
# 建一个 xUnit 测试项目(第 98 章用得上)
dotnet new xunit -o MyApp.Tests
-o 指定输出目录;不加的话就在当前目录生成。常见模板还有 webapi、mstest、nunit、winforms 等。dotnet new list 能列出本机所有模板。
建好后,进入目录跑 dotnet run,就能看到程序跑起来:
cd MyApp
dotnet run
dotnet new console 替你生成的 Program.cs 长这样,正好呼应前 96 章一直在用的风格:
namespace CSharpDemo;
// 文件范围命名空间 + 顶级语句(top-level statements)+ 隐式 usings
// 不用手写 using System;,因为项目开启了 ImplicitUsings
Console.WriteLine("Hello from a .NET project!");
这就是现代 .NET 项目的默认骨架:没有 Main 方法的大括号,没有满屏的 using,逻辑直接写在文件顶层。你写的每一章示例,本质上都是一个最小控制台项目的 Program.cs。理解了这点,「单文件演示」和「真实工程」之间的鸿沟就不存在了。
Tip想看项目到底隐式引入了哪些
using、编译成了什么,可在项目目录执行dotnet build后查看obj/Debug/net10.0/下的生成文件。新手不必深究,知道「顶级语句最终也会被编译器包成 Main」即可。
三、解决方案 .sln 与多项目管理
当你有「主程序 + 类库 + 测试」好几个项目时,需要一个容器把它们拢在一起——这就是解决方案(solution),文件扩展名 .sln。
# 新建一个空白解决方案
dotnet new sln -o MySolution
cd MySolution
# 把已有项目加进解决方案
dotnet sln add MyApp/MyApp.csproj
dotnet sln add MyLib/MyLib.csproj
dotnet sln add MyApp.Tests/MyApp.Tests.csproj
sln 本身不编译,它只是「项目清单」,方便你一条命令构建或打开整个工程:
# 在解决方案目录下一键还原、构建全部项目
dotnet build
Tip小项目一个
.csproj就够了,不必硬套解决方案。项目多了、彼此有引用关系时,再上.sln管理,IDE 打开也清爽。
四、看懂 .csproj
生成的 MyApp.csproj 通常长这样(SDK 风格,极简):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
几个关键节点:
OutputType:Exe是可运行程序,Library是类库。TargetFramework:目标框架,net10.0即 .NET 10。本书基线就写它。ImplicitUsings:开启后自动引入常用using(如System),这就是前面章节「不用手写 using」的开关。Nullable:开启可空引用类型检查(第 90 章讲过)。
想改项目名、加自定义属性,直接编辑这个文件即可。dotnet build 会按它来。
<TargetFramework> 的取值有一套约定:net10.0 表示 .NET 10;net9.0 是上一稳定版;netstandard2.0 则是「跨多版本兼容的底层标准」,常用于写被各种项目引用的类库。选错框架,可能引用不到某些 API,也可能和别的库不兼容。本书所有示例都以 net10.0 为基线,与全书的 C# 14 设定一致。
构建还有「配置(configuration)」之分:默认的 Debug 带调试信息、不做优化,适合开发;dotnet build -c Release 生成的是优化过的发布版本,体积小、跑得快,用于上线。两个配置的输出分别落在 bin/Debug 和 bin/Release,互不干扰。
五、项目引用:自己写的库
主程序要用到你写的类库 MyLib,得加「项目引用(project reference)」:
# 在 MyApp 目录里执行,指向类库项目
dotnet add reference ../MyLib/MyLib.csproj
执行后,csproj 里多了一行:
<ItemGroup>
<ProjectReference Include="..\MyLib\MyLib.csproj" />
</ItemGroup>
之后 MyApp 就能 using MyLib; 用里面的类了。这种「拆成多个项目、彼此引用」的做法,是大型代码分层的基础。
六、NuGet:引用别人写好的包
绝大多数功能不必自己造轮子。NuGet 是 .NET 的包仓库(package repository),上面有几十万个现成库。引用一个包叫「包引用(package reference)」。
# 给当前项目添加 Newtonsoft.Json 这个流行 JSON 库
dotnet add package Newtonsoft.Json
# 指定版本,避免意外升级
dotnet add package Newtonsoft.Json --version 13.0.3
加完之后,csproj 里出现:
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
新版 .NET 也能用「集中式包管理」(一个 Directory.Packages.props 统管所有版本),团队项目常用,这里先知道有这回事即可。
七、依赖管理:还原与锁定
dotnet add package 只是写下了引用。真正把包下载到本地,靠「还原(restore)」:
# 还原所有依赖(首次构建会自动还原)
dotnet restore
还原时会生成 obj/project.assets.json 记录依赖图,还可能产生 packages.lock.json 锁定确切版本,保证别人拉代码后装到一模一样的包——这对可复现构建很重要。
NoteNuGet 包默认装在用户级全局缓存里,不在项目目录。所以你提交代码时一般不用传包本身,只提交
.csproj里的PackageReference,别人dotnet restore会自动补回。配合.gitignore忽略bin/、obj/是标准做法。
八、依赖冲突与版本浮动
当 A 包依赖 Newtonsoft.Json 12,B 包依赖 13,NuGet 会做「最近赢(nearest wins)」式的版本决议,尽量选一个都能用的版本。大多数情况你不用操心。
但偶有冲突报错,常见处理:
- 用
dotnet list package --vulnerable查看有漏洞或过时的包。 - 用
dotnet add package显式统一版本。 - 极端情况用
PackageReference的ExcludeAssets/PrivateAssets精细控制。
九、新手踩坑
- 把
bin/、obj/提交进版本库,导致满屏冲突。加上.gitignore忽略它们。 - 目标框架写错,比如
net8.0的项目去引用只支持net10.0的包,直接编译失败。 - 以为
dotnet add package之后就能立刻用,却忘了using对应命名空间。 - 在类库里写了
OutputType>Exe,类库不该可运行,应改成Library。 - 到处写绝对路径的引用,换台机器就断。项目引用用相对路径最稳。
十、本节小结
dotnet new 建项目,dotnet new sln + dotnet sln add 组解决方案;.csproj 记着框架与引用;自己写的库用「项目引用」,别人的库用 NuGet「包引用」。记住还原机制与忽略 bin/obj,你就具备管理真实工程的雏形了。
下一章我们聊怎么给代码写测试,以及项目该怎么分层才清晰。
动手练一练
- 用
dotnet new console -o CalcApp建一个控制台项目,运行确认输出Hello, World!。 - 再建一个
dotnet new classlib -o CalcLib,在类库里写一个Calculator类(含Add方法),然后用dotnet add reference让CalcApp引用它并调用。 - 给
CalcApp加一个 NuGet 包(比如Newtonsoft.Json),观察.csproj里多出什么。