天天看点

用户代码序列化:CodeDom的力量

   VS.NET中的主要对象持续性机制是通过直接的代码发送来处理的。在所有Web和Windows Forms包含的InitializeComponent方法中,你已经看到了这一现象。在Component-继承类型中也显示了这一过程。两个属性决定了这一行为:System.ComponentModel.Design.Serialization.RootDesignerSerializerAttribute以及System.ComponentModel.Design.Serialization.DesignerSerializerAttribute.,正如在开始讨论到的DesignerAttribute,在根和标准串化器之间有一个明显的区别。但是不像DesignerAttribute,总是利用标准(非-根)串化器,当构件同时是被设计的根构件时,要额外利用根串化器。通常只是定制非-根串化器。此非-根串化器的指示器就是:IComponent接口已经包含根串化器。

[RootDesignerSerializer(typeof(RootCodeDomSerializer), typeof(CodeDomSerializer))] 
public interface IComponent 


  
   
  
      

   但是它没有提供设计常规串化器的属性。但是,IComponent的特殊实现提供这个属性。例如:

[DesignerSerializer(typeof(Microsoft.VSDesigner.WebForms.ControlCodeDomSerializer)), 
typeof(CodeDomSerializer))] 
public class System.Web.UI.Control : IComponent 


  
   
  
      

   以及

[DesignerSerializer(typeof(System.Windows.Forms.Design.ControlCodeDomSerializer)), 
typeof(CodeDomSerializer))] 
public class System.Windows.Forms.Control : Component 


  
   
  
      

   注意:两者都有它们独特的串化器,因为Windows窗体被保存到代码的方法与Web 窗体被保存到代码的方法有很大的区别。前者串行化InitializeComponent方法的所有值和设置,然而后者仅仅储存在代码隐藏事件处理程序连接附件中,因为控件属性被保存在aspx页面中。

   你肯定注意到不管控件是用在VB.NET项目中还是用在一个C#项目(或者是可用于此目的的其它语言)中,InitializeComponent总是用正确的语言发送代码。因为.NET中一个新的功能(此功能被称为CodeDom. )CodeDom是一个类型集,此类型集允许我们输写对象层次结构 ,这些对象层次结构代表更加的普通语言构造,例如:类型,领域以及属性宣告,事件连接附件,try..catch..finally块等等。它们允许我们创建一个所谓的预期目标代码的abstract syntax tree (AST)。abstract syntax tree (AST)是抽象的,它不代表VB.NET或者C#代码,但是代表constructs它们自己。

    串化器传递到集成开发环境的东西就是包含代码的ASTs,这正是它们期望保存的。集成开发环境依次创建一个System.CodeDom.Compiler.CodeDomProvider -继承类型,System.CodeDom.Compiler.CodeDomProvider -继承类型与当前项目完全匹配,例如:Microsoft.CSharp.CSharpCodeProvider 或者Microsoft.VisualBasic.VBCodeProvider.此对象最后负责以具体语言代码传输AST,而这些具体语言代码早已被嵌入到了InitializeComponent方法中。

    CodeDom并不是非常复杂,让我们来迅速地学习一下CodeDom。

CodeDom句法

    最好的方法是通过例子来学习CodeDom,因此来看一下某种C#代码,以及它对等的CodeDom语句(我们假设它们都发生在类型内部)。被下载的代码包括一个项目,以此来检查CodeDomTester文件夹中的CodeDom。它是一个简单的控制器应用程序,在此控制器应用程序上有两个骨架方法:GetMembers和GetStatements。可以把样本CodeDom代码放到这两个方法中,看一下输出的结果。

C#:

private string somefield;

  
   
  
      

CodeDom:

CodeMemberField field = new CodeMemberField(typeof(string), "somefield");      

   所有类型层成员表示法CodeMemberEvent, CodeMemberField, CodeMemberMethod and CodeMemberProperty,都是从CodeTypeMember继承来的,默认地拥有private和final属性。

C#:

public string somefield = "SomeValue";

  
   
  
      

CodeDom:

CodeMemberField field = new CodeMemberField(typeof(string), "somefield"); 
field.InitExpression = new CodePrimitiveExpression("SomeValue"); 
field.Attributes = MemberAttributes.Public; 
      

C#

this.somefield = GetValue();

  
   
  
      

CodeDom:

CodeFieldReferenceExpression field = new CodeFieldReferenceExpression( 
new CodeThisReferenceExpression(), "somefield"); 
CodeMethodInvokeExpression getvalue = new CodeMethodInvokeExpression( 
new CodeThisReferenceExpression(), "GetValue", new CodeExpression[0]); 
CodeAssignStatement assign = new CodeAssignStatement(field, getvalue); 


  
   
  
      

   注意:实际上冗长的程度按指数倍增加。并且注意C#代码中的GetValue()方法对此有一个隐式引用,在CodeDom中必须是显示的。

C#

this.GetValue("Someparameter", this.somefield);      

CodeDom

CodeMethodInvokeExpression call = new CodeMethodInvokeExpression(); 
call.Method = new CodeMethodReferenceExpression( 
new CodeThisReferenceExpression(), "GetValue"); 
call.Parameters.Add(new CodePrimitiveExpression("Someparameter")); 
CodeFieldReferenceExpression field = new 
CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "somefield"); 
call.Parameters.Add(field); 


  
   
  
      

   我们调用同一方法的一个假定超载。注意首先创建方法调用表示法,然后为它(指向this)指定一个方法引用和一个方法名称。下面附加两个参数,第二个单元是这个领域的引用。

    如果想避免无尽的而且没用的变量声明,不采用临时变量就可以创建语句。这使得代码不那么清晰易读,但是更加紧凑。创建这些语法的一个好技术就是考虑目标代码,从内部向外部生成目标代码。例如在上面的代码中。

this.GetValue("Someparameter", this.somefield);

  
   
  
      

   首先创建参数,然后考虑方法引用,一旦想做这些工作,写下下面这些东西:

CodeMethodInvokeExpression call = 
new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), 
"GetValue", 
new CodeExpression[] { new CodePrimitiveExpression("Someparameter"), 
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), 
"somefield")}); 


  
   
  
      

    我们看到的最后情况是一个this.somefield,然后是原始表示法。这是作为方法调用的参数阵列的初始化表示法而传递的。然后你得到this.somefield,最后this.somefield引发实际调用。

    注意正确的缩排可以提供巨大的帮助,但是大部分工作还是需要你自己完成,尤其是一些附带有巢状层次的工作。为了达到一些合法性,最重要的巢状就是阵列初始化(前面所提到的)。也推荐把预期C# (or VB.NET)输出代码放到多线语句上面,因此每个人都知道你试图发送什么。

    这些是定义所有跨语言功能的类型。但是让我们看一下需要给被扩展属性提供的具体持续性代码。

发送CodeDom

    我们将需要把一个自定义串化器与Base控制器连接起来,为了定制持续性,以及发送代码来保存视图 映射以及我们需要的任何潜在代码。

[DesignerSerializer(typeof(ControllerCodeDomSerializer), 
typeof(CodeDomSerializer))] 
public class BaseController : Component, IExtenderProvider 
      

    自定义串化器必须从CodeDomSerializer继承而来。这个基本抽象类型(此基本抽象类型位System.ComponentModel.Design序列化)包含必须实现的两个抽象方法。

public abstract class CodeDomSerializer 
{ 
public abstract object Serialize( 
IDesignerSerializationManager manager, object value); 
public abstract object Deserialize( 
IDesignerSerializationManager manager, object codeObject); 
} 


  
   
  
      

    无论什么时候,对象需要被保存时,Serialize方法都会被调用。返回值必须是类型CodeStatementCollection(此类型包括代码来保存)的一个对象。同样的,Deserialize方法中的codeObject参数包括前面被发送的语句。

    在概述中,我们就谈到示例根组件,通过根设计器来设计它。在根组件世界中,几乎所有构件(以及控件)中都会发生这一过程。真正发生的却是集成开发环境 执行InitializeComponent中的大部分代码,重新创建对象,就像它们处在运行库里一样。我们说大部分而不是所有是因为仅仅修改问题中的构件的语句才被调用:例如:它们中的属性设置以及方法调用.通过定制Deserialize方法,我们有机会在设计时期重建过程中互相作用。通常这是没必要的,因此大部分时间我们就把球传到初始构件串化器ComponentCodeDomSerializer,此ComponentCodeDomSerializer基本上实现此代码。为了得到类型的串化器,我们使用我们接受到的IDesignerSerializationManager参数的GetSerializer方法。此对象有其它有用的方法,我们将在后面使用它们。

    因此Deserialize实现通常是这样的:

public override object Deserialize( 
IDesignerSerializationManager manager, object codeObject) 
{ 
CodeDomSerializer serializer = 
manager.GetSerializer(typeof(Component), typeof(CodeDomSerializer)); 
return serializer.Deserialize(manager, codeObject); 
} 


  
   
  
      

    重新找回构件初始串化器是管理器的普通使用。因为反序列化通常是一样的。我们将把它放在基本类型,并且将从控制器串化器生成基本类型。

internal abstract class BaseCodeDomSerializer : CodeDomSerializer 
{ 
protected CodeDomSerializer GetBaseComponentSerializer( 
IDesignerSerializationManager manager) 
{ 
return (CodeDomSerializer) 
manager.GetSerializer(typeof(Component), typeof(CodeDomSerializer)); 
} 

public override object Deserialize( 
IDesignerSerializationManager manager, object codeObject) 
{ 
return GetBaseComponentSerializer(manager).Deserialize(manager, 
codeObject); 
} 
} 
      

   后面将为这个类型补充其它的公共方法。现在需要进行串行化过程,我们需要通过Hashtable的所有DictionaryEntry元素来迭代,发送下面的代码,为了保存ConfiguredViews属性。

controller.ConfiguredViews.Add("txtID", 
new ViewInfo("txtID", "Text", "Publisher", "ID")); 


  
   
  
      

   另一个公共的方法就是让初始构件串化器执行它自己的工作。然后补充我们的自定义语句。通过这种方法,我们避免了自己保存公共构件属性。因此串化器开始执行这些任务:

internal class ControllerCodeDomSerializer : BaseCodeDomSerializer 
{ 
public override object 
Serialize(IDesignerSerializationManager manager, object value) 
{ 
CodeDomSerializer serial = GetBaseComponentSerializer(manager); 
if (serial == null) 
return null; 
CodeStatementCollection statements = (CodeStatementCollection) 
serial.Serialize(manager, value); 


  
   
  
      

   即使根组件是Base控制器本身时,仍然会调用串化器,意识到这一点非常重要。在这种情况下,我们没必要维持自定义代码,因为当它被用于另一个构件内部时(例如:一个Page或者一个Form),它主要用于基础方面。为了考虑这个问题,我们要求使用先前用过的IDesignerHost服务,核对它的RootComponent属性。IDesignerSerializationManager实现IServiceProvider,因此我们用通常的GetService方法来完成它。

IDesignerHost host = (IDesignerHost) 
manager.GetService(typeof(IDesignerHost)); 
if (host.RootComponent == value) 
return statements; 


   
    
   
      

   当序列化/反序列化代码时,基本CodeDomSerializer类型可以和一些有用的方法一起合作。访问ConfiguredViews属性时,通过正在运行(value参数)的实际控制器的引用来完成这一过程。基本类型里的一个帮助方法创建正确的CodeDom对象,在CodeDom图表中,使用这个引用。

CodeExpression cnref = SerializeToReferenceExpression(manager, value);

   
    
   
      

   现在可以利用CodeExpression来创建属性引用。

CodePropertyReferenceExpression propref = 
new CodePropertyReferenceExpression(cnref, "ConfiguredViews"); 


   
    
   
      

   我们主要定义两个变量,简化下面将建立的一个表示法,再次看一下样本目标方法调用:

controller.ConfiguredViews.Add("txtID", 
new ViewInfo("txtID", "Text", "Publisher", "ID")); 


   
    
   
      

   我们已经为ConfiguredViews属性访问建立了第一部分,接下来的部分就是:

   • CodeMethodInvokeExpression: Add的调用

   • CodeExpression[]: 方法调用的参数. 

   • CodePrimitiveExpression: "txtID" 元字符串值.

   • CodeObjectCreateExpression: 新的 View Info 部分.

   • CodePrimitiveExpression:被传递到构造函数的每一个元字符串值

    因此代码就是:

Collapse 
BaseController cn = (BaseController) value; 
CodeExpression cnref = SerializeToReferenceExpression(manager, value); 

CodePropertyReferenceExpression propref = 
new CodePropertyReferenceExpression(cnref, "ConfiguredViews"); 
//Iterate the entries 
foreach (DictionaryEntry cv in cn.ConfiguredViews) 
{ 
ViewInfo info = (ViewInfo) cv.Value; 
if (info.ControlID != String.Empty && info.ControlProperty != null && 
info.Model != String.Empty && info.ModelProperty != String.Empty) 
{ 
//Generates: 
//controller.ConfiguredViews.Add(key, new ViewInfo([values])); 
statements.Add( 
new CodeMethodInvokeExpression( 
propref, "Add", 
new CodeExpression[] { 
new CodePrimitiveExpression(cv.Key), 
new CodeObjectCreateExpression( 
typeof(ViewInfo), 
new CodeExpression[] { 
new CodePrimitiveExpression(info.ControlID), 
new CodePrimitiveExpression(info.ControlProperty), 
new CodePrimitiveExpression(info.Model), 
new CodePrimitiveExpression(info.ModelProperty) } 
) } 
)); 
} 
} 
      

    注意:正确缩排在制定更加可读的语句方面能怎样提供帮助。顺便提一下,现在仅仅声明了两个临时变量,这两个临时变量也可以省略。

   我们可以使用下面的,给代码补充注释:

statements.Add(new 
CodeCommentStatement("-------- ClariuS Custom Code --------")); 


   
    
   
      

   使用构件,回到窗体,在相关联的InializeComponent章节中,我们可以拥有下面的代码。

private void InitializeComponent() 
{ 
... 
// ------------- ClariuS Custom Code ------------- 
this.controller.ConfiguredViews.Add("txtID", 
new Mvc.Components.Controller.ViewInfo("txtID", 
"Text", "Publisher", "ID")); 
this.controller.ConfiguredViews.Add("txtName", 
new Mvc.Components.Controller.ViewInfo("txtName", 
"Text", "Publisher", "Name")); 
... 


   
    
   
      

   代码生成程序完全适合所有类型引用,因为不会保证开发者将给类型补充必要的using 句子。

   生成代码时,我们也可以发送找到的错误。例如,如果检查到这些属性建立得不正确,也可以发送找到的错误。

Collapse 
if (info.ControlID != String.Empty && info.ControlProperty != null && 
info.Model != String.Empty && info.ModelProperty != String.Empty) 
{ 
//Report errors if necessary 
object ctl = manager.GetInstance(info.ControlID); 
if (ctl == null) 
{ 
manager.ReportError(String.Format("Control '{0}' associated" + 
" with the view mapping in " + "controller '{1}' doesn't " + 
"exist in the page.", info.ControlID, manager.GetName(value))); 
continue; 
} 
if (ctl.GetType().GetProperty(info.ControlProperty) == null) 
{ 
manager.ReportError(String.Format("Control property '{0}' in" + 
" control '{1}' associated " + "with the view mapping in controller" + 
" '{2}' doesn't exist.", info.ControlProperty, info.ControlID, 
manager.GetName(value))); 
continue; 
} 


   
    
   
      

   注意,我们使用其他manager方法,设计错误信息,GetInstance 和GetName允许我们重新找到对象引用以及各自的名称。当找到错误后,通过使用continue能避免无效设置的串行化,有效地删除了无效设置。当此地有无效值时,构件用户可以看到像下面一样得事物: