首页 / C# 入门教程 / 项目与解决方案(csproj/NuGet)

C# 入门教程

项目与解决方案(csproj/NuGet)

本教程共 100 篇 · 第 97 篇 · 更新于 2026-07-31 · 约 11 分钟阅读

C#C# 入门教程编程语言dotnetcsprojNuGet解决方案

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 指定输出目录;不加的话就在当前目录生成。常见模板还有 webapimstestnunitwinforms 等。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>

几个关键节点:

  • OutputTypeExe 是可运行程序,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/Debugbin/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 锁定确切版本,保证别人拉代码后装到一模一样的包——这对可复现构建很重要。

Note

NuGet 包默认装在用户级全局缓存里,不在项目目录。所以你提交代码时一般不用传包本身,只提交 .csproj 里的 PackageReference,别人 dotnet restore 会自动补回。配合 .gitignore 忽略 bin/obj/ 是标准做法。

八、依赖冲突与版本浮动

当 A 包依赖 Newtonsoft.Json 12,B 包依赖 13,NuGet 会做「最近赢(nearest wins)」式的版本决议,尽量选一个都能用的版本。大多数情况你不用操心。

但偶有冲突报错,常见处理:

  1. dotnet list package --vulnerable 查看有漏洞或过时的包。
  2. dotnet add package 显式统一版本。
  3. 极端情况用 PackageReferenceExcludeAssets/PrivateAssets 精细控制。

九、新手踩坑

  1. bin/obj/ 提交进版本库,导致满屏冲突。加上 .gitignore 忽略它们。
  2. 目标框架写错,比如 net8.0 的项目去引用只支持 net10.0 的包,直接编译失败。
  3. 以为 dotnet add package 之后就能立刻用,却忘了 using 对应命名空间。
  4. 在类库里写了 OutputType>Exe,类库不该可运行,应改成 Library
  5. 到处写绝对路径的引用,换台机器就断。项目引用用相对路径最稳。

十、本节小结

dotnet new 建项目,dotnet new sln + dotnet sln add 组解决方案;.csproj 记着框架与引用;自己写的库用「项目引用」,别人的库用 NuGet「包引用」。记住还原机制与忽略 bin/obj,你就具备管理真实工程的雏形了。

下一章我们聊怎么给代码写测试,以及项目该怎么分层才清晰。

动手练一练

  1. dotnet new console -o CalcApp 建一个控制台项目,运行确认输出 Hello, World!
  2. 再建一个 dotnet new classlib -o CalcLib,在类库里写一个 Calculator 类(含 Add 方法),然后用 dotnet add referenceCalcApp 引用它并调用。
  3. CalcApp 加一个 NuGet 包(比如 Newtonsoft.Json),观察 .csproj 里多出什么。