Removing unused assemblies from a .NET project
Once, while studying at a university, a teacher, while checking laboratory work in C ++, suddenly asked me a question: “Why do you need here #include“% library_name% ”? Can you explain what parts of the code each include directive is for? ” That directive that “caught his eye” was added when trying to use some class. The class, apparently, did not take root in the laboratory and its use was safely deleted, but the include remained ...
When programming in C # using Visual Studio, we also encounter unused using directives. But Visual Studio can help to cope with the problem, it is enough for the .cs file to call the command “Remove Unused Usings”. True, there is another place that also would not hurt to clean from time to time. These are the References of the project. Sadly, there is no such command for a C # project. MS Connect even created a bug about this. But there is such a function for VB.NET projects (you can find it in the project properties), but by a bad irony, there is no command for VB.NET projects to delete unused Imports (usings in C #) :)
Fueled by a thirst to do good for colleagues, independent developers decided to write small extensions for Visual Studio. And then there is the Extension Manager from Visual Studio 2010 so simplified the process of distributing extensions. An example of such extensions can be found here and here . It is impossible to judge the algorithms used in these extensions. Although I will not hide that after the first extension shamelessly removed from the project a decent part of the assemblies really needed for compilation, we still looked at it with a reflector ... We did not deal with the second one anymore. In general, the problem is the same, and the key phrase can be found in the pre-previous sentence: necessary for compilation.
Consider a simple example. Let there be 3 projects - 3 assemblies. Assembly_A defines the class Class_A, assembly_B defines the class Class_B inherited from the class Class_A from assembly_A. Each class has different methods, say the method of the Class_A class is Method_A, and the method of the Class_B class is Method_B. In the third assembly (Assembly_C), we want to use the Class_B class. To do this, add references to assemblies Assembly_A and Assembly_B in the project, after which we create an instance of the Class_B class in some of the classes, call the Method_B method and compile the project. Assembly_C is ready, let's open it with ildasm.exe and take a look at the manifest:
What is it that turns out ?! Assembly_A we added to the project, but it is not needed? Open Visual Studio and remove the assembly reference Assembly_A from the Assembly_C project. Compile and ... get the error “The type 'Assembly_A.Class_A' is defined in an assembly that is not referenced. You must add a reference to assembly 'Assembly_A, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null'. ”
It is important to understand the reason for this behavior. Nowhere in the project is there explicit access to assembly types Assembly_A, therefore, a reference to this assembly is not included in the manifest of the project assembly (Assembly_C). At the same time, one of the assembly types Assembly_B is used in the project. In fact, it turns out that for runtime it’s enough to have a reference to assembly Assembly_B. And the assemblies on which it depends will receive the CLR from its manifest and load it the same way. But for the compiler (compile time) it is important to have references to the Assembly_B assembly and Assembly_A in the Assembly_C project, because it must know everything about the used Class_B class, including its ancestors. A good article on build dependencies was published on MSDN Magazine, and you can read it here .
It doesn’t matter where the class is used in your project: as a field of a class, as a parameter of a method, as an attribute, etc. It is important to understand that the compiler should be able to get complete information about all types used in the project. We must clearly indicate the assembly that we want to use, because the class can exist in different versions of the same assembly (even if the compiler can find the assembly (say in the GAC), then how can it choose the one if there are several?). This is what should be the main idea when developing a program capable of finding assemblies not used in a project, i.e. such assemblies that are not required for compilation.
A study of the dependencies of project classes serves as the basis for the Reference Assistant extension that we developed at the Lardite Group. This is a free extension available in the Visual Studio Gallery , and you can also download the reference Assistant source code from the project page on CodePlex.
It was with the analysis of the class hierarchy that the Reference Assistant began. Gradually, an analysis of the hierarchy of interfaces, analysis of attributes and types of their parameters, analysis of imported types (for example, from the COM library), types moved to another assembly was added to it . Yes, there are some! A simple example - ObservableCollection migrated from the assembly of WindowsBase.dll (fx3.5) to System.dll (fx4.0).
I like the example of overloaded method analysis. Assume that the class Class_B is defined in the Assembly_B assembly, in which the SetCode method is overloaded. Let its two overloads take one parameter: one of type System.Int32, the other of Assembly_A.Class_A. In the project assembly (Assembly_C), one of the overloaded SetCode methods of the Class_B class is called that takes one parameter. In this case, the compiler must know everything about the parameter types of both methods in order to choose the most suitable one. And this means that assemblies in which there are definitions of the types involved in the hierarchy should be in the project links. Those. in our case, the references to the Assembly_C project should contain a reference to the Assembly_A and Assembly_B assemblies. The described example in the form of code:
This is the most basic thing I wanted to tell. Of course, during development, we were faced with many nuances, which would be too much to describe in one article. But we will certainly try to write about the most interesting in other articles. In conclusion, I want to say a few words about using the Reference Assistant.
As I said earlier, you can download the Reference Assistant from either CodePlex or Visual Studio Gallery . There is a slight difference between them - the extension laid out in Gallery cannot be used in the Express edition of Visual Studio (this is a limitation of the Visual Studio Gallery), but the extension with CodePlex is possible.
The easiest way to install is to use Extension Manager, a Visual Studio utility.
To remove unused assemblies in the context menu of the project or project links (the References folder), select “Remove Unused References”.
Before deleting unused assemblies, a window to confirm the list will be displayed. You can edit this list if you are sure that the assembly is needed for some reason (for example, it is dynamically connected to the application depending on the settings in the configuration file).
You can also turn off the display of the “Unused References List” window using the “Don't show this dialog next time” option. You can enable it again in the extension configuration: menu Tools -> Options ... -> Reference Assistant.
More information can be found in the documentation at http://refassistant.codeplex.comin the Downloads section. User's guide describes how to use the Reference Assistant. It also describes how to enable error logging. You can send a description of the error to us at RefAssistant [at] lardite.com or register it in the error tracker . The Developer's guide describes the evaluation criteria for the assembly referenced by the project, and gives an idea of the general architecture of the extension.
When programming in C # using Visual Studio, we also encounter unused using directives. But Visual Studio can help to cope with the problem, it is enough for the .cs file to call the command “Remove Unused Usings”. True, there is another place that also would not hurt to clean from time to time. These are the References of the project. Sadly, there is no such command for a C # project. MS Connect even created a bug about this. But there is such a function for VB.NET projects (you can find it in the project properties), but by a bad irony, there is no command for VB.NET projects to delete unused Imports (usings in C #) :)
Fueled by a thirst to do good for colleagues, independent developers decided to write small extensions for Visual Studio. And then there is the Extension Manager from Visual Studio 2010 so simplified the process of distributing extensions. An example of such extensions can be found here and here . It is impossible to judge the algorithms used in these extensions. Although I will not hide that after the first extension shamelessly removed from the project a decent part of the assemblies really needed for compilation, we still looked at it with a reflector ... We did not deal with the second one anymore. In general, the problem is the same, and the key phrase can be found in the pre-previous sentence: necessary for compilation.
Consider a simple example. Let there be 3 projects - 3 assemblies. Assembly_A defines the class Class_A, assembly_B defines the class Class_B inherited from the class Class_A from assembly_A. Each class has different methods, say the method of the Class_A class is Method_A, and the method of the Class_B class is Method_B. In the third assembly (Assembly_C), we want to use the Class_B class. To do this, add references to assemblies Assembly_A and Assembly_B in the project, after which we create an instance of the Class_B class in some of the classes, call the Method_B method and compile the project. Assembly_C is ready, let's open it with ildasm.exe and take a look at the manifest:
// Metadata version: v4.0.30319
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 4:0:0:0
}
.assembly extern Assembly_B
{
.ver 1:0:0:0
}
.assembly Assembly_C
{
// метаданные для текущей сборки
}
.module Assembly_C.exe
// MVID: {B387984A-3515-4B26-9450-592FCF5FB6FA}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000003 // ILONLY 32BITREQUIRED
// Image base: 0x014C0000
* This source code was highlighted with Source Code Highlighter.
What is it that turns out ?! Assembly_A we added to the project, but it is not needed? Open Visual Studio and remove the assembly reference Assembly_A from the Assembly_C project. Compile and ... get the error “The type 'Assembly_A.Class_A' is defined in an assembly that is not referenced. You must add a reference to assembly 'Assembly_A, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null'. ”
It is important to understand the reason for this behavior. Nowhere in the project is there explicit access to assembly types Assembly_A, therefore, a reference to this assembly is not included in the manifest of the project assembly (Assembly_C). At the same time, one of the assembly types Assembly_B is used in the project. In fact, it turns out that for runtime it’s enough to have a reference to assembly Assembly_B. And the assemblies on which it depends will receive the CLR from its manifest and load it the same way. But for the compiler (compile time) it is important to have references to the Assembly_B assembly and Assembly_A in the Assembly_C project, because it must know everything about the used Class_B class, including its ancestors. A good article on build dependencies was published on MSDN Magazine, and you can read it here .
It doesn’t matter where the class is used in your project: as a field of a class, as a parameter of a method, as an attribute, etc. It is important to understand that the compiler should be able to get complete information about all types used in the project. We must clearly indicate the assembly that we want to use, because the class can exist in different versions of the same assembly (even if the compiler can find the assembly (say in the GAC), then how can it choose the one if there are several?). This is what should be the main idea when developing a program capable of finding assemblies not used in a project, i.e. such assemblies that are not required for compilation.
A study of the dependencies of project classes serves as the basis for the Reference Assistant extension that we developed at the Lardite Group. This is a free extension available in the Visual Studio Gallery , and you can also download the reference Assistant source code from the project page on CodePlex.
It was with the analysis of the class hierarchy that the Reference Assistant began. Gradually, an analysis of the hierarchy of interfaces, analysis of attributes and types of their parameters, analysis of imported types (for example, from the COM library), types moved to another assembly was added to it . Yes, there are some! A simple example - ObservableCollection migrated from the assembly of WindowsBase.dll (fx3.5) to System.dll (fx4.0).
I like the example of overloaded method analysis. Assume that the class Class_B is defined in the Assembly_B assembly, in which the SetCode method is overloaded. Let its two overloads take one parameter: one of type System.Int32, the other of Assembly_A.Class_A. In the project assembly (Assembly_C), one of the overloaded SetCode methods of the Class_B class is called that takes one parameter. In this case, the compiler must know everything about the parameter types of both methods in order to choose the most suitable one. And this means that assemblies in which there are definitions of the types involved in the hierarchy should be in the project links. Those. in our case, the references to the Assembly_C project should contain a reference to the Assembly_A and Assembly_B assemblies. The described example in the form of code:
// Assembly_A.dll
namespace Assembly_A
{
public class Class_A
{
public int Code { get; set; }
}
}
// Assembly_B.dll
using Assembly_A;
namespace Assembly_B
{
public class Class_B
{
public void SetCode(int code)
{
// some actions…
}
public void SetCode(Class_A code)
{
// some actions…
}
}
}
// Assembly_C.dll (проект, который использует Assembly_B)
using Assembly_B;
namespace Assembly_C
{
public class Class_C
{
public void Run()
{
// some actions…
var classB = new Class_B();
classB.SetCode(1);
// some actions…
}
}
}
* This source code was highlighted with Source Code Highlighter.
This is the most basic thing I wanted to tell. Of course, during development, we were faced with many nuances, which would be too much to describe in one article. But we will certainly try to write about the most interesting in other articles. In conclusion, I want to say a few words about using the Reference Assistant.
As I said earlier, you can download the Reference Assistant from either CodePlex or Visual Studio Gallery . There is a slight difference between them - the extension laid out in Gallery cannot be used in the Express edition of Visual Studio (this is a limitation of the Visual Studio Gallery), but the extension with CodePlex is possible.
The easiest way to install is to use Extension Manager, a Visual Studio utility.
To remove unused assemblies in the context menu of the project or project links (the References folder), select “Remove Unused References”.
Before deleting unused assemblies, a window to confirm the list will be displayed. You can edit this list if you are sure that the assembly is needed for some reason (for example, it is dynamically connected to the application depending on the settings in the configuration file).
You can also turn off the display of the “Unused References List” window using the “Don't show this dialog next time” option. You can enable it again in the extension configuration: menu Tools -> Options ... -> Reference Assistant.
More information can be found in the documentation at http://refassistant.codeplex.comin the Downloads section. User's guide describes how to use the Reference Assistant. It also describes how to enable error logging. You can send a description of the error to us at RefAssistant [at] lardite.com or register it in the error tracker . The Developer's guide describes the evaluation criteria for the assembly referenced by the project, and gives an idea of the general architecture of the extension.