문제

The CodeModel is a powerful tool to discover code inside Visual Studio. We use the CodeModel of VS2013 in combination with T4 to generate most of the tedious code in our 3 tier architecture.

What I've discovered is the following:

We have 2 projects, let's say A and B, of which the one (A) has a reference to (B). In project A our extensions for the model are generated using only the model classes in this project. The classes use several classes in project B. Here's an example of 1 of those classes.

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;
        }
    }
}

We have decorated the class with 2 attributes of which the [CustomResultClass] attribute is used for the code generation.

The problem is that when we find the CodeModel for this class in project A these attributes are not available. I've written a search routine that searches the entire solution for the class. Here's the code:

    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;
    }

This code works fine and we do get the right CodeModel for the class but the code is very, very slow. My guess is that not only traversing all the projects and project items makes it slow but I have the feeling that during the resolution of the the CodeModels found the text in the classes is parsed.

How can we improve the search algorithm in such a way that performance becomes acceptable?

도움이 되었습니까?

해결책

You are correct that the slowness in general is coming from having to parse the content of the files to generate the CodeElements. I solved this problem in one of our projects by limiting which files I processed based on some basic heuristics about what I was searching for.

You can do a simple String.Contains to see if something you care about is even in the file before you parse it. Here is a simple example of this, you need to do the FileCodeModel null check last as accessing that property requires some parsing to take place.

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);
}

The above approach is the one I took and worked well for us. It still requires reading all the content of all the ".cs" files in the project and looking for something in their content but that is faster than parsing them all.

One other approach I thought of if you happen to have an insane amount of files in your project but know very few really need to be parsed is to actually mark the files that way with a comment on the first line of the file for instance if you made the first line of a file you want to consider for parsing be //PARSEME then the above code can be modified to.

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);
}

With the addition of the File.ReadLines(fileName).First().Contains("//PARSEME") check it means that you are only really reading the first line from most files before you get to the more expensive contains check. The downside of this is of course that you need to remember to mark the parse-able files if you add new ones.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top