我有一个很大的C#解决方案文件(大约100个项目),我正在努力缩短构建时间.我认为"复制本地"在很多情况下对我们来说是浪费的,但我想知道最佳实践是什么.

在我们的.sln中,我们有依赖于程序集B的应用程序A,而程序集B依赖于程序集C.在我们的示例中,有几十个"B"和少数几个"C".因为这些都包含在.sln中,所以我们使用项目引用.当前所有程序集都构建到$(SolutionDir)/Debug(或Release)中.

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

后续行动:

很多回复建议将构建拆分成更小的.sln文件……在上面的示例中,我将首先构建基础类"C",然后构建大部分模块"B",然后构建几个应用程序"A".在这个模型中,我需要从B到C的非项目引用.我在那里遇到的问题是"Debug"或"Release"被烘焙到提示路径中,并且我最终针对"C"的调试版本构建了"B"的发布版本.

对于那些将建筑拆分为多个的人.sln文件,你如何处理这个问题?

推荐答案

在之前的一个项目中,我使用了一个带有项目参考的大型解决方案,并遇到了性能问题.解决方案有三个方面:

  1. 始终将Copy Local属性设置为False,并通过自定义msbuild步骤强制执行此操作

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

  3. 框架附带的默认cs目标计算要复制到当前正在生成的项目的输出目录的引用集.由于这需要在"References"关系下计算传递闭包,因此成本可能会很高.我的解决方法是在一个公共目标文件(例如Common.targets)中重新定义GetCopyToOutputDirectoryItems个目标,该文件在导入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>
    

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

The redefined GetCopyToOutputDirectoryItems can 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.targets into Common.targets.

为完整起见,生成的目标定义随后变为:

<!-- 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.

.net相关问答推荐

AutoMapper在实体框架查询后不知从哪里带来数据

如何通过在后台预加载流项来优化流迭代的性能?

在本地运行 Azure 函数会在 .NET7 升级后出现无运行时错误

为什么我在环境变量中有不同的值?

使用 SSH.NET 查找具有特定文件名的最新 SFTP 文件

判断内部异常的最佳方法?

为什么具有可为空值的 struct 的 HashSet 非常慢?

C#字符串的GetHashCode()是如何实现的?

lock() 是否保证按请求的顺序获得?

在 .NET 中获取执行 exe 路径的最佳方法是什么?

如何判断 IOException 是否为 Not-Enough-Disk-Space-Exception 类型?

AutoMapper 的替代品

有什么方法可以使用 .NET 应用程序使用 git 吗?

如何以编程方式判断类型是 struct 还是类?

为什么我得到 411 Length required 错误?

为什么 C# 多维数组不实现 IEnumerable

如何授予所有用户对我的应用程序创建的文件的完全权限?

无法在 Windows 10 上安装 Windows SDK 7.1

C# (.NET) 设计缺陷

ADO.NET Entity Framework:更新向导不会添加表