copy-to-output-directory
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseChoosing a CopyToOutputDirectory Mode
选择CopyToOutputDirectory模式
Overview
概述
The metadata (and its publish counterpart ) controls whether an item — , , , or — is copied next to your build output, and under what conditions the copy happens. Picking the wrong mode causes either stale files in or an unnecessary per-build performance hit.
CopyToOutputDirectoryCopyToPublishDirectoryContentNoneEmbeddedResourceCompilebin/As of MSBuild 17.13 / .NET SDK 9.0.2xx there are four values:
| Mode | Copies when… | Incremental cost | Typical use |
|---|---|---|---|
| Never | None | Files not needed at runtime |
| Source is newer than destination (or destination missing) | Cheap (timestamp check) | The common case — source files you edit |
| Every build, unconditionally | Expensive — copies on every build even in no-op builds | Legacy workaround; avoid (see below) |
| Source differs from destination in either direction (newer or older, or size differs, or destination missing) | Cheap (timestamp + size check) | Destination may be mutated between builds |
xml
<ItemGroup>
<None Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
<None Include="testdata\seed.db" CopyToOutputDirectory="IfDifferent" />
</ItemGroup>You can use either the attribute form shown above or the child-element form:
xml
<None Include="testdata\seed.db">
<CopyToOutputDirectory>IfDifferent</CopyToOutputDirectory>
</None>CopyToOutputDirectoryCopyToPublishDirectoryContentNoneEmbeddedResourceCompilebin/从MSBuild 17.13 / .NET SDK 9.0.2xx版本开始,共有四种取值:
| 模式 | 复制时机 | 增量构建成本 | 典型用途 |
|---|---|---|---|
| 从不复制 | 无 | 运行时不需要的文件 |
| 源文件新于目标文件(或目标文件缺失) | 低成本(仅检查时间戳) | 常见场景——你编辑的源文件 |
| 每次构建无条件复制 | 高成本——即使在无操作构建中也会每次复制 | 遗留解决方案;尽量避免(见下文) |
| 源文件与目标文件存在差异(无论源文件比目标文件新或旧,或大小不同,或目标文件缺失) | 低成本(检查时间戳+文件大小) | 目标文件可能在构建之间被修改的场景 |
xml
<ItemGroup>
<None Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
<None Include="testdata\seed.db" CopyToOutputDirectory="IfDifferent" />
</ItemGroup>你可以使用上述属性形式,也可以使用子元素形式:
xml
<None Include="testdata\seed.db">
<CopyToOutputDirectory>IfDifferent</CopyToOutputDirectory>
</None>Why Always
is usually the wrong choice
Always为什么Always
通常不是正确选择
AlwaysAlwaysHistorically was the only way to handle a specific scenario: the destination file can change between builds — for example an SQLite database, a storage/state file, or a config file that a test run mutates. With , if the destination is modified (making its timestamp newer than the source) MSBuild will not restore the original source file, because the source is no longer newer. People reached for to force the file back into a known-good state — paying the copy cost on every build as a side effect.
AlwaysPreserveNewestAlwaysAlways历史上,是处理特定场景的唯一方式:目标文件可能在构建之间被修改——例如SQLite数据库、存储/状态文件,或被测试运行修改的配置文件。使用模式时,如果目标文件被修改(使其时间戳新于源文件),MSBuild将不会恢复原始源文件,因为源文件不再是更新的版本。人们选择来强制将文件恢复到已知良好状态——但副作用是每次构建都要付出复制成本。
AlwaysPreserveNewestAlwaysIfDifferent
: copy when different, in either direction
IfDifferentIfDifferent
:存在差异时双向复制
IfDifferentIfDifferentUnder the hood the target uses the task with . That "unchanged" check is a heuristic: it compares last-write timestamp and file size only — not a content hash — so a destination that was edited to the same size and timestamp as the source is treated as unchanged and is not re-copied. In practice this restores a mutated destination back to the source version on the next build (the reason people reached for ) while avoiding the unconditional per-build copy.
_CopyDifferingSourceItemsToOutputDirectoryCopySkipUnchangedFiles="true"AlwaysUse when:
IfDifferent- A test run or the app itself writes to the copied file (databases, caches, state/storage files, editable config) and you want each build to reset it to the source version.
- You were using purely as a "keep the output in sync with the source" mechanism, not because you truly need a copy on every single build.
Always
xml
<ItemGroup>
<!-- Reset the fixture DB to the source copy whenever it has drifted,
but don't pay a copy on every no-op build. -->
<None Include="fixtures\catalog.db" CopyToOutputDirectory="IfDifferent" />
</ItemGroup>IfDifferent在底层,目标使用带有参数的任务。这里的“未更改”检查是一种启发式判断:仅比较最后写入时间戳和文件大小——而非内容哈希——因此如果目标文件被编辑后与源文件的大小和时间戳相同,会被视为未更改,不会重新复制。在实际使用中,这会在下一次构建时将被修改的目标文件恢复为源文件版本(这正是人们选择的原因),同时避免了无条件的每次构建复制操作。
_CopyDifferingSourceItemsToOutputDirectorySkipUnchangedFiles="true"CopyAlways在以下场景中使用:
IfDifferent- 测试运行或应用本身会写入已复制的文件(数据库、缓存、状态/存储文件、可编辑配置),且你希望每次构建都将其重置为源文件版本。
- 你使用纯粹是为了“保持输出与源文件同步”,而非真正需要在每次构建时都进行复制。
Always
xml
<ItemGroup>
<!-- 当测试用例数据库发生偏离时,将其重置为源文件副本,
但不在每次无操作构建时付出复制成本。 -->
<None Include="fixtures\catalog.db" CopyToOutputDirectory="IfDifferent" />
</ItemGroup>Globally softening Always
with $(SkipUnchangedFilesOnCopyAlways)
Always$(SkipUnchangedFilesOnCopyAlways)使用$(SkipUnchangedFilesOnCopyAlways)
全局弱化Always
模式
$(SkipUnchangedFilesOnCopyAlways)AlwaysIf you have an existing codebase full of items and want the performance benefit without editing every item, set the property:
CopyToOutputDirectory="Always"xml
<PropertyGroup>
<SkipUnchangedFilesOnCopyAlways>true</SkipUnchangedFilesOnCopyAlways>
</PropertyGroup>This makes the target pass to its task, so items are only copied when they actually differ — effectively giving the same skip-unchanged behavior as .
_CopyOutOfDateSourceItemsToOutputDirectoryAlwaysSkipUnchangedFiles="true"CopyAlwaysAlwaysIfDifferent- Default is for backwards compatibility (classic
false= copy every build).Always - Set it in to opt an entire repo in at once.
Directory.Build.props - Prefer converting individual items to when you can; use this property when a bulk, non-invasive opt-in is more practical.
IfDifferent
如果你的现有代码库中大量使用的项目项,且希望无需编辑每个项就能获得性能提升,可以设置以下属性:
CopyToOutputDirectory="Always"xml
<PropertyGroup>
<SkipUnchangedFilesOnCopyAlways>true</SkipUnchangedFilesOnCopyAlways>
</PropertyGroup>这会让目标向其任务传递参数,因此仅当模式的项目项确实存在差异时才会复制——实际上让模式拥有与相同的跳过未更改文件行为。
_CopyOutOfDateSourceItemsToOutputDirectoryAlwaysCopySkipUnchangedFiles="true"AlwaysAlwaysIfDifferent- 为保持向后兼容性,默认值为(传统
false模式=每次构建都复制)。Always - 在中设置此属性,可一次性为整个代码库启用该优化。
Directory.Build.props - 尽可能将单个项目项转换为模式;当需要批量、无侵入式启用优化时,再使用此属性。
IfDifferent
How the modes flow through the build
模式在构建流程中的流转
GetCopyToOutputDirectoryItemsCopyToOutputDirectory_CopySourceItemsToOutputDirectoryCopyFilesToOutputDirectory- —
_CopyOutOfDateSourceItemsToOutputDirectoryitems (incremental viaPreserveNewest/Inputstimestamp comparison).Outputs - —
_CopyOutOfDateSourceItemsToOutputDirectoryAlwaysitems (unconditional copy unlessAlwaysis$(SkipUnchangedFilesOnCopyAlways)).true - —
_CopyDifferingSourceItemsToOutputDirectoryitems (IfDifferent).SkipUnchangedFiles="true"
All copied files are registered in , so removes them.
FileWritesdotnet cleanTransitive copy: items marked , , or also flow to referencing projects through (via ). items do not. participates in ClickOnce publish item collection alongside /.
AlwaysPreserveNewestIfDifferentProjectReference_CopyToOutputDirectoryTransitiveItemsNeverIfDifferentAlwaysPreserveNewestGetCopyToOutputDirectoryItemsCopyToOutputDirectory_CopySourceItemsToOutputDirectoryCopyFilesToOutputDirectory- —— 处理
_CopyOutOfDateSourceItemsToOutputDirectory模式的项目项(通过PreserveNewest/Inputs时间戳比较实现增量构建)。Outputs - —— 处理
_CopyOutOfDateSourceItemsToOutputDirectoryAlways模式的项目项(无条件复制,除非Always设为$(SkipUnchangedFilesOnCopyAlways))。true - —— 处理
_CopyDifferingSourceItemsToOutputDirectory模式的项目项(IfDifferent)。SkipUnchangedFiles="true"
所有被复制的文件都会注册到中,因此会将其删除。
FileWritesdotnet clean传递复制:标记为、或的项目项也会通过(经由)流转到引用项目。模式的项目项则不会。会与/一起参与ClickOnce发布项集合。
AlwaysPreserveNewestIfDifferentProjectReference_CopyToOutputDirectoryTransitiveItemsNeverIfDifferentAlwaysPreserveNewestVersion requirement
版本要求
IfDifferent$(SkipUnchangedFilesOnCopyAlways)AlwaysPreserveNewestIfDifferentglobal.jsonIfDifferent$(SkipUnchangedFilesOnCopyAlways)AlwaysPreserveNewestIfDifferentglobal.jsonQuick decision guide
快速决策指南
- Don't need the file at runtime → (or omit — it's the default).
Never - Normal source file you edit → .
PreserveNewest - Destination gets mutated between builds and must be reset to the source → .
IfDifferent - You truly need a fresh copy on literally every build → (rare).
Always - Stuck with lots of legacy and want the perf win without edits → keep
Alwaysbut setAlways.$(SkipUnchangedFilesOnCopyAlways)=true
- 运行时不需要该文件 → (或省略——这是默认值)。
Never - 正常编辑的源文件 → 。
PreserveNewest - 目标文件在构建之间会被修改,且需要重置为源文件版本 → 。
IfDifferent - 确实需要在每次构建时都获得全新副本 → (罕见)。
Always - 遗留大量模式,希望无需编辑即可获得性能提升 → 保留
Always模式,但设置Always。$(SkipUnchangedFilesOnCopyAlways)=true