Как быстрее найти класс, используя кодовую модель 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") проверка означает, что вы действительно действительно читаете первую строку из большинства файлов, прежде чем перейдете к более дорогой проверке содержимого. Недостатком этого является, конечно, то, что вы должны помнить, чтобы пометить анализируемые файлы, если вы добавляете новые.

Другие вопросы по тегам