CodeModel的范例:一个为项目所有相关类添加工厂方法的AddIn

1、问题描述

对一个程序做性能优化,发现程序里会大量创建动态对象,是影响性能的一个瓶颈。程序里都是采用Activator.CreateInstance(Type)的方法,记得在codeproject看过一篇文章(原文在此:Dynamic Objects,Factories,and Runtime Machines to Boost Performance),对动态创建对象的几种方式进行效率对比,Activator.CreateInstance是效率较低的一种。采用FormatterServices.GetUninitializedObject得到一个未初始化的类,再调用类的工厂方法,效率有大幅提高。下面是简单的代码说明。更多内容请参见上文提到的文章。
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Runtime.Serialization;

  5. //第一种方式:Activator.CreateInstance(Type)方式。效率较低
  6. namespace ActivatorCreator
  7. {
  8. public class Widget { }
  9. public class WidgetA : Widget { }
  10. public class WidgetB : Widget { }
  11. //..
  12. public class WidgetZ : Widget { }

  13. public abstract class Creator
  14. {
  15. public static Widget DynamicCreate(Widget w)
  16. {
  17. return (Widget)Activator.CreateInstance(w.GetType());
  18. }
  19. }
  20. }
  21. //第二种动态创建对象方式
  22. //采用FormatterServices.GetUninitializedObject(Type)
  23. //获得一个未初始化对象,再调用对象的工厂方法得到对象。
  24. //经测试时间效率大概是前一种的150倍左右。
  25. namespace SerializationCreator
  26. public class Widget
  27. public virtual Widget GetInstance()
  28. return new Widget();
  29. public class WidgetA : Widget
  30. public override Widget GetInstance()
  31. return new WidgetA();
  32. public class WidgetB : Widget
  33. return new WidgetB();
  34. //
  35. public class WidgetZ : Widget
  36. return new WidgetZ();
  37. //Serialization配合工厂方法动态生成对象,高效率
  38. Widget widgetFactory =
  39. (Widget)FormatterServices.GetUninitializedObject(w.GetType());
  40. return widgetFactory.GetInstance();
  41. }
复制代码
因此,决定采用FormatterServices.GetUninitializedObject方法来代替Activator.CreateInstance(Type)。整个项目有两个基类以及从该基类继承的大量子类(数目大概有100+)需要更改,而且继承关系最深达到5层。代码的修改工作量不小。

2、解决办法


2.1 复制粘贴

大概是最常规也最无趣的方法,我在复制了10个左右的类后实在受不了这种单调和枯燥,放弃了。


2.2 Code Snippet

复制的进阶,就是用Code Snippet了。把相同代码做成Snippet,效率有大幅提高。Snippet文件内容如下:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  3. <CodeSnippet Format="1.0.0">
  4. <Header>
  5. <Title>Add Factory Method</Title>
  6. <Author>lumber</Author>
  7. <Description>为类添加工厂方法</Description>
  8. <HelpUrl></HelpUrl>
  9. <SnippetTypes />
  10. <Keywords />
  11. <Shortcut>fm</Shortcut>
  12. </Header>
  13. <Snippet>
  14. <References />
  15. <Imports />
  16. <Declarations>
  17. <Literal Editable="false">
  18. <ID> RetType </ID>
  19. <Type></Type>
  20. <ToolTip>返回的类型</ToolTip>
  21. <Default> RetType </Default>
  22. <!--注意这里的Function,以及ClassName()函数-->
  23. <Function>ClassName()</Function>
  24. </Literal>
  25. </Declarations>
  26. <Code Language="csharp" Kind="" Delimiter="$">
  27. <![CDATA[internal override CADEntityData GetInstance()
  28. {
  29. return new $RetType$();
  30. }]]>
  31. </Code>
  32. </Snippet>x
  33. </CodeSnippet>
  34. </CodeSnippets>
复制代码
在上面的snippet中,值得一提的是,使用了snippet function。即先定义了一个Literal,名称为RetType,代表工厂方法返回类型。我们知道不同的子类,工厂方法的函数签名相同,不同的是返回该类的实例。即RetType的值要等于被插入的类的名称。于是我们为RetType这个Literal提供了一个function,ClassName(),该函数返回snippet所在类的名称。

实际我们不需要手工来写这个文件,这里推荐Code Snippet Editor这个小 工具


2.3 CodeModel

现在只需要找到需要更改的类,敲下快捷键fm,再双击tab就ok了,委实比当初复制、编辑幸福多了。但人心难足,Snippet还是有不爽的地方:

1、找到并打开所有要修改的类(100+啊兄弟,项目里每个类都是一个单独文件)不停地重复按键,也是挺无聊的活。更重要的是,要保证不能遗漏——上文说了,很多子类都经过了多达5层的继承……

2、不同的基类要重新编制一个snippet。另外考虑到,如果以后别的项目要有类似的更改呢?如果项目是用VB.NET而不是c#呢?。。。。

看来,最好的解决办法是写一个AddIn了。

简单分析下任务,其实就是两个:1、寻找项目中要添加工厂方法的基类及所有派生类。2、为这些类添加一个相应的工厂函数。

很自然需要用到CodeModel。

关于VS的扩展开发,推荐园子里Anders Cui的 系列文章。而Anders Cui的系列恰好没有写关于CodeModel的内容,既有珠玉在前,所以小可也就斗胆续貂,简单写写关于CodeModel的内容。

直接用Vs2008新建项目,选择“Visual Stduio外接程序”,选择使用c# 语言开发,然后一路默认,编辑器就自动为你生成了一个AddIn项目。该项目已经自动生成了大部分代码,包括将的AddIn程序添加到工具 菜单上等。接下来只需要将需要执行的代码加入到Exec函数中即可。

我们再在项目中添加一个含文本框的窗体,作为输入界面,输入项目中要添加工厂方法的基类的全名(含命名空间)。接下来就是利用CodeModel寻找该类及其派生类,并添加工厂方法。还是代码说话吧,相关函数我在注释里都有说明,另外可以查询msdn。
  1. /// <summary>
  2. /// 实现 IDTCommandTarget接口的 Exec 方法。此方法在调用该命令时调用。编辑器自动生成。
  3. /// </summary>
  4. /// <param term='commandName'>要执行的命令的名称。</param>
  5. /// <param term='executeOption'>描述该命令应如何运行。</param>
  6. /// <param term='varIn'>从调用方传递到命令处理程序的参数。</param>
  7. /// <param term='varOut'>从命令处理程序传递到调用方的参数。</param>
  8. /// <param term='handled'>通知调用方此命令是否已被处理。</param>
  9. /// <seealso class='Exec' />
  10. public void Exec(string commandName,vsCommandExecOption executeOption,
  11. ref object varIn,ref object varOut,ref bool handled)
  12. handled = false;
  13. if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
  14. if (commandName == "AddFactoryMethod.Connect.AddFactoryMethod")
  15. InputBox ibox = new InputBox();
  16. //程序添加一个输入窗口,输入要添加工厂方法的基类的全称(含命名空间)
  17. if (ibox.ShowDialog() == DialogResult.OK
  18. && !string.IsNullOrEmpty(ibox.Str))
  19. {
  20. //获取当前解决方案的第一个项目的CodeModel
  21. //注意CodeModel中集合都是以下标1开始而非0。如Projects.Item(1)
  22. CodeModel cm = _applicationObject.Solution.Projects.Item(1).CodeModel;
  23. foreach (CodeElement ce in cm.CodeElements)
  24. {
  25. //只有命名空间和类中才包含类。
  26. if ((ce is CodeNamespace) | (ce is CodeClass))
  27. {
  28. SetAllClass(ce,ibox.Str);
  29. }
  30. }
  31. }
  32. handled = true;
  33. return;
  34. /// 寻找基类及所有子类,并添加工厂方法。
  35. /// 这个函数主要演示了:
  36. /// 1、如何寻找一个项目中包含的所有类。
  37. /// 注意,所有的类应该包含在项目的命名空间和类中。
  38. /// 2、如何判断类是指定类的派生类。
  39. /// 3、CodeElement(包括CodeClass、CodeFunction……)等一系列对象的基本用法。
  40. /// <param name="ct">类或者命名空间</param>
  41. /// <param name="str">基类的全称(含命名空间)</param>
  42. private void SetAllClass(CodeElement ct,string str)
  43. CodeClass cc = ct as CodeClass;
  44. if (cc != null && cc.get_IsDerivedFrom(str))
  45. //cc.get_IsDerivedFrom(str)函数用来判断类是否由全名为str的类派生得来。
  46. //注意!自身也是自身的派生类,即cc.get_IsDerivedFrom(cc.FullName)将返回true。
  47. //为基类和派生类添加工厂方法
  48. AddFactoryMethod(cc,str);
  49. CodeElements elements;
  50. if (ct is CodeNamespace)
  51. elements = (ct as CodeNamespace).Members;
  52. else
  53. elements = (ct as CodeClass).Members;
  54. foreach (var ce in elements)
  55. if ((ce is CodeNamespace) | (ce is CodeClass))
  56. //寻找嵌套的类
  57. SetAllClass(ce as CodeElement,serif; font-size: 12px; ">/// 为类添加工厂方法。
  58. /// 1、如何用AddFunction方法为类添加方法。
  59. /// 2、CodeModel和文档操作之间的结合。
  60. /// code***.GetStartPoint/GetEndPoint可以获得TextPoint;
  61. /// TextPoint.CodeElement可获得对应的codeElement。
  62. /// 3、为不同的编程语言(VB.NET,c#)提供插件
  63. /// <param name="cc">要添加方法的类</param>
  64. /// <param name="fullname">基类的全称</param>
  65. private void AddFactoryMethod(CodeClass cc,string fullname)
  66. string str1 = "";
  67. string str2 = "";
  68. if (cc.Language == CodeModelLanguageConstants.vsCMLanguageCSharp)
  69. str1 = string.Format("return new {0}();\n",cc.Name);
  70. if (cc.FullName == fullname)
  71. //基类
  72. str2 = "public virtual";
  73. else//子类
  74. str2 = "public override";
  75. else if (cc.Language == CodeModelLanguageConstants.vsCMLanguageVB)
  76. str1 = string.Format("return new {0}()\n",serif; font-size: 12px; "> str2 = "Public Overridable";
  77. str2 = "Public Overrides";
  78. //添加函数
  79. CodeFunction cf = cc.AddFunction("GetInstance",vsCMFunction.vsCMFunctionFunction,serif; font-size: 12px; "> fullname,-1,vsCMAccess.vsCMAccessPublic,null);
  80. //为函数添加文档注释
  81. cf.DocComment = "<summary>\n工厂方法生成一个实例。\n</summary>";
  82. EditPoint ep = cf.GetEndPoint(vsCMPart.vsCMPartBody).CreateEditPoint();
  83. //添加函数体
  84. ep.Insert(str1);
  85. ep = cf.GetStartPoint(vsCMPart.vsCMPartHeader).CreateEditPoint();
  86. ep.ReplaceText(6,str2,0);
  87. private DTE2 _applicationObject;
复制代码
完整代码可以在这里下载: 附件: AddFactoryMethod.rar(下载 0次)。

3、一些问题

3.1 CodeClass的属性DerivedTypes没有实现。否则寻找指定类的所有派生类将更为简化,程序更简单。

3.2 CodeClass.AddFunction方法,第2和第5个参数,使用示例代码以外的参数值,均会出现异常。Bug??所以示例最后被迫使用ReplaceText来完成加入虚函数的任务。希望有高手指点一下。

3.3 CodeModel中大部分集合,特别是有item属性但item非默认属性者,使用下标索引时,从1而非0开始。EnvDTE命名空间中其他集合大概也是。或者,推而广之,涉及到VSTA开发的(如VSTO),集合一般下标从1开始。毕竟这些领域还是VB为主。

3.4 和本文无关——后台编辑器在编辑代码的时候很不好用啊。尤其选择折叠代码的时候,居然不能正常显示。。。我浏览器的问题麽?

4、参考文献

1、《 Dynamic Objects,and Runtime Machines to Boost Performance》 by Philip Liebscher
2、《 Creating a C# Class using the CodeModel Object》 MSDN
3、《 如何:使用 CodeModel 对象分析 Visual Basic 代码》 MSDN
4、关于Vs扩展的系列文章: Visual Studio 2008 可扩展性开发(九):总结篇

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Format[$] ( expr [ , fmt ] ) format 返回变体型 format$ 强制返回为文本 -------------------------------- 数字类型的格式化 --------------------------------     固定格式参数:     General Number 普通数字,如可以用来去掉千位分隔号     format$("100,1
VB6或者ASP 格式化时间为 MM/dd/yyyy 格式,竟然没有好的办法, Format 或者FormatDateTime 竟然结果和系统设置的区域语言的日期和时间格式相关。意思是尽管你用诸如 Format(Now, "MM/dd/yyyy"),如果系统的设置格式区域语言的日期和时间格式分隔符是"-",那他还会显示为 MM-dd-yyyy     只有拼凑: <%response.write
在项目中添加如下代码:新建窗口来显示异常信息。 Namespace My ‘全局错误处理,新的解决方案直接添加本ApplicationEvents.vb 到工程即可 ‘添加后还需要一个From用来显示错误。如果到这步还不会则需要先打好基础啦 ‘======================================================== ‘以下事件
转了这一篇文章,原来一直想用C#做k3的插件开发,vb没有C#用的爽呀,这篇文章写与2011年,看来我以前没有认真去找这个方法呀。 https://blog.csdn.net/chzjxgd/article/details/6176325 金蝶K3 BOS的插件官方是用VB6编写的,如果  能用.Net下的语言工具开发BOS插件是一件很愉快的事情,其中缘由不言而喻,而本文则是个人首创,实现在了用V
Sub 分列() ‘以空格为分隔符,连续空格只算1个。对所选中的单元格进行处理 Dim m As Range, tmpStr As String, s As String Dim x As Integer, y As Integer, subStr As String If MsgBox("确定要分列处理吗?请确定分列的数据会覆盖它后面的单元格!", _
  窗体代码 1 Private Sub Text1_OLEDragDrop(Data As DataObject, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single) 2 Dim path As String, hash As String 3 For Each fil
  Imports MySql.Data.MySqlClient Public Class Form1 ‘ GLOBAL DECLARATIONS Dim conString As String = "Server=localhost;Database=net2;Uid=root;Pwd=123456;" Dim con As New MySqlConnection
‘導入命名空間 Imports ADODB Imports Microsoft.Office.Interop   Private Sub A1() Dim Sql As String Dim Cnn As New ADODB.Connection Dim Rs As New ADODB.Recordset Dim S As String   S = "Provider=OraOLEDB.Oracl
Imports System.IO Imports System.Threading Imports System.Diagnostics Public Class Form1 Dim A(254) As String    Function ping(ByVal IP As Integer) As String Dim IPAddress As String IPAddress = "10.0.
VB运行EXE程序,并等待其运行结束 参考:https://blog.csdn.net/useway/article/details/5494084 Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long Pr
今天碰到一个问题,登陆的时候,如果不需要验证手机号为空,则不去验证手机号 因为登陆的时候所有的验证信息都存放在一个数组里 Dim CheckUserInfo() As String ={UserBirthday, SecEmail, UserMob, UserSex, RealNameFirst, RealName, CheckCardID, CheckCardType, Contactemail
在VB6.0中,数据访问接口有三种: 1、ActiveX数据对象(ADO) 2、远程数据对象(RDO) 3、数据访问对象(DAO) 1.使用ADO(ActiveX Data Objec,ActiveX数据对象)连接SQL Server 1)使用ADO控件连接 使用ADO控件的ConnectionString属性就可以连接SQL Server,该属性包含一个由分号分隔的argument=value语
注:大家如果没有VB6.0的安装文件,可自行百度一下下载,一般文件大小在200M左右的均为完整版的软件,可以使用。   特别提示:安装此软件的时候最好退出360杀毒软件(包括360安全卫士,电脑管家等,如果电脑上有这些软件的话),因为现如今的360杀毒软件直接会对VB6.0软件误报,这样的话就可能会在安装过程中被误报阻止而导致安装失败,或者是安装后缺乏很多必须的组件(其它的杀毒软件或安全卫士之类的
Private Sub Form_Load() Call conndb End Sub Private Function conndb() Dim cn As New ADODB.Connection Dim rs As New ADODB.Recordset Dim strCn, sql As String Dim db_host As String Dim db_user As String
  PPSM06S70:  Add  moddate  EDITSPRINTJOB:  MAX(TO_CHAR(ETRN.MODDATE, ‘yyyy/mm/dd/HH24:MI AM‘)) ACTUAL_SHIPDATE   4.Test Scenario (1) :Query SQL Test DN:8016578337 SELECT CTRN.TKCTID TRUCK_ID,        
  沒有出現CrystalReportViewer時,須安裝CRforVS_13_0. 新增1個數據集,新增1個數據表,添加二列,列名要和資料庫名一樣. 修改目標Framework 修改app.config, <startup >改成<startup useLegacyV2RuntimeActivationPolicy ="true">  CrystalReport1.rpt增加數據庫專家 在表單
Imports System.Threading Imports System Public Class Form1 Dim th1, th2 As Thread Public Sub Method1() Dim i As Integer For i = 1 To 100 If Me.Label1.BackColor =
Friend Const PROCESS_ALL_ACCESS = &H1F0FFF = 2035711 Friend Const PROCESS_VM_READ = &H10 Friend Const PROCESS_VM_WRITE = &H20 Friend Const PAGE_READONLY = &H2 Friend Const PAGE_READWRITE = &H4 Friend
以下代码随手写的 并没有大量测试 效率也有待提升 如果需要C#的请自行转换 Function SplitBytes(Data As Byte(), Delimiter As Byte()) As List(Of Byte()) Dim i = 0 Dim List As New List(Of Byte()) Dim bytes As New
Imports System.Data.SqlClient Public Class Form1 REM Public conn1 As SqlConnection = New SqlConnection("server=.; Integrated Security=False;Initial Catalog= mydatabase1; User ID= sa;password")