Как быстрее найти класс, используя кодовую модель Visual Studio?
CodeModel - это мощный инструмент для обнаружения кода в Visual Studio. Мы используем CodeModel VS2013 в сочетании с T4 для генерации большей части утомительного кода в нашей трехуровневой архитектуре.
Я обнаружил следующее:
У нас есть 2 проекта, скажем, A и B, один из которых (A) имеет ссылку на (B). В проекте A наши расширения для модели генерируются с использованием только классов модели в этом проекте. Классы используют несколько классов в проекте B. Вот пример 1 из этих классов.
using Common.Library;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace Projecten.Model.DataContracts.Statistiek
{
[DataObject]
[CustomResultClass("FactuurStatistiek")]
public partial class FactuurStatistiek : BaseStatistiek
{
public FactuurStatistiek Copy()
{
FactuurStatistiek copy = new FactuurStatistiek();
copy.AddRange(this);
return copy;
}
}
}
Мы украсили класс двумя атрибутами, из которых атрибут [CustomResultClass] используется для генерации кода.
Проблема в том, что когда мы находим CodeModel для этого класса в проекте A, эти атрибуты недоступны. Я написал процедуру поиска, которая ищет все решение для класса. Вот код:
public CodeType CodeType
{
get
{
if (m_CodeType == null)
{
m_CodeType = GetCodeType();
}
return m_CodeType;
}
}
/// <summary>
/// Get the CodeType for the property
/// </summary>
private CodeType GetCodeType()
{
string name = Name;
CodeType codeType = null;
if (name == "FactuurStatistiek")
{
codeType = FindCodeType(CodeProperty.Type.AsFullName);
}
if (codeType == null)
{
CodeTypeRef codeTypeRef = CodeProperty.Type;
if (codeTypeRef.CodeType.IsCodeType)
{
codeType = codeTypeRef.CodeType;
}
if (codeType == null)
{
if (string.IsNullOrEmpty(CodeProperty.Type.AsFullName))
{
codeType = CodeModel.CodeTypeFromFullName(CodeProperty.Type.AsString);
}
else
{
codeType = CodeModel.CodeTypeFromFullName(CodeProperty.Type.AsFullName);
}
}
}
return codeType;
}
private CodeType FindCodeType(string fullName)
{
CodeType codeType = null;
foreach (Project project in Metadata.Dte.Solution.Projects)
{
foreach (ProjectItem projectItem in project.ProjectItems)
{
codeType = RecurseProjectItems(projectItem.ProjectItems, fullName);
if (codeType != null)
{
return codeType;
}
}
}
return null;
}
private CodeType RecurseProjectItems(ProjectItems projectItems, string fullName)
{
CodeType codeType = null;
if (projectItems != null)
{
foreach (ProjectItem projectItem in projectItems)
{
if (projectItem.FileCodeModel != null)
{
codeType = RecurseCodeElements(projectItem.FileCodeModel.CodeElements, fullName);
if (codeType != null)
{
break;
}
}
codeType = RecurseProjectItems(projectItem.ProjectItems, fullName);
if (codeType != null)
{
break;
}
}
}
return codeType;
}
private CodeType RecurseCodeElements(CodeElements codeElements, string fullName)
{
CodeType codeType = null;
if (codeElements != null)
{
foreach (CodeElement codeElement in codeElements)
{
if (codeElement.Kind == vsCMElement.vsCMElementNamespace)
{
codeType = RecurseCodeElements(((CodeNamespace)codeElement).Members, fullName);
}
else if (codeElement.Kind == vsCMElement.vsCMElementClass)
{
string classFullName = ((CodeClass)codeElement).FullName;
if (((CodeClass)codeElement).FullName == fullName)
{
codeType = (CodeType)codeElement;
}
}
if (codeType != null)
{
break;
}
}
}
return codeType;
}
Этот код работает нормально, и мы получаем правильный CodeModel для класса, но код очень, очень медленный. Я предполагаю, что не только обход всех проектов и элементов проекта делает его медленным, но у меня есть ощущение, что во время разрешения найденных CodeModels текст в классах анализируется.
Как мы можем улучшить алгоритм поиска таким образом, чтобы производительность стала приемлемой?
1 ответ
Вы правы в том, что в целом медлительность возникает из-за необходимости разбирать содержимое файлов для генерации CodeElements
, Я решил эту проблему в одном из наших проектов, ограничив количество обрабатываемых файлов на основе некоторой базовой эвристики о том, что я искал.
Вы можете сделать простой String.Contains
чтобы увидеть, есть ли что-то, о чем вы заботитесь, даже в файле, прежде чем анализировать его. Вот простой пример этого, вам нужно сделать FileCodeModel
null check last, поскольку для доступа к этому свойству требуется некоторый анализ.
var fileName = projectItem.FileNames[1];
var shouldProcessFile = File.Exsits(fileName)
&& Path.GetExtension(fileName) == ".cs"
&& File.ReadAllText(fileName).Contains("FactuurStatistiek")
&& projectItem.FileCodeModel != null;
if(shouldProcessFile)
{
codeType = RecurseCodeElements(projectItem.FileCodeModel.CodeElements, fullName);
}
Вышеупомянутый подход - тот, который я выбрал и работал хорошо для нас. Для этого по-прежнему требуется прочитать все содержимое всех файлов ".cs" в проекте и найти что-то в их содержимом, но это быстрее, чем их анализ.
Еще один подход, о котором я подумал, если у вас в вашем проекте безумное количество файлов, но вы знаете, что очень немногие действительно нужно анализировать, это на самом деле пометить файлы таким образом с комментарием к первой строке файла, например, если вы сделал первую строку файла, который вы хотите рассмотреть для анализа //PARSEME
тогда приведенный выше код может быть изменен на.
var fileName = projectItem.FileNames[1];
var shouldProcessFile = File.Exsits(fileName)
&& Path.GetExtension(fileName) == ".cs"
&& File.ReadLines(fileName).First().Contains("//PARSEME")
&& File.ReadAllText(fileName).Contains("FactuurStatistiek")
&& projectItem.FileCodeModel != null;
if(shouldProcessFile)
{
codeType = RecurseCodeElements(projectItem.FileCodeModel.CodeElements, fullName);
}
С добавлением File.ReadLines(fileName).First().Contains("//PARSEME")
проверка означает, что вы действительно действительно читаете первую строку из большинства файлов, прежде чем перейдете к более дорогой проверке содержимого. Недостатком этого является, конечно, то, что вы должны помнить, чтобы пометить анализируемые файлы, если вы добавляете новые.