C# “复制本地”和项目引用的最佳实践是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/280751/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 21:26:16  来源:igfitidea点击:

What is the best practice for "Copy Local" and with project references?

c#.netvisual-studiomsbuild

提问by Dave Moore

I have a large c# solution file (~100 projects), and I am trying to improve build times. I think that "Copy Local" is wasteful in many cases for us, but I am wondering about best practices.

我有一个很大的 c# 解决方案文件(~100 个项目),我正在尝试缩短构建时间。我认为“复制本地”在很多情况下对我们来说都是浪费,但我想知道最佳实践。

In our .sln, we have application A depending on assembly B which depends on assembly C. In our case, there are dozens of "B" and a handful of "C". Since these are all included in the .sln, we're using project references. All assemblies currently build into $(SolutionDir)/Debug (or Release).

在我们的 .sln 中,应用程序 A 依赖于程序集 B,而程序集 B 又依赖于程序集 C。在我们的例子中,有几十个“B”和一些“C”。由于这些都包含在 .sln 中,因此我们使用了项目引用。当前所有程序集都构建到 $(SolutionDir)/Debug(或 Release)中。

By default, Visual Studio marks these project references as "Copy Local", which results in every "C" being copied into $(SolutionDir)/Debug once for every "B" that builds. This seems wasteful. What can go wrong if I just turn "Copy Local" off? What do other people with large systems do?

默认情况下,Visual Studio 将这些项目引用标记为“Copy Local”,这会导致每个“C”被复制到 $(SolutionDir)/Debug 中,每次生成的“B”。这似乎很浪费。如果我只是关闭“复制本地”会出现什么问题?其他拥有大型系统的人会做什么?

FOLLOWUP:

跟进:

Lots of responses suggest breaking up the build into smaller .sln files... In the example above, I would build the foundation classes "C" first, followed by the bulk of the modules "B", and then a few applications, "A". In this model, I need to have non-project references to C from B. The problem I run into there is that "Debug" or "Release" gets baked into the hint path and I wind up building my Release builds of "B" against debug builds of "C".

许多响应建议将构建分解为更小的 .sln 文件......在上面的示例中,我将首先构建基础类“C”,然后是大部分模块“B”,然后是一些应用程序,“一种”。在这个模型中,我需要从 B 对 C 进行非项目引用。我在那里遇到的问题是“调试”或“发布”进入提示路径,我最终构建了“B”的发布版本反对“C”的调试版本。

For those of you that split the build up into multiple .sln files, how do you manage this problem?

对于那些将构建拆分为多个 .sln 文件的人,您如何解决这个问题?

采纳答案by Bas Bossink

In a previous project I worked with one big solution with project references and bumped into a performance problem as well. The solution was three fold:

在之前的一个项目中,我使用了一个带有项目引用的大型解决方案,但也遇到了性能问题。解决方法有三:

  1. Always set the Copy Local property to false and enforce this via a custom msbuild step

  2. Set the output directory for each project to the same directory (preferably relative to $(SolutionDir)

  3. The default cs targets that get shipped with the framework calculate the set of references to be copied to the output directory of the project currently being built. Since this requires calculating a transitive closure under the 'References' relation this can become VERYcostly. My workaround for this was to redefine the GetCopyToOutputDirectoryItemstarget in a common targets file (eg. Common.targets) that's imported in every project after the import of the Microsoft.CSharp.targets. Resulting in every project file to look like the following:

    <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        ... snip ...
      </ItemGroup>
      <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
      <Import Project="[relative path to Common.targets]" />
      <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
           Other similar extension points exist, see Microsoft.Common.targets.
      <Target Name="BeforeBuild">
      </Target>
      <Target Name="AfterBuild">
      </Target>
      -->
    </Project>
    
  1. 始终将 Copy Local 属性设置为 false 并通过自定义 msbuild 步骤强制执行此操作

  2. 将每个项目的输出目录设置为同一个目录(最好相对于$(SolutionDir)

  3. 框架附带的默认 cs 目标计算要复制到当前正在构建的项目的输出目录的引用集。由于这需要在“参考”关系下计算传递闭包,因此这可能会变得非常昂贵。我对这个解决办法是重新定义GetCopyToOutputDirectoryItems目标在一个共同的目标文件(如。Common.targets)这是进口的每一个项目的导入后Microsoft.CSharp.targets。导致每个项目文件如下所示:

    <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        ... snip ...
      </ItemGroup>
      <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
      <Import Project="[relative path to Common.targets]" />
      <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
           Other similar extension points exist, see Microsoft.Common.targets.
      <Target Name="BeforeBuild">
      </Target>
      <Target Name="AfterBuild">
      </Target>
      -->
    </Project>
    

This reduced our build time at a given time from a couple of hours (mostly due to memory constraints), to a couple of minutes.

这将给定时间的构建时间从几个小时(主要是由于内存限制)减少到几分钟。

The redefined GetCopyToOutputDirectoryItemscan be created by copying the lines 2,438–2,450 and 2,474–2,524 from C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Microsoft.Common.targetsinto Common.targets.

重新定义的GetCopyToOutputDirectoryItems可以通过复制从线和2,438-2,450 2,474-2,524被创建C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Microsoft.Common.targetsCommon.targets

For completeness the resulting target definition then becomes:

为了完整起见,最终的目标定义变为:

<!-- This is a modified version of the Microsoft.Common.targets
     version of this target it does not include transitively
     referenced projects. Since this leads to enormous memory
     consumption and is not needed since we use the single
     output directory strategy.
============================================================
                    GetCopyToOutputDirectoryItems

Get all project items that may need to be transferred to the
output directory.
============================================================ -->
<Target
    Name="GetCopyToOutputDirectoryItems"
    Outputs="@(AllItemsFullPathWithTargetPath)"
    DependsOnTargets="AssignTargetPaths;_SplitProjectReferencesByFileExistence">

    <!-- Get items from this project last so that they will be copied last. -->
    <CreateItem
        Include="@(ContentWithTargetPath->'%(FullPath)')"
        Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(ContentWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"
            >
        <Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
                Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
                Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
    </CreateItem>

    <CreateItem
        Include="@(_EmbeddedResourceWithTargetPath->'%(FullPath)')"
        Condition="'%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"
            >
        <Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
                Condition="'%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
                Condition="'%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
    </CreateItem>

    <CreateItem
        Include="@(Compile->'%(FullPath)')"
        Condition="'%(Compile.CopyToOutputDirectory)'=='Always' or '%(Compile.CopyToOutputDirectory)'=='PreserveNewest'">
        <Output TaskParameter="Include" ItemName="_CompileItemsToCopy"/>
    </CreateItem>
    <AssignTargetPath Files="@(_CompileItemsToCopy)" RootFolder="$(MSBuildProjectDirectory)">
        <Output TaskParameter="AssignedFiles" ItemName="_CompileItemsToCopyWithTargetPath" />
    </AssignTargetPath>
    <CreateItem Include="@(_CompileItemsToCopyWithTargetPath)">
        <Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
                Condition="'%(_CompileItemsToCopyWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
                Condition="'%(_CompileItemsToCopyWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
    </CreateItem>

    <CreateItem
        Include="@(_NoneWithTargetPath->'%(FullPath)')"
        Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(_NoneWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"
            >
        <Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
                Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
                Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
    </CreateItem>
</Target>

With this workaround in place I found it workable to have as much as > 120 projects in one solution, this has the main benefit that the build order of the projects can still be determined by VS instead of doing that by hand by splitting up your solution.

有了这个解决方法,我发现在一个解决方案中包含多达 120 个项目是可行的,这的主要好处是项目的构建顺序仍然可以由 VS 确定,而不是通过拆分解决方案手动完成.

回答by TcKs

our "best practise" is to avoid solutions with many projects. We have a directory named "matrix" with current versions of assemblies, and all references are from this directory. If you change some project and you can say "now the change is complete" you can copy the assembly into the "matrix" directory. So all projects that depends on this assembly will have the current(=latest) version.

我们的“最佳实践”是避免许多项目的解决方案。我们有一个名为“matrix”的目录,其中包含当前版本的程序集,所有引用都来自该目录。如果您更改了某个项目并且您可以说“现在更改已完成”,您可以将程序集复制到“矩阵”目录中。因此,所有依赖于此程序集的项目都将具有当前(=最新)版本。

If you have few projects in solution, the build process is much faster.

如果解决方案中的项目很少,则构建过程会快得多。

You can automate the "copy assembly to matrix directory" step using visual studio macros or with "menu -> tools -> external tools...".

您可以使用 Visual Studio 宏或“菜单 -> 工具 -> 外部工具...”自动执行“将程序集复制到矩阵目录”步骤。

回答by kenny

I tend to build to a common directory (e.g. ..\bin), so I can create small test solutions.

我倾向于构建到一个公共目录(例如..\bin),这样我就可以创建小的测试解决方案。

回答by Bruno Shine

In my opinion, having a solution with 100 projects is a BIG mistake. You could probably split your solution in valid logical small units, thus simplifying both maintenance and builds.

在我看来,拥有 100 个项目的解决方案是一个大错误。您可能可以将您的解决方案拆分为有效的逻辑小单元,从而简化维护和构建。

回答by CubanX

Usually, you only need to Copy Local if you want your project using the DLL that is in your Bin vs. what is somewhere else (the GAC, other projects, etc.)

通常,如果您希望您的项目使用 Bin 中的 DLL 而不是其他地方的 DLL(GAC、其他项目等),您只需要复制本地

I would tend to agree with the other folks that you should also try, if at all possible, to break up that solution.

我倾向于同意其他人的意见,如果可能的话,您也应该尝试打破该解决方案。

You can also use Configuration Manager to make yourself different build configurations within that one solution that will only build given sets of projects.

您还可以使用 Configuration Manager 在该解决方案中为自己设置不同的构建配置,该解决方案将只构建给定的项目集。

It would seem odd if all 100 projects relied on one another, so you should be able to either break it up or use Configuration Manager to help yourself out.

如果所有 100 个项目都相互依赖,这看起来很奇怪,因此您应该能够将其分解或使用 Configuration Manager 来帮助自己解决。

回答by Aleksandar

You can try to use a folder where all assemblies that are shared between projects will be copied, then make an DEVPATH environment variable and set

<developmentMode developerInstallation="true" />

in machine.config file on each developer's workstation. The only thing you need to do is to copy any new version in your folder where DEVPATH variable points.

您可以尝试使用一个文件夹来复制项目之间共享的所有程序集,然后创建一个 DEVPATH 环境变量并

<developmentMode developerInstallation="true" />

在每个开发人员的工作站上的 machine.config 文件中进行设置。您唯一需要做的就是在 DEVPATH 变量指向的文件夹中复制任何新版本。

Also divide your solution into few smaller solutions if possible.

如果可能,还将您的解决方案分成几个较小的解决方案。

回答by jyoung

This may not be best pratice, but this is how I work.

这可能不是最佳实践,但这就是我的工作方式。

I noticed that Managed C++ dumps all of its binaries into $(SolutionDir)/'DebugOrRelease'. So I dumped all my C# projects there too. I also turned off the "Copy Local" of all references to projects in the solution. I had noticable build time improvement in my small 10 project solution. This solution is a mixture of C#, managed C++, native C++, C# webservice, and installer projects.

我注意到托管 C++ 将其所有二进制文件转储到 $(SolutionDir)/'DebugOrRelease' 中。所以我也把我所有的 C# 项目都扔到了那里。我还关闭了对解决方案中项目的所有引用的“复制本地”。在我的 10 个小型项目解决方案中,我的构建时间得到了显着改善。此解决方案是 C#、托管 C++、本机 C++、C# web 服务和安装程序项目的混合体。

Maybe something is broken, but since this is the only way I work, I do not notice it.

也许有些东西坏了,但因为这是我唯一的工作方式,所以我没有注意到。

It would be interesting to find out what I am breaking.

找出我正在破坏的东西会很有趣。

回答by Bruno Shine

You can have your projects references pointing to the debug versions of the dlls. Than on your msbuild script, you can set the /p:Configuration=Release, thus you will have a release version of your application and all satellite assemblies.

您可以让您的项目引用指向 dll 的调试版本。比在您的 msbuild 脚本上,您可以设置/p:Configuration=Release,因此您将拥有应用程序和所有附属程序集的发布版本。

回答by Torbj?rn Gyllebring

If you got the dependency structure defined via project references or via solution level dependencies it's safe to turn of "Copy Local" I would even say that it's a best practice todo so since that will let you use MSBuild 3.5 to run your build in parallel (via /maxcpucount) without diffrent processes tripping over each other when trying to copy referenced assemblies.

如果您通过项目引用或解决方案级别的依赖项定义了依赖项结构,那么关闭“本地复制”是安全的,我什至会说这是一个最佳实践,因为这将使您可以使用 MSBuild 3.5 并行运行您的构建(通过 /maxcpucount) 尝试复制引用的程序集时,没有不同的进程相互绊倒。

回答by Julien Hoarau

I'll suggest you to read Patric Smacchia's articles on that subject :

我建议您阅读 Patric Smacchia 关于该主题的文章:

CC.Net VS projects rely on the copy local reference assembly option set to true. [...] Not only this increase significantly the compilation time (x3 in the case of NUnit), but also it messes up your working environment. Last but not least, doing so introduces the risk for versioning potential problems. Btw, NDepend will emit a warning if it founds 2 assemblies in 2 different directories with the same name, but not the same content or version.

The right thing to do is to define 2 directories $RootDir$\bin\Debug and $RootDir$\bin\Release, and configure your VisualStudio projects to emit assemblies in these directories. All project references should reference assemblies in the Debug directory.

CC.Net VS 项目依赖于设置为 true 的复制本地引用程序集选项。[...] 这不仅显着增加了编译时间(在 NUnit 的情况下为 x3),而且还会弄乱您的工作环境。最后但并非最不重要的一点是,这样做会带来版本控制潜在问题的风险。顺便说一句,如果 NDepend 在 2 个不同目录中发现 2 个具有相同名称但内容或版本不同的程序集,则会发出警告。

正确的做法是定义 2 个目录 $RootDir$\bin\Debug 和 $RootDir$\bin\Release,并将您的 VisualStudio 项目配置为在这些目录中发出程序集。所有项目引用都应引用 Debug 目录中的程序集。

You could also read this articleto help you reduce your projects number and improve your compilation time.

您还可以阅读本文以帮助您减少项目数量并缩短编译时间。