天天看點

使用者代碼序列化: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能避免無效設定的串行化,有效地删除了無效設定。當此地有無效值時,構件使用者可以看到像下面一樣得事物:

繼續閱讀