谈谈.NET中如何根据代码自动生成代码对象模型的解决思路
2010-09-28 21:10
639 查看
一、写作背景
最近笔者一直在从事如何利用Visual Studio Add-in和.NET 的CodeDOM来提升GIS领域程序员开发效率的研究。目前总算是得到了一些成果,当然,这些成果笔者准备开源,并陆续地写成博客和大家分享。其中这些成果当中,对.NET 的CodeDOM的研究是相对独立的一块,所以想先单独拿出来写点东西。但同时,笔者也观察到近来在园子里这方面的文章也非常多,如Artech的《从数据到代码——通过代码生成机制实现强类型编程[上篇]》,以及破狼的 CodeDom系列,都是非常不错的好文章。所以他们写过的笔者肯定是不能写的,主要是怕被拍砖。因此笔者准备写点他们都没涉及到的内容,比如题目中提到的CodeDomProvider.Parse的解决思路。CodeDomProvider.Parse主要是为了将代码文件中的代码文本动态地解析成CodeCompileUnit,即CodeDOM的内存对象模型。其中CodeCompileUnit的使用在本文不会涉及,感兴趣的朋友可以参考前面提到的相关文章。
二、问题描述
使用过CodeDomProvider.Parse的朋友也许知道,.NET4.0中并没有具体实现该方法,如果强行调用则会抛出异常,甚至微软官方解释目前只能要求程序员自己实现该方法,因此纯粹依赖.NET是无法实现将代码文件中的文本动态地解析成CodeCompileUnit的。但是,在代码自动生成的程序当中却会很自然地涉及到这方面的需求,所以笔者决定重写一个类CodeDOMSerializer来代替原来CodeDomProvider。
三、设计思路
该类CodeDOMSerializer继承了抽象类CodeDomProvider,并在其中定义了CodeDomProvider类型的成员变量来提供基本的根据CodeCompileUnit动态生成代码的功能。而为了解决将代码文件中的文本动态地解析成CodeCompileUnit的问题,则是先在实例动态生成代码的过程中自动维护一个与生成的代码配套的CodeCompileUnit二进制序列化文件(本文定义该文件后缀名为.dom),而当需要将代码文件中的文本动态地解析成CodeCompileUnit的时候则将该代码文件对应的.dom文件中反序列化成CodeCompileUnit,从而有效地回避了必须从代码文件生成CodeCompileUnit实例的问题。流程图如下所示:
CodeDOMSerializer
AddinHelper
五、我们在哪
本文中在类CodeDOMSerializer中设计的一个.dom文件来存储CodeCompileUnit实例的二进制序列化结果,从而解决了CodeDomProvider.Parse未实现的将代码文件中的文本动态地解析成CodeCompileUnit的问题。并且接口的设计将对.dom文件的管理完全封装到了该类的内部,对调用方完全不可见,从而避免了调用程序需要做出大量修改才能使用该类功能的情况。
六、后言
由于本文中的例程主要是针对在VS2010 Add-in中实现代码自动生成功能,并且为了确保该类在VS2010种使用更方便,不可避免地在设计时就将该类与VS2010的对象模型绑定。若您需要在自定义环境中使用该类,则根据本文中介绍的原理对该类的实现做些简单调整即可。
最近笔者一直在从事如何利用Visual Studio Add-in和.NET 的CodeDOM来提升GIS领域程序员开发效率的研究。目前总算是得到了一些成果,当然,这些成果笔者准备开源,并陆续地写成博客和大家分享。其中这些成果当中,对.NET 的CodeDOM的研究是相对独立的一块,所以想先单独拿出来写点东西。但同时,笔者也观察到近来在园子里这方面的文章也非常多,如Artech的《从数据到代码——通过代码生成机制实现强类型编程[上篇]》,以及破狼的 CodeDom系列,都是非常不错的好文章。所以他们写过的笔者肯定是不能写的,主要是怕被拍砖。因此笔者准备写点他们都没涉及到的内容,比如题目中提到的CodeDomProvider.Parse的解决思路。CodeDomProvider.Parse主要是为了将代码文件中的代码文本动态地解析成CodeCompileUnit,即CodeDOM的内存对象模型。其中CodeCompileUnit的使用在本文不会涉及,感兴趣的朋友可以参考前面提到的相关文章。
二、问题描述
使用过CodeDomProvider.Parse的朋友也许知道,.NET4.0中并没有具体实现该方法,如果强行调用则会抛出异常,甚至微软官方解释目前只能要求程序员自己实现该方法,因此纯粹依赖.NET是无法实现将代码文件中的文本动态地解析成CodeCompileUnit的。但是,在代码自动生成的程序当中却会很自然地涉及到这方面的需求,所以笔者决定重写一个类CodeDOMSerializer来代替原来CodeDomProvider。
三、设计思路
该类CodeDOMSerializer继承了抽象类CodeDomProvider,并在其中定义了CodeDomProvider类型的成员变量来提供基本的根据CodeCompileUnit动态生成代码的功能。而为了解决将代码文件中的文本动态地解析成CodeCompileUnit的问题,则是先在实例动态生成代码的过程中自动维护一个与生成的代码配套的CodeCompileUnit二进制序列化文件(本文定义该文件后缀名为.dom),而当需要将代码文件中的文本动态地解析成CodeCompileUnit的时候则将该代码文件对应的.dom文件中反序列化成CodeCompileUnit,从而有效地回避了必须从代码文件生成CodeCompileUnit实例的问题。流程图如下所示:
CodeDOMSerializer
class CodeDOMSerializer : CodeDomProvider, SymbolEditor.Addin.CodeDom.ICodeDOMSerializer { #region fields private CodeDomProvider provider = null; private string objDir = "obj"; #endregion #region constuctor public CodeDOMSerializer(string language) : base() { provider = CodeDomProvider.CreateProvider(language); } #endregion #region IDisposable 成员 public void Dispose() { base.Dispose(); provider.Dispose(); provider = null; } #endregion #region public methods [Obsolete()] public override ICodeCompiler CreateCompiler() { return provider.CreateCompiler(); } [Obsolete()] public override ICodeGenerator CreateGenerator() { return provider.CreateGenerator(); } public override void GenerateCodeFromCompileUnit(System.CodeDom.CodeCompileUnit compileUnit, System.IO.TextWriter writer, CodeGeneratorOptions options) { if (writer is StreamWriter && ((writer as StreamWriter).BaseStream is FileStream)) { string filename = ((writer as StreamWriter).BaseStream as FileStream).Name; string parentDir = (new FileInfo(filename)).Directory.FullName; string objDirectory = parentDir + "\\" + objDir; if (!Directory.Exists(objDirectory)) { Directory.CreateDirectory(objDirectory); } string domfilename = System.IO.Path.GetFileNameWithoutExtension(filename) + ".DOM"; string domfullname = objDirectory + "\\" + domfilename; serializeCodeDOMUnit(compileUnit, domfullname); provider.GenerateCodeFromCompileUnit(compileUnit, writer, options); } else { throw new NotSupportedException("non file streamwriter is not supported!"); } } public void GenerateCodeFromCompileUnit(System.CodeDom.CodeCompileUnit compileUnit, ProjectItem codeItem, CodeGeneratorOptions options) { if (codeItem.Kind == Constants.vsProjectItemKindPhysicalFile) { using (AddinHelper.SafeAccessDocument safeADoc = new AddinHelper.SafeAccessDocument(codeItem)) { //AddinHelper.SafeAccessDocument(codeItem); //check whether the file being in read only status if (codeItem.Document.ReadOnly) { throw new FileLoadException("The document '" + codeItem.Document.FullName + "' is read only!"); } /* * get the dom file coresponding to the code document, *if the file is not exsit ,auto create it . */ ProjectItem domDoc = getSameNameSubItem(codeItem); using (AddinHelper.SafeAccessDocument safeADom = new AddinHelper.SafeAccessDocument(domDoc)) { //AddinHelper.SafeAccessDocument(domDoc); //get textwriter to the document file string dompath = domDoc.Document.FullName; using (StreamWriter writer = new StreamWriter(codeItem.Document.FullName)) { serializeCodeDOMUnit(compileUnit, dompath); provider.GenerateCodeFromCompileUnit(compileUnit, writer, options); } } } } else { throw new InvalidDataException("Invalid project item"); } } public override System.CodeDom.CodeCompileUnit Parse(TextReader codeStream) { if (codeStream is StreamReader && ((codeStream as StreamReader).BaseStream is FileStream)) { string filename = ((codeStream as StreamReader).BaseStream as FileStream).Name; string parentDir = (new FileInfo(filename)).Directory.FullName; string objDirectory = parentDir + "\\" + objDir; string domfilename = System.IO.Path.GetFileNameWithoutExtension(filename) + ".DOM"; string domfullname = objDirectory + "\\" + domfilename; return DeserializeCodeDOMUnit(filename); } else { throw new NotSupportedException("non file streamReader is not supported!"); } } public System.CodeDom.CodeCompileUnit Parse(ProjectItem codeItem) { CodeCompileUnit ret = null; if (codeItem.Kind == Constants.vsProjectItemKindPhysicalFile) { ProjectItem domDoc = getSameNameSubItem(codeItem); using (AddinHelper.SafeAccessDocument safeADoc = new AddinHelper.SafeAccessDocument(domDoc)) { ret = DeserializeCodeDOMUnit(domDoc.Document.FullName); } } return ret; } public override void GenerateCodeFromStatement(System.CodeDom.CodeStatement statement, System.IO.TextWriter writer, CodeGeneratorOptions options) { provider.GenerateCodeFromStatement(statement, writer, options); } #endregion #region private methods private ProjectItem getSameNameSubItem(ProjectItem parentItem) { string filename = parentItem.Name + ".DOM"; string filepath = parentItem.Document.Path + "\\" + filename; ProjectItem targetItem = null; IEnumerable<ProjectItem> targets = parentItem.ProjectItems.Cast<ProjectItem>(). Where(element => element.Name.ToUpper().Equals(filename.ToUpper())); if (targets.Count() > 0) { targetItem = targets.First(); } else { if (File.Exists(filepath)) File.Delete(filepath); //if not exsit ,create it targetItem = AddinHelper.AddEmptyDoc2Project(parentItem.ProjectItems, filename); } return targetItem; } private void serializeCodeDOMUnit(System.CodeDom.CodeCompileUnit compileUnit, string domfullname) { IFormatter formatter = new BinaryFormatter(); Stream stream = new FileStream(domfullname, FileMode.Create, FileAccess.Write, FileShare.None); try { formatter.Serialize(stream, compileUnit); } finally { stream.Close(); } } private System.CodeDom.CodeCompileUnit DeserializeCodeDOMUnit(string domfullname) { if (File.Exists(domfullname)) { CodeCompileUnit ret = null; IFormatter formatter = new BinaryFormatter(); Stream stream = new FileStream(domfullname, FileMode.Open, FileAccess.Read, FileShare.None); try { ret = formatter.Deserialize(stream) as CodeCompileUnit; } finally { stream.Dispose(); } return ret; } else { throw new FileNotFoundException(domfullname); } } #endregion }
AddinHelper
class AddinHelper { public static string AddReference(EnvDTE.Project proj,string refPath) { string ret = string.Empty; VSLangProj.VSProject vsProject = (VSLangProj.VSProject)proj.Object; try { vsProject.References.Add(refPath); } catch (Exception ex) { ret = ex.ToString(); } return ret; } public static ProjectItem checkDocumentExsist(EnvDTE.ProjectItems projItems, string filename) { ProjectItem ret = null; ProjectItem item = null; int foldercnt = 0; for (int i = 1; i <= projItems.Count; i++) { item = projItems.Item(i); if (item.Kind == Constants.vsProjectItemKindPhysicalFolder) { if (item.ProjectItems.Count > 0) foldercnt++; } else { if (item.Kind == Constants.vsProjectItemKindPhysicalFile && item.Name.ToUpper().Equals(filename.ToUpper())) { ret = item; break; } } } if (ret == null) { if (foldercnt > 0) { ProjectItem subItem = null; for (int i = 1; i <= projItems.Count; i++) { item = projItems.Item(i); if (item.Kind == Constants.vsProjectItemKindPhysicalFolder && item.ProjectItems.Count > 0) { subItem = checkDocumentExsist(item.ProjectItems, filename); if (subItem != null) { ret = subItem; break; } } } } } // = findDocument(proj, filename); return ret; } public static ProjectItem AddEmptyDoc2Project(ProjectItems parent, string docname) { if ((new FileInfo(parent.ContainingProject.FullName)).IsReadOnly) { throw new FileLoadException("The project file '" + parent.ContainingProject.FullName + "' is read only!"); } ProjectItem ret=null; string tmpDir = System.IO.Path.GetTempPath(); string tmpCodefile = tmpDir + docname; if (File.Exists(tmpCodefile)) { File.Delete(tmpCodefile); } FileInfo file = new FileInfo(tmpCodefile); FileStream stream = file.Create(); stream.Dispose(); ret = parent.AddFromFileCopy(tmpCodefile); File.Delete(tmpCodefile); return ret; } public delegate object safeDelegate(ProjectItem doc); public class SafeAccessDocument:IDisposable { ProjectItem doc = null; Window wnd = null; public SafeAccessDocument(ProjectItem doc) { this.doc = doc; if (doc.Kind == Constants.vsProjectItemKindPhysicalFile) { if (!doc.IsOpen) { wnd = doc.Open(Constants.vsViewKindPrimary); } } } #region IDisposable 成员 public void Dispose() { if (wnd != null) wnd.Close(vsSaveChanges.vsSaveChangesNo); } #endregion } //public static object SafeAccessDocument(ProjectItem doc, safeDelegate func = null) //{ // object ret = null; // if (doc.Kind == Constants.vsProjectItemKindPhysicalFile) // { // if (!doc.IsOpen) // { // /* // * the function 'libclsItem.Open' must be invoked // * because visual studio initializes the object 'ProjectItem.Document' // * asynchronously.So if call the function could force vs initializes // * it right away. // */ // Window wnd = doc.Open(Constants.vsViewKindPrimary); // if (func != null) // { // ret = func(doc); // } // /* // * if you open the document in the vs window, // * of cource you should close it ,because of avoiding // * the problem of opening the document later. // */ // wnd.Close(vsSaveChanges.vsSaveChangesNo); // } // else // { // if (func != null) // { // ret = func(doc); // } // } // } // return ret; //} }
五、我们在哪
本文中在类CodeDOMSerializer中设计的一个.dom文件来存储CodeCompileUnit实例的二进制序列化结果,从而解决了CodeDomProvider.Parse未实现的将代码文件中的文本动态地解析成CodeCompileUnit的问题。并且接口的设计将对.dom文件的管理完全封装到了该类的内部,对调用方完全不可见,从而避免了调用程序需要做出大量修改才能使用该类功能的情况。
六、后言
由于本文中的例程主要是针对在VS2010 Add-in中实现代码自动生成功能,并且为了确保该类在VS2010种使用更方便,不可避免地在设计时就将该类与VS2010的对象模型绑定。若您需要在自定义环境中使用该类,则根据本文中介绍的原理对该类的实现做些简单调整即可。
相关文章推荐
- [置顶] 如何根据动态SQL代码自动生成DTO
- 如何解决Eclipse中Android 代码自动补全卡死的问题
- Android Studio插件-自动根据布局生成Activity等代码(插件代码开源)
- js自动生成对象的属性示例代码
- Spring Boot如何让Web API自动生成文档,并解决swagger-annotations的API注解description属性废弃的问题
- 一个自动根据xcode中的objective-c代码生成类关系图的神器
- 【原创】有关Silverlight中自动生成的类中 没有WCF层edmx模型新加入的对象 原因分析。
- 用Pytohn写了一个根据表结构自动生成C#对像代码的小工具
- 已解决。。。mybatis 代码自动生成报错 Result Maps collection already contains value for BaseResultMap
- RemObject解决自动生成代码的想法.
- 在新复制的MFC Visual C++项目中不能自动生成代码问题的解决
- 根据Word表格自动生成SQL数据库脚本的VBScript代码
- 根据mysql数据库自动生成mvc三层代码及jsp页,极速开发srpingmvc+mybatis+bootstrap项目。
- 使用xorm工具,根据数据库自动生成 go 代码
- 自动代码工具-json自动生成模型文件
- eclipse代码自动提示设置、如何配置eclipse的代码自动提示功能(同时解决自动补全变量名的问题)?
- Java进阶之 如何自动生成代码
- visio 如何反向连接数据库生成er图( 即根据数据库自动生成ER图 )
- PHP FOR MYSQL 代码生成助手(根据Mysql里的字段自动生成类文件的)
- 关于SubSonic3.0生成的表名自动加复数(s)的“用户代码未处理SqlException,对象名'xxxs'无效”异常处理