C#: Automated removal of unnecessary assembly references?

1.8k views Asked by At

I'm working with a large code base with a huge number of projects, each of which has a handful (and in some cases, enormous) amount of references to others. There has been substantial refactoring done on this codebase over time, and as a result there are many assemblies that are referenced by some projects only because they used to contain a class that has since moved elsewhere; that sort of thing.

ReSharper has a tool integrated into the IDE that allows users to find code that actually uses a given reference for a given project, however in order to turn this into a solution we would need to get a person to right click on every reference in every project and then to actually check for no usages and then delete them, which is not only a long process but also it borders on torture.

I would like to be able to automate this process so that we just run it and unnecessary references are removed; we could then perhaps integrate it into some sort of regular process so that overlooked mistakes would be caught.

The two options I've thought of would be A) Automate ReSharper with Powershell, if possible, or B) perhaps Visual Studio 2010 Architecture dependency diagrams could handle this, and maybe in a scriptable way if I'm lucky.

My questions are these:

  • Can ReSharper be scripted for something like this?
  • Does VS2010 Architecture provide for removal of unused references in any sort of batch/automated way?
3

There are 3 answers

2
x0n On BEST ANSWER

You should be able to do this with just plain powershell:

1) load visual studio with your solution

2) compile entire solution

3) leave VS running and start powershell.exe

4) grab a reference to the still running instance of VS's DTE from the ROT (important - ensure only one instance running - if its elevated, powershell should be too):

ps> $dte = [System.Runtime.InteropServices.Marshal]::GetActiveObject("visualstudio.dte")

5) Test by enumerating all projects in the solution with their references:

ps> $dte.solution.projects | select @{l="name";e={$_.name}}, `
        @{l="references";e={$_.object.references|select -exp name}} | ft -auto

... dumps all project names and references ...

6) Now, write some script to traverse the solution folder and projects

7) when you hit the bin\ folder, load up the assembly with reflection only load:

$assembly = [reflection.assembly]::reflectiononlyload($dll)

8) get the actual referenced assemblies in your output assembly

$refs = $assembly.getreferencedassemblies()

9) compare the actual referenced assemblies to the assemblies referenced in the project and remove the redundant ones via the VS DTE object model

# example
$currentproj.object.references.item("system.core").remove()
$currentproj.save()

10) profit!

This works because .net only links assemblies that are actually referenced in the code. Sorry I can't post a full working example but this should be enough to get you started.

-Oisin

0
stej On

Not a full solution, but have a look at Resolve csproj dependencies when projects are dependent on assemblies from bin as well and How to find wrong assembly dependencies via PowerShell. There is a way how to find referencies to other projects and how to find referencies in an assembly.

The second part is the same as Oisin proposed. The first one is slightly different - it grabs the referencies from csproj file (taken as xml file and processed in this way).

Take it at least as an inspiration ;)

0
Csaba Toth On

I followed @x0n's instructions but it didn't work out well for me. Maybe I was missing something. I have to admit that I could not load my dlls with ReflectionOnlyLoad because of a COM error. So I loaded them with LoadFile. The assembly references provided by LoadFile was exactly the same when I loaded the Assembly with Reflector. In the end my PowerShell script generated a list showing the references which are in the project but not loaded by the assembly. In theory these supposed to be the "unnecessary" references. When I started to remove them, the build failed. For example in case of my first project the lack of any of the 4 "unnecessary" references would cause the build to fail. I skim through the output of the powershell script and it lists "System" too for example. If I remove it the compiler doesn't even complain about a bunch of "using System;" but it fails before that - stating that I need to reference that assembly because of an interface... BTW, our project is not a toy, it involves 140+ dlls (almost half of which is Test nUnit dll though) and I thought I'll do some houskeeping. My script (which doesn't go recursively into project folders - some projects contain more dlls) so maybe someone can use it:

`
$dte = [System.Runtime.InteropServices.Marshal]::GetActiveObject("visualstudio.dte")

$binfiles=Get-ChildItem C:\YourSourcePath\bin\debug
$dlls=$binfiles | where { $_.extension -eq ".dll" -and $_.name -like "*YourCompanyName*" }

foreach ($dll in $dlls) {
    foreach($prj in $dte.solution.projects) {
        if ($prj.Kind -eq "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") {
            if ($prj.Properties.Item("OutputFileName").Value -eq $dll.Name) {
                $loaded = [reflection.assembly]::LoadFile($dll.FullName)
                $refs = $loaded.GetReferencedAssemblies()
                Write "============="
                Write $dll.Name
                Write "-------------"
                foreach($pref in $prj.Object.References) {
                    $found = 0
                    foreach($ref in $refs) {
                        if ($ref.Name -eq $pref.Name) {
                            $found = 1
                            break;
                        }
                    }
                    if ($found -eq 0) {
                        Write $pref.Name
                    }
                }
            }
        }
    }
}

`