Visual Studio動態(tài)代碼生成的實現(xiàn)基礎(chǔ)
這篇文章討論以下3個問題:
1.代碼生成器應(yīng)該做什么
2.大多數(shù)代碼生成器的缺點
3.動態(tài)代碼生成實現(xiàn)的基礎(chǔ)
代碼生成器應(yīng)該做什么?
我認為,目標(biāo)是加快項目開發(fā),方式是減少重復(fù)代碼手工操作,實現(xiàn)是用過代碼生成技術(shù)。反過來說,就是代碼生成要盡量讓能自動化的代碼不手動來操作。當(dāng)然產(chǎn)生了很多附屬的優(yōu)點,如穩(wěn)定性、便于測試、可以集中精力在業(yè)務(wù)邏輯上等,可是不能本末倒置。套用一句話,一切不以自動化為目的代碼生成器都是耍流氓。
大多數(shù)代碼生成器的缺點
現(xiàn)在大多數(shù)的(應(yīng)該不是所有)代碼生成器有一個***的問題,就是多次生成導(dǎo)致的拷貝粘貼(無法動態(tài)響應(yīng)類結(jié)構(gòu)或表結(jié)構(gòu)的變化)。現(xiàn)在的代碼生成器大多數(shù)是在表結(jié)構(gòu)和類代碼之間單向生成,但不代表代碼生成不能生成其他代碼。就算只從生成持久化代碼的角度考慮,這些代碼生成器除了能根據(jù)某個時間點的狀態(tài)生成一次性的代碼,就沒有什么價值了。而這些生成的代碼如果用于實際項目(不可能不調(diào)整結(jié)構(gòu)),又會進行多次生成,產(chǎn)生多次拷貝粘貼。這樣的代碼生成器其實就是YY,只是YY。當(dāng)然YY有理(我就曾是其中一員),YY無罪。我只想問問有幾個人在項目中實際使用了?有幾個人的項目連表結(jié)構(gòu)和字段都沒改過? 我跟大家一樣都曾經(jīng)對代碼生成器很感興趣,都自己去DIY一個。我甚至在實際項目中嘗試使用,可惜效果并不好。
理想的代碼生成器
理想的代碼生成器在我看來應(yīng)該有以下優(yōu)點:
1.能夠集成到開發(fā)環(huán)境中
2.能夠隨時根據(jù)模型或數(shù)據(jù)表的變化重新生成代碼
3.通過分部類或繼承完全隔離手工代碼出現(xiàn)在生成代碼的文件中
4.不要實體類和數(shù)據(jù)表之間直接映射
動態(tài)代碼生成實現(xiàn)的基礎(chǔ)
如果Visual Studio提供了對2種方式(從代碼或數(shù)據(jù)表開始)代碼生成的支持(如上所述,我不建議直接映射):
1.CodeModelEvents事件提供了對動態(tài)檢測模型變化并重新生成數(shù)據(jù)表的支持。
2.通過AddIn方式、T4方式對數(shù)據(jù)庫表進行動態(tài)檢測并重新生成模型文件的支持。
CodeModelEvents 的示意代碼
- /// <summary>實現(xiàn) IDTExtensibility2 接口的 OnConnection 方法。接收正在加載外接程序的通知。</summary>
- /// <param term='application'>宿主應(yīng)用程序的根對象。</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>實現(xiàn) IDTExtensibility2 接口的 OnDisconnection 方法。接收正在卸載外接程序的通知。</summary>
- /// <param term='disconnectMode'>描述外接程序的卸載方式。</param>
- /// <param term='custom'>特定于宿主應(yīng)用程序的參數(shù)數(shù)組。</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中檢測數(shù)據(jù)庫表結(jié)構(gòu)的變化,示意代碼如下
- <#@ 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,可以通過調(diào)用Solution.FindProjectItem找到T4文件,通過Open和Save方法讓T4模板自動運行更新代碼來達到動態(tài)更新的目的。
后記
T4的在我的電腦上實在是慢,每次總要弄的VS卡住一小會,如果在AddIn中直接生成代碼又失去了模板的靈活性,考慮在AddIn中先生成中間映射文件,再通知代碼生成程序來調(diào)用T4的模板方式或其他方式來生成代碼,也許效果會更好些。對于代碼生成,我現(xiàn)在的理解就是應(yīng)該集成到IDE中并可以動態(tài)調(diào)用,做到生成代碼不能改動,重新生成十分方便,將由于結(jié)構(gòu)變化導(dǎo)致需要調(diào)整的代碼部分的工作量壓縮到最小。
每隔一段時間,園子里總會熱一段代碼生成相關(guān)的話題。我希望盡量不誤導(dǎo)初學(xué)者,尤其是那些說自己的代碼生成器生成大部分代碼的,提高了多少倍工作效率的,要么是基本不提需求變化,要么是變化了很少改動結(jié)構(gòu),或者根本是自己寫的穩(wěn)定需求。從我個人實踐的角度,用這些所謂的外置的代碼生成器,一旦數(shù)據(jù)庫表結(jié)構(gòu)生成變化,就需要重新生成代碼并拷貝進項目。本來代碼生成就不是只針對對象持久化方面,也不是只有外置的方式,甚至在我看來外置的方式就是YY。
代碼生成這個東西,生成的應(yīng)該是經(jīng)過實踐的、穩(wěn)定的、可以自動生成的代碼。如果對生成的代碼理解都很困難還在嘗試寫自己的代碼生成器,我覺得不好。寫代碼生成器,如果拋開代碼生成產(chǎn)生的根源和要解決的問題,一再的強調(diào)一些不相關(guān)的東西,卻一直回避到底如何在應(yīng)用中加快項目進度、提高效率等這些內(nèi)容,對初學(xué)者真的是很不好的影響。一個編碼能力很差(這么說不表示我編碼能力強,我就是很弱的那種),閱讀代碼能力都不行的,公布一個代碼生成器,你這是鬧哪出呢。
也許有人說,初學(xué)者可以在自己編寫代碼生成的過程中學(xué)到很多知識,比如界面和控件、模板等,這是另一種方式的自欺欺人。大多外置代碼生成器都不是web方式的,如果工作中一直從事asp.net開發(fā),那所謂的界面和控件學(xué)到的知識還不如去學(xué)學(xué)自定義web控件開發(fā)了,除了代碼生成,學(xué)到的模板知識在其他哪些方面基本沒啥作用。畢竟所有初學(xué)者都遇到這種必須用模板的需求只能是YY。
通過查找發(fā)現(xiàn)相似代碼,進行重構(gòu),引入框架或代碼生成,本身就對代碼能力有一定的要求,一些基礎(chǔ)的代碼實現(xiàn)和代碼結(jié)構(gòu)都搞不懂,就想做個會有很多人使用的某某代碼生成器,既浪費了自己的時間又誤導(dǎo)了初學(xué)者,這樣不好。我就有過這樣的經(jīng)歷,希望能對別人也起到一些警醒的作用,在本身閱讀代碼和寫一些基礎(chǔ)代碼的能力都沒有的時候,別跟風(fēng),別浮躁,努力提高自己的基礎(chǔ),即使研究代碼生成,也從加快項目進度的角度出發(fā),把精力用在集成在IDE中的動態(tài)代碼生成上,要有一種追根溯源,立足實用的態(tài)度,不要看人家把msdn的示例代碼改一改發(fā)個隨筆說成自己原創(chuàng),你就模仿,也不要看別人發(fā)了很多新技術(shù)的掃盲或入門系列很火,你就模仿。更不要看一些人用標(biāo)題忽悠了很多人評論,你也照著做。搞不清楚來龍去脈,不要去學(xué)人家拋什么“XX技術(shù)無用論”,用"最XX的XX"之類的眼球文。即使能獲得很多不明真相的小白的推崇,誤導(dǎo)了眾多停留在索要源代碼為目標(biāo)的初學(xué)者,也不能對自己起到任何提升的作用。
原文鏈接:http://www.cnblogs.com/easygame/archive/2012/09/18/2690819.html
【編輯推薦】
- Visual Studio 2012的C++原生單元測試
- Visual Studio 2012 Ultimate RC安裝手記
- VS 2012單元測試和測試資源管理器
- 微軟正式發(fā)布 Visual Studio 2012
- Visual Studio 11下的C++異步編程1