Visual Studio动态代码生成的实现基础
这篇文章讨论以下3个问题:
1.代码生成器应该做什么
2.大多数代码生成器的缺点
3.动态代码生成实现的基础
代码生成器应该做什么?
我认为,目标是加快项目开发,方式是减少重复代码手工操作,实现是用过代码生成技术。反过来说,就是代码生成要尽量让能自动化的代码不手动来操作。当然产生了很多附属的优点,如稳定性、便于测试、可以集中精力在业务逻辑上等,可是不能本末倒置。套用一句话,一切不以自动化为目的代码生成器都是耍流氓。
大多数代码生成器的缺点
现在大多数的(应该不是所有)代码生成器有一个最大的问题,就是多次生成导致的拷贝粘贴(无法动态响应类结构或表结构的变化)。现在的代码生成器大多数是在表结构和类代码之间单向生成,但不代表代码生成不能生成其他代码。就算只从生成持久化代码的角度考虑,这些代码生成器除了能根据某个时间点的状态生成一次性的代码,就没有什么价值了。而这些生成的代码如果用于实际项目(不可能不调整结构),又会进行多次生成,产生多次拷贝粘贴。这样的代码生成器其实就是YY,只是YY。当然YY有理(我就曾是其中一员),YY无罪。我只想问问有几个人在项目中实际使用了?有几个人的项目连表结构和字段都没改过? 我跟大家一样都曾经对代码生成器很感兴趣,都自己去DIY一个。我甚至在实际项目中尝试使用,可惜效果并不好。
理想的代码生成器
理想的代码生成器在我看来应该有以下优点:
1.能够集成到开发环境中
2.能够随时根据模型或数据表的变化重新生成代码
3.通过分部类或继承完全隔离手工代码出现在生成代码的文件中
4.不要实体类和数据表之间直接映射
动态代码生成实现的基础
如果Visual Studio提供了对2种方式(从代码或数据表开始)代码生成的支持(如上所述,我不建议直接映射):
1.CodeModelEvents事件提供了对动态检测模型变化并重新生成数据表的支持。
2.通过AddIn方式、T4方式对数据库表进行动态检测并重新生成模型文件的支持。
CodeModelEvents 的示意代码
- /// <summary>实现 IDTExtensibility2 接口的 OnConnection 方法。接收正在加载外接程序的通知。</summary>
- /// <param term='application'>宿主应用程序的根对象。</param>
- /// <param term='connectMode'>描述外接程序的加载方式。</param>
- /// <param term='addInInst'>表示此外接程序的对象。</param>
- /// <seealso class='IDTExtensibility2' />
- public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, refArray custom)
- {
- _applicationObject = (DTE2)application;
- _addInInstance = (AddIn)addInInst;
- OutputWindow outputWindow = (OutputWindow)_applicationObject.Windows.Item(Constants.vsWindowKindOutput).Object;
- outputWindowPane = outputWindow.OutputWindowPanes.Add("Customer Event Information");
- codeModelEvents = ((Events2)_applicationObject.Events).get_CodeModelEvents(null);
- codeModelEvents.ElementChanged += CodeModelElementChanged;
- codeModelEvents.ElementAdded += CodeModelElementAdded;
- codeModelEvents.ElementDeleted += CodeModelElementDeleted;
- }
- /// <summary>实现 IDTExtensibility2 接口的 OnDisconnection 方法。接收正在卸载外接程序的通知。</summary>
- /// <param term='disconnectMode'>描述外接程序的卸载方式。</param>
- /// <param term='custom'>特定于宿主应用程序的参数数组。</param>
- /// <seealso class='IDTExtensibility2' />
- public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
- {
- if(codeModelEvents!=null)
- {
- codeModelEvents.ElementChanged -= CodeModelElementChanged;
- codeModelEvents.ElementAdded -= CodeModelElementAdded;
- codeModelEvents.ElementDeleted -= CodeModelElementDeleted;
- }
- }
- /// <summary>
- /// 输出即使窗口信息
- /// </summary>
- /// <param name="Element"></param>
- private void OutPutElementMessage(CodeElement Element)
- {
- outputWindowPane.OutputString("文件:" + Element.ProjectItem.Document.Name + "\n");
- outputWindowPane.OutputString("元素:" + Element.Name.ToString() + "\n");
- outputWindowPane.OutputString("类型:" + Element.Kind.ToString() + "\n");
- }
- /// <summary>
- /// 代码模型元素更改
- /// </summary>
- /// <param name="Element"></param>
- /// <param name="Change"></param>
- private void CodeModelElementChanged(CodeElement Element, vsCMChangeKind Change)
- {
- OutPutElementMessage(Element);
- }
- /// <summary>
- /// 代码模型元素更改
- /// </summary>
- /// <param name="Element"></param>
- private void CodeModelElementAdded(CodeElement Element)
- {
- OutPutElementMessage(Element);
- }
- /// <summary>
- /// 代码模型元素删除
- /// </summary>
- /// <param name="Parent"></param>
- /// <param name="Element"></param>
- private void CodeModelElementDeleted(object Parent, CodeElement Element)
- {
- OutPutElementMessage(Element);
- }
运行效果:

可以在AddIn或T4中检测数据库表结构的变化,示意代码如下
- <#@ template debug="false" hostspecific="True" Language="C#" #>
- <#@ output extension=".cs" #>
- <#@ Assembly Name="EnvDTE" #>
- <#@ Import Namespace="EnvDTE" #>
- <#@ Assembly Name="System.Xml" #>
- <#@ Import Namespace="System.Xml" #>
- <#@ Assembly Name="System.Data" #>
- <#@ Import Namespace="System.Data" #>
- <#@ Import Namespace="System.Data.Common" #>
- <#@ Assembly Name="System.Configuration" #>
- <#@ Import Namespace="System.Configuration" #>
- using System;
- namespace <#
- DTE dte = ((DTE)((IServiceProvider)this.Host).GetService(typeof(DTE)));
- Project project = null;
- try
- {
- project = dte.SelectedItems.Item(1).ProjectItem.ContainingProject;
- }
- catch
- {
- project = dte.SelectedItems.Item(1).Project;
- }
- this.Write(project.Name);
- #>.Model
- {
- <#
- ProjectItem configItem = null;
- try
- {
- configItem = project.ProjectItems.Item("web.config");
- }
- catch
- {
- configItem = project.ProjectItems.Item("app.config");
- }
- XmlDocument configDoc = new XmlDocument();
- configDoc.Load(configItem.Document.FullName);
- XmlNode node = configDoc.SelectSingleNode("//configuration//connectionStrings//add[@name='ConnectionString']");
- string providerName = node.Attributes["providerName"].Value;
- string connectionString = node.Attributes["connectionString"].Value;
- DbProviderFactory factory = DbProviderFactories.GetFactory(providerName);
- using (DbConnection conn = factory.CreateConnection())
- {
- conn.ConnectionString = connectionString;
- conn.Open();
- DataTable schema = conn.GetSchema("TABLES");
- for (int i = 0; i < schema.Rows.Count; i++)
- {
- //
- }
- conn.Close();
- }
- #>
- }
在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"之类的眼球文。即使能获得很多不明真相的小白的推崇,误导了众多停留在索要源代码为目标的初学者,也不能对自己起到任何提升的作用。
Visual Studio动态代码生成的实现基础的更多相关文章
- Visual Studio动态生成版权信息
Visual Studio动态生成版权信息 VS2008 1.1,类文件模板:在安装目录打开CS模板文件夹(D:\Program Files (x86)\Microsoft Visual Studio ...
- Visual Studio 2010 C++ 属性设置基础
在 <Visual Studio 2010 C++ 工程文件解读>中提到了C++工程中可以进行用户自定义的属性设置,如何进行属性设置呢? 下面我们来了解一下 props 文件的基本规则: ...
- Visual Studio Code常用设置及快捷键
1. Visual Studio Code常用设置 { // 控制是否显示 minimap(缩略图) "editor.minimap.enabled": false, // 控制折 ...
- 自定义Visual Studio.net Extensions 开发符合ABP vnext框架代码生成插件[附源码]
介绍 我很早之前一直在做mvc5 scaffolder的开发功能做的已经非常完善,使用代码对mvc5的项目开发效率确实能成倍的提高,就算是刚进团队的新成员也能很快上手,如果你感兴趣 可以参考 http ...
- Visual Studio中UnitTesting单元测试模板代码生成
在软件研发过程中,单元测试的重要性直接影响软件质量.经验表明一个尽责的单元测试方法将会在软件开发的某个阶段发现很多的Bug,并且修改它们的成本也很低.在软件开发的后期阶段,Bug的发 ...
- Visual Studio调试之断点基础篇
Visual Studio调试之断点基础篇 我曾经问过很多人,你一般是怎么调试你的程序的? F9, F5, F11, F…… 有很多书和文章都是介绍怎么使用Visual Studio编写WinForm ...
- 使用Visual Studio Team Services进行压力和性能测试(一)——创建基础的URL压力测试
使用Visual Studio Team Services进行压力和性能测试(一)--创建基础的URL压力测试 概述 压力测试使应用程序更加健壮,并审核在用户负载下的行为,这样我们可以在当前的基础设施 ...
- 跟我从零基础学习Unity3D开发--Unity3D开发必备神器(Visual Studio Tools for Unity)
开发Unity3D程序你用的什么IDE呢? 1.MonoDevelop 2.VS 可能你的回答是这样的,我用的vs写代码MonoDevelop来做调试.这时有同学就会反驳了傻X不知道用unityVS吗 ...
- Visual Studio插件开发基础
Visual Studio插件主要有两种:Add-in 和 VSX(Visual Studio eXtensibility) 两者区别可参考这篇文章:Visual Studio Extensions ...
随机推荐
- 对于eclipse新建maven工程需要注意的地方。
新建项目的时候,如果想配置默认的maven的jre为1.6或者别的. http://hi.baidu.com/hi_hi/item/765ec8bbc49880d384dd79d1 1.cmd命令建立 ...
- Git教程(5)常用技巧之本地分支
http://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%AE%80%E4%BB%8B 基础 Git 研发组 ...
- Windows 8 自带定时关机的4种实现方法
问题描述:前几天发布了一篇文章[ Windows 7/8 自带定时关机命令 ],文章中的用到的命令我在Windows 7都运行成功,但没有在Windows 8 上进行测试,因为我认为Windows 8 ...
- POJ 2586 Y2K Accounting Bug(贪心)
题目连接:http://poj.org/problem?id=2586 题意:次(1-5.2-6.3-7.4-8.5-9.6-10.7-11.8-12),次统计的结果全部是亏空(盈利-亏空<0) ...
- POJ 3096 Surprising Strings(STL map string set vector)
题目:http://poj.org/problem?id=3096 题意:给定一个字符串S,从中找出所有有两个字符组成的子串,每当组成子串的字符之间隔着n字符时,如果没有相同的子串出现,则输出 &qu ...
- KindEditor设置为过滤模式,但在代码模式下提交表单时不过虑HTML标签的解决方法
KindEditor设置filterMode为true,但在代码模式下提交表单的话,发现并没有过虑掉自己不想保留的HTML标签. 这时只需同步内容前加上红色部分内容即可: onClick=" ...
- 在PowerDesigner中设计物理模型2——约束
唯一约束 唯一约束与创建唯一索引基本上是一回事,因为在创建唯一约束的时候,系统会创建对应的一个唯一索引,通过唯一索引来实现约束.不过唯一约束更直观的表达了对应列的唯一性,使得对应索引的目的更加清晰,所 ...
- dll打包进需要发布的exe z
http://www.cnblogs.com/Jarvin/p/3721195.html 我们需要发布的exe文件很多时候都可能会依赖于一堆松散的dll,如果想在发布 的时候只提供exe文件,而不想把 ...
- 4种activity的启动模式
在android里,有4种activity的启动模式,分别为: “standard” (默认) “singleTop” “singleTask” “singleInstance” 它们主要有如下不同: ...
- 设计模式_Facade_门面模式
形象例子: 我有一个专业的Nikon相机,我就喜欢自己手动调光圈.快门,这样照出来的照片才专业,但MM可不懂这些,教了半天也不会.幸好相机有Facade设计模式,把相机调整到自动档,只要对准目标按快门 ...