198

如何在解决方案中找到未使用的 NuGet 包?

我有许多解决方案,其中安装了很多软件包,并且其中大量被标记为具有更新。

但是,我担心可能会有重大更改,所以我首先想通过删除任何未使用的包来进行清理。

4

9 回答 9

73

ReSharper 2016.1 具有删除未使用的 NuGet 的功能。

它可以在解决方案和解决方案中的每个项目上运行,它执行以下操作:

  1. 分析您的代码并收集对程序集的引用。
  2. 根据程序集的使用情况构建 NuGet 使用情况图。
  3. 没有内容文件、未使用本身和没有使用依赖项的包被假定为未使用并建议删除。

不幸的是,这不适用于project.json项目 ( RSRP-454515 ) 和 ASP.NET 核心项目 ( RSRP-459076 )

于 2016-04-15T10:55:48.983 回答
26

您可以使用 Visual Studio 扩展ResolveUR - 解决未使用的引用

通过解决方案和项目节点处的菜单项解决未使用的引用,包括 Visual Studio 2012/2013/2015 项目中的 nuget 引用解决方案资源管理器工具窗口

这不是一件容易的事,所以我建议之前进行备份和/或提交,只是为了在出现问题时回滚。

于 2015-07-01T17:13:37.247 回答
18

You can accomplish this using ReSharper 2019.1.1.

Right click on the project > Refactor > Remove Unused References.

If your project is small, you can also use: project > Optimize Used References . . .

A window will pop up. Select all references and remove them all. Then go back and re-add the ones that give you a compiler error.

于 2018-06-14T15:32:53.917 回答
14

Visual Studio 2019(版本 16.9)内置了 remove-unused-packages 功能,我们现在需要手动启用它。

转到工具 > 选项 > 文本编辑器 > C# > 高级 >(在分析部分下)勾选显示“已删除未使用的引用”命令

Visual Studio 16.10 版具有删除未使用的引用功能。右键单击项目 > 删除未使用的引用。

在此处输入图像描述

于 2021-05-27T04:18:18.943 回答
7

下面是一个小 PowerShell 脚本,用于查找 .NET Core / .NET 5+ 项目的冗余 NuGet 包。对于每个项目文件,它会删除每个引用一次并检查它是否编译。这将需要很多时间。在此之后,您将获得每个可能被排除的参考的摘要。最后,由您决定应该删除哪些内容。很可能您将无法删除它建议的所有内容(由于依赖关系),但它应该为您提供一个良好的起点。

将下面的脚本保存为 ps1 文件,并将第 89 行中的字符串C:\MySolutionDirectory替换为您要扫描的目录,然后运行 ​​ps1 文件。万一出现问题,请先进行备份。

function Get-PackageReferences {
    param($FileName, $IncludeReferences, $IncludeChildReferences)

    $xml = [xml] (Get-Content $FileName)

    $references = @()

    if($IncludeReferences) {
        $packageReferences = $xml | Select-Xml -XPath "Project/ItemGroup/PackageReference"

        foreach($node in $packageReferences)
        {
            if($node.Node.Include)
            {
                if($node.Node.Version)
                {
                    $references += [PSCustomObject]@{
                        File = (Split-Path $FileName -Leaf);
                        Name = $node.Node.Include;
                        Version = $node.Node.Version;
                    }
                }
            }
        }
    }

    if($IncludeChildReferences)
    {
        $projectReferences = $xml | Select-Xml -XPath "Project/ItemGroup/ProjectReference"

        foreach($node in $projectReferences)
        {
            if($node.Node.Include)
            {
                $childPath = Join-Path -Path (Split-Path $FileName -Parent) -ChildPath $node.Node.Include

                $childPackageReferences = Get-PackageReferences $childPath $true $true

                $references += $childPackageReferences
            }
        }   
    }

    return $references
}

function Get-ProjectReferences {
    param($FileName, $IncludeReferences, $IncludeChildReferences)

    $xml = [xml] (Get-Content $FileName)

    $references = @()

    if($IncludeReferences) {
        $projectReferences = $xml | Select-Xml -XPath "Project/ItemGroup/ProjectReference"

        foreach($node in $projectReferences)
        {
            if($node.Node.Include)
            {
                $references += [PSCustomObject]@{
                    File = (Split-Path $FileName -Leaf);
                    Name = $node.Node.Include;
                }
            }
        }
    }

    if($IncludeChildReferences)
    {
        $projectReferences = $xml | Select-Xml -XPath "Project/ItemGroup/ProjectReference"

        foreach($node in $projectReferences)
        {
            if($node.Node.Include)
            {
                $childPath = Join-Path -Path (Split-Path $FileName -Parent) -ChildPath $node.Node.Include

                $childProjectReferences = Get-ProjectReferences $childPath $true $true

                $references += $childProjectReferences
            }
        }   
    }

    return $references
}

$files = Get-ChildItem -Path C:\MySolutionDirectory -Filter *.csproj -Recurse

Write-Output "Number of projects: $($files.Length)"

$stopWatch = [System.Diagnostics.Stopwatch]::startNew()

$obseletes = @()

foreach($file in $files) {

    Write-Output ""
    Write-Output "Testing project: $($file.Name)"

    $rawFileContent = [System.IO.File]::ReadAllBytes($file.FullName)

    $childPackageReferences = Get-PackageReferences $file.FullName $false $true
    $childProjectReferences = Get-ProjectReferences $file.FullName $false $true

    $xml = [xml] (Get-Content $file.FullName)

    $packageReferences = $xml | Select-Xml -XPath "Project/ItemGroup/PackageReference"
    $projectReferences = $xml | Select-Xml -XPath "Project/ItemGroup/ProjectReference"

    $nodes = @($packageReferences) + @($projectReferences)

    foreach($node in $nodes)
    {
        $previousNode = $node.Node.PreviousSibling
        $parentNode = $node.Node.ParentNode
        $parentNode.RemoveChild($node.Node) > $null

        if($node.Node.Include)
        {
            $xml.Save($file.FullName)

            if($node.Node.Version)
            {
                $existingChildInclude = $childPackageReferences | Where-Object { $_.Name -eq $node.Node.Include -and $_.Version -eq $node.Node.Version } | Select-Object -First 1

                if($existingChildInclude)
                {
                    Write-Output "$($file.Name) references package $($node.Node.Include) ($($node.Node.Version)) that is also referenced in child project $($existingChildInclude.File)."
                    continue
                }
                else 
                {
                    Write-Host -NoNewline "Building $($file.Name) without package $($node.Node.Include) ($($node.Node.Version))... "
                }
            }
            else
            {
                $existingChildInclude = $childProjectReferences | Where-Object { $_.Name -eq $node.Node.Include } | Select-Object -First 1

                if($existingChildInclude)
                {
                    Write-Output "$($file.Name) references project $($node.Node.Include) that is also referenced in child project $($existingChildInclude.File)."
                    continue
                }
                else 
                {
                    Write-Host -NoNewline "Building $($file.Name) without project $($node.Node.Include)... "
                }
            }
        }
        else 
        {
            continue
        }

        dotnet build $file.FullName > $null

        if($LastExitCode -eq 0)
        {
            Write-Output "Building succeeded."

            if($node.Node.Version)
            {
                $obseletes += [PSCustomObject]@{
                    File = $file;
                    Type = 'Package';
                    Name = $node.Node.Include;
                    Version = $node.Node.Version;
                }
            }
            else
            {
                $obseletes += [PSCustomObject]@{
                    File = $file;
                    Type = 'Project';
                    Name = $node.Node.Include;
                }
            }
        }
        else 
        {
            Write-Output "Building failed."
        }


        if($null -eq $previousNode)
        {
            $parentNode.PrependChild($node.Node) > $null
        } 
        else 
        {
            $parentNode.InsertAfter($node.Node, $previousNode.Node) > $null
        }

        # $xml.OuterXml

        $xml.Save($file.FullName)
    }

    [System.IO.File]::WriteAllBytes($file.FullName, $rawFileContent)

    dotnet build $file.FullName > $null

    if($LastExitCode -ne 0)
    {
        Write-Error "Failed to build $($file.FullName) after project file restore. Was project broken before?"
        return
    }
}

Write-Output ""
Write-Output "-------------------------------------------------------------------------"
Write-Output "Analyse completed in $($stopWatch.Elapsed.TotalSeconds) seconds"
Write-Output "$($obseletes.Length) reference(s) could potentially be removed."

$previousFile = $null
foreach($obselete in $obseletes)
{
    if($previousFile -ne $obselete.File)
    {
        Write-Output ""
        Write-Output "Project: $($obselete.File.Name)"
    }

    if($obselete.Type -eq 'Package')
    {
        Write-Output "Package reference: $($obselete.Name) ($($obselete.Version))"
    }
    else
    {
        Write-Output "Project refence: $($obselete.Name)"
    }

    $previousFile = $obselete.File
}

您可以在此处找到更多信息:https ://devblog.pekspro.com/posts/finding-redundant-project-references

于 2021-02-06T10:45:47.297 回答
2

右键单击 Visual Studio 2019 中的 Dotnet 核心项目,您将看到删除未使用的引用的选项。 在此处输入图像描述

于 2021-06-16T08:24:27.647 回答
1

我不认为有一个默认的方法来找出这个。主要原因是这些包可以做各种各样的事情,从引用程序集到将源代码注入您的项目。不过,您可能需要检查Nuget.Extensions。codeplex 上的以下线程讨论了 nuget 包的审计报告。

http://nuget.codeplex.com/discussions/429694

(NuGet 已从 Codeplex 移至 GitHub。上述链接的存档:) https://web.archive.org/web/20171212202557/http://nuget.codeplex.com:80/discussions/429694

于 2013-11-16T04:21:01.083 回答
1

这是体力劳动,但它有效。

  1. 使用 ReSharper 或类似的代码分析工具来识别项目中任何未使用的引用并卸载相应项目中的 nuget。

  2. 有时,卸载的 nuget 仍会停留在“管理 NuGet 包”对话框的“已安装包”和“更新”列表中。关闭 Visual Studio,然后删除该packages文件夹,然后重新打开解决方案并恢复您的 nuget。

于 2015-01-25T12:35:37.147 回答
1

在从最新版本开始的 Visual Studio 2019 和 Visual Studio 2022 中,您可以删除之前评论中报告的未使用的包,但仅适用于 SDK 样式项目。如果您尝试旧项目,例如 .Net Framework,您将看不到此选项。作为解决方法,为了验证,您可以创建两个简单的控制台应用程序:一个使用 .Net Core 或更高版本,一个使用 .Net Framework 4.7 或 4.8。


请参考:删除未使用的引用

于 2021-12-10T12:15:16.893 回答