четверг, 22 сентября 2011 г.

Удаление неиспользуемых сборок из .NET проекта

Когда-то во время учебы в университете, преподаватель, проверяя лабораторную работу по C++, вдруг неожиданно для меня задал вопрос: “А зачем вам здесь #include “%имя_библиотеки%”? Вы можете пояснить, для каких частей кода нужна каждая директива include?” Та директива, что «бросилась ему в глаза», была добавлена при попытке использовать какой-то класс. Класс, видимо, не прижился в лабораторной и его использование было благополучно удалено, а include остался…

Программируя в С#, с использованием Visual Studio, мы так же сталкиваемся с неиспользуемыми директивами using. Но Visual Studio может помочь справиться с проблемой, достаточно для .cs файла вызвать команду “Remove Unused Usings”. Правда есть еще одно место, которое так же не мешало бы время от времени чистить. Это ссылки (References) проекта. Как ни печально, но для C# проекта такой команды нет. В MS Connect даже баг создали по этому поводу. А вот для VB.NET проектов такая функция есть (найти её можно в свойствах проекта), но по злой иронии судьбы для VB.NET проектов нет команды для удаления неиспользуемых Imports (usings в C#) :)

Подогреваемые жаждой сделать полезное коллегам, независимые разработчики решили написать небольшие расширения для Visual Studio. А тут еще и Extension Manager из Visual Studio 2010 так упростил процесс распространения расширений. Пример таких расширений можно найти здесь и здесь. Невозможно судить об алгоритмах, используемых в этих расширениях. Хотя не буду скрывать, что после того как первое расширение бессовестно удалило из проекта приличную часть реально нужных для компиляции сборок, мы все таки посмотрели его рефлектором… Разбираться со вторым уже не стали. В общем-то, проблема одинакова, а ключевое словосочетание можно найти в пред-предыдущем предложении: нужных для компиляции.

Рассмотрим простой пример. Пусть есть 3 проекта – 3 сборки. Сборка Assembly_A определяет класс Class_A, сборка Assembly_B определяет класс Class_B, унаследованный от класса Class_A из сборки Assembly_A. У каждого класса есть различные методы, скажем метод класса Class_A это Method_A, а метод класса Class_B – Method_B. В третьей сборке (Assembly_C) мы хотим использовать класс Class_B. Для этого в проекте добавляем ссылки на сборки Assembly_A и Assembly_B, после чего в каком-то из классов создаем экземпляр класса Class_B, вызываем метод Method_B и компилируем проект. Сборка Assembly_C готова, давайте откроем её с помощью ildasm.exe и взглянем на манифест:
// 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
Это что же получается?! Assembly_A мы добавили к проекту, а она и не нужна? Открываем Visual Studio и удаляем из проекта Assembly_C ссылку на сборку Assembly_A. Компилируем и… получаем ошибку “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'.

Важно понимать причину такого поведения. В проекте нигде нет явного обращения к типам сборки Assembly_A, поэтому ссылка на эту сборку не включается в манифест сборки проекта (Assembly_C). В то же время один из типов сборки Assembly_B используется в проекте. Фактически, получается, что для времени выполнения (runtime) достаточно иметь ссылку на сборку Assembly_B. А сборки, от которых она зависит, CLR получит уже из её манифеста и так же загрузит. Но для компилятора (compile time) важно иметь в проекте Assembly_C ссылки и на сборку Assembly_B и на сборку Assembly_A, ведь он должен знать все об используемом классе Class_B, в том числе и её предков. Хорошая статья о зависимостях сборок была опубликована в MSDN Magazine, прочитать её можно здесь.

Не важно, где в вашем проекте используется класс: как поле какого-то класса, как параметр метода, как атрибут и т.п. Важно понимать то, что у компилятора должна быть возможность получить полную информацию обо всех типах, используемых в проекте. Мы должны четко указывать сборку, которую хотим использовать, ведь класс может существовать в разных версиях одной сборки (даже если компилятор сможет найти сборку (скажем в GAC), то, как ему выбрать нужную, если их несколько?). Вот, что должно быть основной идеей при разработке программы способной находить неиспользуемые в проекте сборки, т.е. такие сборки которые не требуются для компиляции.

Исследование зависимостей классов проекта служит основой расширения Reference Assistant, которое мы разработали в Lardite Group. Это бесплатное расширение доступное в Visual Studio Gallery, кроме того, вы можете загрузить исходный код Reference Assistant со страницы проекта на CodePlex.

Именно с анализа иерархии классов начался Reference Assistant. Постепенно к нему добавился анализ иерархии интерфейсов, анализ атрибутов и типов их параметров, анализ импортированных типов (например, из COM библиотеки), типов перемещенных в другую сборку. Да, есть и такие! Простой пример - ObservableCollection перекочевал из сборки WindowsBase.dll (fx3.5) в System.dll (fx4.0).

Мне нравится пример с анализом перегруженных методов. Предположим, в сборке Assembly_B определен класс Class_B, в котором метод SetCode перегружен. Пусть две его перегрузки принимают по одному параметру: один типа System.Int32, другой Assembly_A.Class_A. В сборке проекта (Assembly_C) вызывается один из перегруженных методов SetCode класса Class_B принимающий один параметр. В этом случае компилятор должен знать всё о типах параметров обоих методов, чтобы выбрать наиболее подходящий. А это значит, что сборки, в которых есть определения типов участвующих в иерархии, должны быть в ссылках проекта. Т.е. в нашем случае в ссылках проекта Assembly_C должна быть ссылка на сборки Assembly_A и Assembly_B. Описанный пример в виде кода:
// 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…
    }
  }
}
Это самое основное, что хотелось рассказать. Конечно, во время разработки, мы столкнулись со множеством нюансов, описать которые в одной статье было бы перебором. Но о самых интересных мы непременно постараемся написать в других статьях. В заключении, хочется пару слов сказать об использовании Reference Assistant.

Как я уже говорил ранее, скачать Reference Assistant можно либо с CodePlex, либо с Visual Studio Gallery. Между ними есть небольшое различие – расширение, выложенное в Gallery, нельзя использовать в Express редакции Visual Studio (это ограничение Visual Studio Gallery), но расширение с CodePlex можно.

Самый простой способ установки - использовать Extension Manager, утилиту Visual Studio.

Для удаления неиспользуемых сборок в контекстном меню проекта или ссылок проекта (папка References) выбрать пункт “Remove Unused References”.

Перед удалением неиспользуемых сборок будет показано окно для подтверждения списка. Вы можете редактировать это список, если уверены, что сборка нужна по каким-либо причинам (например, динамически подключается к приложению в зависимости от настроек в файле конфигурации).

Можно так же отключить показ окна “Unused References List” с помощью опции “Don’t show this dialog next time”. Снова включить можно в конфигурации расширения: меню Tools -> Options… -> Reference Assistant.

Более подробную информацию можно найти в документации на http://refassistant.codeplex.com в разделе Downloads. User’s guide описывает работу с Reference Assistant. В нем так же описано, как включить протоколирование ошибок. Описание ошибки вы можете отправить нам по адресу RefAssistant[at]lardite.com или зарегистрировать её в трекере ошибок. Developer’s guide описывает критерии оценки сборки, на которую ссылается проект, а также дает представление об общей архитектуре расширения.

Комментариев нет:

Отправить комментарий