Visual Studio动态代码生成的实现基础

开发 后端
本文作者之前想谈一谈如何通过引入框架或代码生成来解决(在某种程度上)项目开发实际会遇到的编码的问题(重复相似代码和多种实现问题),顺便提醒下初学者把精力放在静态代码生成器上,还不如放在如何在编辑代码时动态生成基础设施或持久化的代码。

 这篇文章讨论以下3个问题:

1.代码生成器应该做什么

2.大多数代码生成器的缺点

3.动态代码生成实现的基础

代码生成器应该做什么?

我认为,目标是加快项目开发,方式是减少重复代码手工操作,实现是用过代码生成技术。反过来说,就是代码生成要尽量让能自动化的代码不手动来操作。当然产生了很多附属的优点,如稳定性、便于测试、可以集中精力在业务逻辑上等,可是不能本末倒置。套用一句话,一切不以自动化为目的代码生成器都是耍流氓。

大多数代码生成器的缺点

现在大多数的(应该不是所有)代码生成器有一个***的问题,就是多次生成导致的拷贝粘贴(无法动态响应类结构或表结构的变化)。现在的代码生成器大多数是在表结构和类代码之间单向生成,但不代表代码生成不能生成其他代码。就算只从生成持久化代码的角度考虑,这些代码生成器除了能根据某个时间点的状态生成一次性的代码,就没有什么价值了。而这些生成的代码如果用于实际项目(不可能不调整结构),又会进行多次生成,产生多次拷贝粘贴。这样的代码生成器其实就是YY,只是YY。当然YY有理(我就曾是其中一员),YY无罪。我只想问问有几个人在项目中实际使用了?有几个人的项目连表结构和字段都没改过? 我跟大家一样都曾经对代码生成器很感兴趣,都自己去DIY一个。我甚至在实际项目中尝试使用,可惜效果并不好。

理想的代码生成器

理想的代码生成器在我看来应该有以下优点:

1.能够集成到开发环境中

2.能够随时根据模型或数据表的变化重新生成代码

3.通过分部类或继承完全隔离手工代码出现在生成代码的文件中

4.不要实体类和数据表之间直接映射

动态代码生成实现的基础

如果Visual Studio提供了对2种方式(从代码或数据表开始)代码生成的支持(如上所述,我不建议直接映射):

1.CodeModelEvents事件提供了对动态检测模型变化并重新生成数据表的支持。

2.通过AddIn方式、T4方式对数据库表进行动态检测并重新生成模型文件的支持。

CodeModelEvents 的示意代码

  1. /// <summary>实现 IDTExtensibility2 接口的 OnConnection 方法。接收正在加载外接程序的通知。</summary> 
  2. /// <param term='application'>宿主应用程序的根对象。</param> 
  3. /// <param term='connectMode'>描述外接程序的加载方式。</param> 
  4. /// <param term='addInInst'>表示此外接程序的对象。</param> 
  5. /// <seealso class='IDTExtensibility2' /> 
  6. public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, refArray custom) 
  7. _applicationObject = (DTE2)application; 
  8. _addInInstance = (AddIn)addInInst; 
  9.   
  10. OutputWindow outputWindow = (OutputWindow)_applicationObject.Windows.Item(Constants.vsWindowKindOutput).Object; 
  11. outputWindowPane = outputWindow.OutputWindowPanes.Add("Customer Event Information"); 
  12.   
  13. codeModelEvents = ((Events2)_applicationObject.Events).get_CodeModelEvents(null); 
  14. codeModelEvents.ElementChanged += CodeModelElementChanged; 
  15. codeModelEvents.ElementAdded += CodeModelElementAdded; 
  16. codeModelEvents.ElementDeleted += CodeModelElementDeleted; 
  17.   
  18. /// <summary>实现 IDTExtensibility2 接口的 OnDisconnection 方法。接收正在卸载外接程序的通知。</summary> 
  19. /// <param term='disconnectMode'>描述外接程序的卸载方式。</param> 
  20. /// <param term='custom'>特定于宿主应用程序的参数数组。</param> 
  21. /// <seealso class='IDTExtensibility2' /> 
  22. public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom) 
  23. if(codeModelEvents!=null
  24. codeModelEvents.ElementChanged -= CodeModelElementChanged; 
  25. codeModelEvents.ElementAdded -= CodeModelElementAdded; 
  26. codeModelEvents.ElementDeleted -= CodeModelElementDeleted; 
  27. /// <summary> 
  28. /// 输出即使窗口信息 
  29. /// </summary> 
  30. /// <param name="Element"></param> 
  31. private void OutPutElementMessage(CodeElement Element) 
  32. outputWindowPane.OutputString("文件:" + Element.ProjectItem.Document.Name + "\n"); 
  33. outputWindowPane.OutputString("元素:" + Element.Name.ToString() + "\n"); 
  34. outputWindowPane.OutputString("类型:" + Element.Kind.ToString() + "\n"); 
  35. /// <summary> 
  36. /// 代码模型元素更改 
  37. /// </summary> 
  38. /// <param name="Element"></param> 
  39. /// <param name="Change"></param> 
  40. private void CodeModelElementChanged(CodeElement Element, vsCMChangeKind Change) 
  41. OutPutElementMessage(Element); 
  42. /// <summary> 
  43. /// 代码模型元素更改 
  44. /// </summary> 
  45. /// <param name="Element"></param> 
  46. private void CodeModelElementAdded(CodeElement Element) 
  47. OutPutElementMessage(Element); 
  48. /// <summary> 
  49. /// 代码模型元素删除 
  50. /// </summary> 
  51. /// <param name="Parent"></param> 
  52. /// <param name="Element"></param> 
  53. private void CodeModelElementDeleted(object Parent, CodeElement Element) 
  54. OutPutElementMessage(Element); 

运行效果:

可以在AddIn或T4中检测数据库表结构的变化,示意代码如下

  1. <#@ template debug="false" hostspecific="True" Language="C#" #> 
  2. <#@ output extension=".cs" #> 
  3. <#@ Assembly Name="EnvDTE" #> 
  4. <#@ Import Namespace="EnvDTE" #> 
  5. <#@ Assembly Name="System.Xml" #> 
  6. <#@ Import Namespace="System.Xml" #> 
  7. <#@ Assembly Name="System.Data" #> 
  8. <#@ Import Namespace="System.Data" #> 
  9. <#@ Import Namespace="System.Data.Common" #> 
  10. <#@ Assembly Name="System.Configuration" #> 
  11. <#@ Import Namespace="System.Configuration" #> 
  12. using System; 
  13. namespace <
  14. DTE dte = ((DTE)((IServiceProvider)this.Host).GetService(typeof(DTE))); 
  15. Project project = null
  16. try 
  17. project = dte.SelectedItems.Item(1).ProjectItem.ContainingProject; 
  18. catch 
  19. project = dte.SelectedItems.Item(1).Project; 
  20. this.Write(project.Name); 
  21. #>.Model 
  22. <
  23. ProjectItem configItem = null
  24. try 
  25. configItem = project.ProjectItems.Item("web.config"); 
  26. catch 
  27. configItem = project.ProjectItems.Item("app.config"); 
  28. XmlDocument configDoc = new XmlDocument(); 
  29. configDoc.Load(configItem.Document.FullName); 
  30. XmlNode node = configDoc.SelectSingleNode("//configuration//connectionStrings//add[@name='ConnectionString']"); 
  31. string providerName = node.Attributes["providerName"].Value; 
  32. string connectionString = node.Attributes["connectionString"].Value; 
  33. DbProviderFactory factory = DbProviderFactories.GetFactory(providerName); 
  34. using (DbConnection conn = factory.CreateConnection()) 
  35. conn.ConnectionString = connectionString
  36. conn.Open(); 
  37. DataTable schema = conn.GetSchema("TABLES"); 
  38. for (int i = 0; i < schema.Rows.Count; i++) 
  39. // 
  40. conn.Close(); 
  41. #> 

 

在AddIn中也可以直接使用CodeModel或FileCodleModel直接进行代码生成,如果通过T4,可以通过调用Solution.FindProjectItem找到T4文件,通过Open和Save方法让T4模板自动运行更新代码来达到动态更新的目的。

后记

T4的在我的电脑上实在是慢,每次总要弄的VS卡住一小会,如果在AddIn中直接生成代码又失去了模板的灵活性,考虑在AddIn中先生成中间映射文件,再通知代码生成程序来调用T4的模板方式或其他方式来生成代码,也许效果会更好些。对于代码生成,我现在的理解就是应该集成到IDE中并可以动态调用,做到生成代码不能改动,重新生成十分方便,将由于结构变化导致需要调整的代码部分的工作量压缩到最小。

每隔一段时间,园子里总会热一段代码生成相关的话题。我希望尽量不误导初学者,尤其是那些说自己的代码生成器生成大部分代码的,提高了多少倍工作效率的,要么是基本不提需求变化,要么是变化了很少改动结构,或者根本是自己写的稳定需求。从我个人实践的角度,用这些所谓的外置的代码生成器,一旦数据库表结构生成变化,就需要重新生成代码并拷贝进项目。本来代码生成就不是只针对对象持久化方面,也不是只有外置的方式,甚至在我看来外置的方式就是YY。

代码生成这个东西,生成的应该是经过实践的、稳定的、可以自动生成的代码。如果对生成的代码理解都很困难还在尝试写自己的代码生成器,我觉得不好。写代码生成器,如果抛开代码生成产生的根源和要解决的问题,一再的强调一些不相关的东西,却一直回避到底如何在应用中加快项目进度、提高效率等这些内容,对初学者真的是很不好的影响。一个编码能力很差(这么说不表示我编码能力强,我就是很弱的那种),阅读代码能力都不行的,公布一个代码生成器,你这是闹哪出呢。

也许有人说,初学者可以在自己编写代码生成的过程中学到很多知识,比如界面和控件、模板等,这是另一种方式的自欺欺人。大多外置代码生成器都不是web方式的,如果工作中一直从事asp.net开发,那所谓的界面和控件学到的知识还不如去学学自定义web控件开发了,除了代码生成,学到的模板知识在其他哪些方面基本没啥作用。毕竟所有初学者都遇到这种必须用模板的需求只能是YY。

通过查找发现相似代码,进行重构,引入框架或代码生成,本身就对代码能力有一定的要求,一些基础的代码实现和代码结构都搞不懂,就想做个会有很多人使用的某某代码生成器,既浪费了自己的时间又误导了初学者,这样不好。我就有过这样的经历,希望能对别人也起到一些警醒的作用,在本身阅读代码和写一些基础代码的能力都没有的时候,别跟风,别浮躁,努力提高自己的基础,即使研究代码生成,也从加快项目进度的角度出发,把精力用在集成在IDE中的动态代码生成上,要有一种追根溯源,立足实用的态度,不要看人家把msdn的示例代码改一改发个随笔说成自己原创,你就模仿,也不要看别人发了很多新技术的扫盲或入门系列很火,你就模仿。更不要看一些人用标题忽悠了很多人评论,你也照着做。搞不清楚来龙去脉,不要去学人家抛什么“XX技术无用论”,用"最XX的XX"之类的眼球文。即使能获得很多不明真相的小白的推崇,误导了众多停留在索要源代码为目标的初学者,也不能对自己起到任何提升的作用。

原文链接:http://www.cnblogs.com/easygame/archive/2012/09/18/2690819.html

【编辑推荐】

  1. Visual Studio 2012的C++原生单元测试
  2. Visual Studio 2012 Ultimate RC安装手记
  3. VS 2012单元测试和测试资源管理器
  4. 微软正式发布 Visual Studio 2012
  5. Visual Studio 11下的C++异步编程1

 

责任编辑:彭凡 来源: 博客园
相关推荐

2009-06-16 10:44:50

JS代码折叠Visual Stud

2010-03-11 16:09:38

Visual StudSilverligh

2011-01-10 08:48:53

2009-11-05 09:42:42

Visual Stud

2022-04-01 15:16:40

GitHub程序员人工智能

2009-09-07 09:22:17

Visual Stud代码片段

2009-08-21 13:29:20

Visual Stud

2010-09-25 08:50:00

Visual Stud

2009-12-04 11:17:00

Visual Stud

2009-12-10 16:50:58

Visual Stud

2009-12-02 09:43:38

Visual Stud

2009-12-04 14:29:05

Visual Stud

2011-02-18 10:46:57

Visual Stud

2009-07-01 17:30:14

样式生成器Visual Stud

2009-12-10 17:05:10

Visual Stud

2009-12-04 17:21:35

Visual Stud

2012-04-25 11:04:13

Visual Stud

2010-03-11 16:29:28

Visual Stud

2013-11-14 01:09:35

微软Visual StudVisual Stud

2023-09-05 07:32:22

vscode开源故障
点赞
收藏

51CTO技术栈公众号