现在的位置: 首页 > 综合 > 正文

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

2013年02月15日 ⁄ 综合 ⁄ 共 4286字 ⁄ 字号 评论关闭
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.     
  14.     public abstract class Creator
  15.     {
  16.         public static Widget DynamicCreate(Widget w)
  17.         {
  18.             return (Widget)Activator.CreateInstance(w.GetType());
  19.         }
  20.     }
  21. }
  22. //第二种动态创建对象方式
  23. //采用FormatterServices.GetUninitializedObject(Type)
  24. //获得一个未初始化对象,再调用对象的工厂方法得到对象。
  25. //经测试时间效率大概是前一种的150倍左右。
  26. namespace SerializationCreator
  27. {
  28.     public class Widget
  29.     {
  30.         public virtual Widget GetInstance()
  31.         {
  32.             return new Widget();
  33.         }
  34.     }
  35.     public class WidgetA : Widget
  36.     {
  37.         public override Widget GetInstance()
  38.         {
  39.             return new WidgetA();
  40.         }
  41.     }
  42.     public class WidgetB : Widget
  43.     {
  44.         public override Widget GetInstance()
  45.         {
  46.             return new WidgetB();
  47.         }
  48.     }
  49.     //
  50.     public class WidgetZ : Widget
  51.     {
  52.         public override Widget GetInstance()
  53.         {
  54.             return new WidgetZ();
  55.         }
  56.     }

  57.     //Serialization配合工厂方法动态生成对象,高效率
  58.     public abstract class Creator
  59.     {
  60.         public static Widget DynamicCreate(Widget w)
  61.         {
  62.             Widget widgetFactory = 
  63.                 (Widget)FormatterServices.GetUninitializedObject(w.GetType());
  64.             return widgetFactory.GetInstance();
  65.         }
  66.     }
  67. }

复制代码

因此,决定采用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. {
  13.     handled = false;
  14.     if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
  15.     {
  16.         if (commandName == "AddFactoryMethod.Connect.AddFactoryMethod")
  17.         {
  18.             InputBox ibox = new InputBox();

抱歉!评论已关闭.