用于解决方案和项目文件的CodeDOM类(第5部分)





5.00/5 (11投票s)
用于VS解决方案和项目文件的CodeDOM对象。
引言
本文档介绍了如何创建用于建模“.sln”和“.csproj”文件功能的解决方案和项目CodeDOM对象,并可用于解析、修改和保存这些文件。已包含源代码。这是关于CodeDOM系列文章的第5部分,但对于任何希望以编程方式操作VS解决方案和项目文件的人来说,它都可能很有用。在之前的几部分中,我已讨论了“CodeDOMs”,并提供了C# CodeDOM以及WPF UI和IDE,以及C#解析器。
我们需要什么?
这里的主要目的是创建Solution
和Project
类,以通用的方式模拟这些概念的功能——解决方案作为项目的容器,项目作为构建为单个输出程序集的代码文件集合,并具有对其他项目和外部程序集的引用。次要目标是能够加载、修改和保存现有的VS“.sln”和“.csproj”文件,因此需要处理专有格式和数据——我们希望尽可能“隐藏”实现细节,以便与用户隔离开来。目前我们不会为非C#项目添加完全支持,但我们仍然需要能够以有限的方式处理它们(当我们进行解析时,我们将希望从其他语言项目的输出程序集中加载元数据)。
建模解决方案
Solution
主要是一系列Project
的集合,所以我给了它一个Project
的ChildList
。当然,它像所有CodeDOM对象一样派生自CodeObject
,并实现了INamedCodeObject
接口,以及一个新的IFile
接口,因为它通常映射到一个文件。我确定我希望解决方案能够保存整个解决方案的注释集合(消息和“列出的”注释,如“TODO”)——但我发现需要记录每个Annotation
所关联的Project
和/或CodeUnit
,所以我创建了一个CodeAnnotation
包装类,并将解决方案的ObservableCollection
设置为该类型。解决方案还具有ActiveConfiguration
和ActivePlatform
(均为字符串)。有一个LoadOptions
枚举来控制加载过程中的解析、日志记录等,还有一个LoadStatus
枚举用于向任何关联的UI广播进度事件。这些枚举和CodeAnnotation
类是全局作用域的,但目前我选择将它们包含在“Solution.cs”文件中,因为它们很小。
实现细节包括处理“.sln”文件的内容,该文件是具有专有格式的文本文件。它为每个项目都有一个条目,其中包含“项目部分”条目,还有一个全局区域,包含诸如源代码控制、配置信息、项目嵌套等内容的“全局部分”条目。因此,我创建了ProjectEntry
、ProjectSection
和GlobalSection
嵌套类来模拟这些信息。解决方案解析所有这些信息,并可用于处理(如果需要),但它是自动管理的,在大多数情况下可以忽略。我还不需要处理源代码控制信息,所以为了方便起见,我没有单独建模它,但如果需要,可以通过适当的全局部分进行访问。
以下是加载解决方案的示例(摘自Nova.Examples项目)
// Load a solution, specifying the configuration and platform - these are optional,
// but can affect conditionally compiled code. Also, turn on logging of messages.
Solution solution = Solution.Load("Nova.Examples.sln", "Debug", "x86",
LoadOptions.Complete | LoadOptions.LogMessages);
if (solution != null)
Log.WriteLine("Solution '" + solution.Name + "' successfully loaded, parsed.");
建模项目
目前,我只为所有类型的项目创建了一个Project
类——如果/当支持其他语言时,可以将其拆分为项目子类型的层次结构。它拥有CodeUnit
的ChildList
,派生自CodeObject
,并实现了INamedCodeObject
和IFile
接口。项目有许多全局设置,例如ProductVersion
、ProjectGuid
、AssemblyName
等等。它还有多个*配置*,每个配置都有一个名称(如“Debug”)、平台(如“x86”)以及各种配置特定的设置,例如OutputPath
、DefineConstants
(条件编译符号)、WarningLevel
等。一个嵌套的Configuration
类模拟了这些设置,并且项目拥有这些设置的ChildList
。CurrentConfiguration
属性表示当前选定的配置。项目还有一个Reference
对象的ChildList
,该对象被细分为AssemblyReference
、ProjectReference
和COMReference
——目前,除了跟踪它们之外,我们并没有对它们做太多(我们将在下一篇文章中使用它们来加载元数据)。
实现细节包括处理“.csproj”文件的内容,该文件是XML格式,包含许多专有标签。设置存储在PropertyGroup
标签下,在全局和特定于配置的组中,并直接读入全局字段或Configuration
对象。引用和文件存储在ItemGroup
标签下,其中Reference
标签用于引用,Compile
标签用于源文件,以及各种其他文件标签(Content
、Page
等)。引用被读入适当的Reference
对象子类的ChildList
,而文件则被读入FileItem
实例(嵌套类型)的ChildList
,同时为每个源文件创建一个CodeUnit
。这使得在必要时可以处理项目中的非源文件,而通常只处理CodeUnit
集合。项目文件使用XmlReader
进行解析,但存储在上述字段和对象中。这带来了一个问题:新的XML标签可能会被添加,而这些标签尚未被代码支持。这通过存储任何未识别项目文件数据的嵌套UnhandledData
类型的列表来处理,以便可以检查它们,并在保存文件时将它们写回文件。
还支持解析“网站项目”的“Web.config”文件。此外,每个项目都有一系列项目类型Guid
对象,其中许多是预定义的,例如Project.CSProjectType
是C#项目的GUID。项目很复杂,附件代码中还处理了更多细节,但我们已经涵盖了更重要的事情。
项目通常在加载其父解决方案时自动加载,但您也可以直接加载它们,如下面的示例所示(摘自Nova.Examples项目)
// Load a project, specifying the configuration and platform - these are optional, but
// the configuration can affect conditionally compiled code.
Project project = Project.Load("Nova.Examples.csproj", "Debug", "x86");
if (project != null)
Log.WriteLine("Project '" + project.Name + "' successfully loaded, parsed.");
创建解决方案和项目
也可以创建新解决方案/项目并保存它们,如下所示(来自Nova.Examples)
// Create a new solution and project
const string path = @"Generated\";
Solution solution = new Solution(path + "Solution"); // extension will default to '.sln'
Project project = solution.CreateProject(path + "Project"); // will default to '.csproj'
project.OutputType = Project.OutputTypes.Exe; // Output type will default to Library
// Add a file to the project, and put some code in it
CodeUnit codeUnit = project.CreateCodeUnit(path + "Program"); // will default to '.cs'
codeUnit.Add(
new UsingDirective(project.ParseName("System")),
new UsingDirective(project.ParseName("System.Text"))
);
NamespaceDecl namespaceDecl = new NamespaceDecl(project.ParseName("Generated"));
codeUnit.Add(namespaceDecl);
ClassDecl classDecl = new ClassDecl("Program");
namespaceDecl.Add(classDecl);
MethodDecl methodDecl = new MethodDecl("Main", typeof(void), Modifiers.Static,
new ParameterDecl("args", typeof(string[])));
methodDecl.Add(new Comment("Add code here"));
classDecl.Add(methodDecl);
// Save the entire solution, including all projects and files
solution.SaveAll();
Nova Studio 更改
我的“IDE”现在可以加载“.sln”和“.csproj”文件了。加载过程中生成的任何消息(“冒泡”到Solution
的CodeAnnotation
实例)都会显示在底部的消息窗口中,右侧的窗格现在是一个完整的“解决方案树”,其中包含工具提示(项目显示其配置和目标平台)。活动配置和平台显示在工具栏上,单击它们会显示一个窗口,允许更改它们。更改会保存在“.nuo”用户设置文件中,并且进行更改还会强制重新解析,因为它可能会影响条件编译指令的解析方式。下面是加载了“Nova.sln”解决方案的截图。
加载大型解决方案时,您会注意到解决方案树在加载过程中通过状态回调动态填充。我认为它相当快——加载我用近2000个测试项目从Mono编译器创建的解决方案不到6秒。相比之下,VS每个项目需要大约1秒,所以大约需要30分钟(我说“大约”是因为上次我真的等了那么久,它似乎卡住了,从未完成)。公平地说,我还没有加载引用的程序集——我将在本系列的后面进行更详细的比较。
使用附加源代码
本期新增的代码位于Nova.CodeDOM
项目中的新CodeDOM/Projects
文件夹下。除了新的Solution
和Project
类以及References
文件夹外,现有的CodeUnit
类和Namespaces
文件夹也已移至此文件夹。CodeDOM/Base/Interfaces
中有一个新的IFile
接口,由Solution
、Project
和CodeUnit
实现。Nova.Examples
中有新的示例,用于加载解决方案和项目、手动生成解决方案,并且现在有LINQ查询示例用于处理解决方案和项目。Nova Studio现在可以加载(解析)并保存或比较解决方案和项目文件!尝试加载“Nova.sln
”,或者尝试您自己的解决方案或项目。
和往常一样,提供了一个单独的包含二进制文件的ZIP文件,以便您无需先构建即可运行它们。
总结
我的CodeDOM现在可以模拟解决方案和项目,并可以解析和保存“.sln”和“.csproj”文件。Nova Studio开始看起来更像一个真正的IDE,但仍有大量工作要做。代码中那些红色的未解析符号引用怎么办?我们确实需要实现解析它们的能力。然而,许多引用指向外部程序集中声明的内容,例如.NET BCL。所以……在我的下一篇文章中,我将添加从.NET程序集中加载类型元数据,为在CodeDOM中解析符号引用做准备。