熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

.NET家族新成員:G#語言簡介

2013-11-13 09:54:13  來源: .NET編程 

  什麼是G#
  
    G#是我在過去幾個月裡構思出來的一種新的程序設計語言其目的是生成類型安全的代碼這些代碼能夠在編譯時或運行時被注入(Inject)到一個代碼基(Code Base)中其語法是C# 的一個超集和其他代碼生成技術與工具(如CodeSmith一種偉大的工具/語言)不同G#並不打算生成用作起始點(Starting Point)或用於消費(Consumption)的代碼取而代之G#使用了面向方面的程序設計(AOP)技術來向客戶代碼中注入代碼我們會快速地介紹一下AOP因為它對很多開發者來說還是嶄新的
  
    AOP
  
    AOP或稱面向方面的軟件開發(AOSD)於年在Xerox Parc創建是一種相對先進的軟件典范(Paradigm)其思想很簡單通過使開發者每次只關注一個問題域來降低軟件開發的復雜性換句話說人們在嘗試解決一個業務問題(比如在互聯網上銷售產品)時無需考慮安全線程登錄數據訪問和其他領域的問題這被稱為關注點的分離(Separation of Concerns)通過分離這些領域或者方面某一特殊方面的專家可以開發能夠解決該方面問題的最好的解決方案因此開發者無需再去掌握所有的行業這樣就有望產生健壯並且功能完善的軟件因為開發者只需做一名軟件問題域的專家
  
    AOP通過定義方面(也就是一組行為)來開始然後將代碼注入到適當的方法中去每個代碼注入點都被稱作是一個結合點(Join Point)讓我們以安全為例所有的輸入都是邪惡的是安全界的一條曼特羅(Mantra咒語)對抗這一難題的一種做法是要求所有的開發者編寫代碼時都要在使用數據之前檢查是否有惡意的輸入開發者們很可能會開發一個輔助方法用來解決這一問題然後所有的開發者都會在他們的代碼中簡單地調用這個輔助方法AOP可以解決這一問題它抽取這些相同的輔助方法並創建一個方面然後將其注入到需要對用戶輸入進行檢查的地方這個過程稱為編排(Weaving)我們沒有簡單地定義一個將會收到邪惡輸入方面的位置列表而是定義了將要使用的一組標准(Criteria)既然是這樣我們就希望除方面之外能夠注入所有帶有參數的公共屬性方法和構造器比起創建一個列表這樣做的好處是開發者們無需再憑借他們的記憶來將需要對輸入進行檢查的方法添加到列表中
  
    相對於你所熟悉的AOP語言如ASPectJG#並沒有單獨的編排文件編排被集成到了語法當中對於大多數程序員來說別人可以將代碼注入到他們的代碼基之中這無疑是一種容易引起恐慌的建議為了解決這一問題G#包含了一個用來處理這一問題的安全模型並且允許程序員來控制哪些人可以注入代碼以及可以注入什麼樣的代碼這將放在後面進行討論在我們深入之前先來看一些基礎要素
  
    基礎

     public class Client
  {
   public Client()
   {
    Messenger(Hello World);
   }
  
   private void Messenger(string message)
   {
    ConsoleWriteLine(message);
   }
  }
  
  public generator Rename
  {
   static generation ChangeIt : target ClientMessenger(string message)
   {
    pre
    {
     string oldMessage = message;
     message = Hello G#;
    }
  
    post
    {
     message = oldMessage;
    }
   }
  } 

  盡管這個例子沒有任何用途但它演示了G#的大量特性首先Client類使用了標准的C#語法——這在G#中是有效的它只是簡單地向控制台輸出了消息Hello World這個類定義下面是G#中新增的語言構造稱作生成器(Generator)現在只需認為生成器是所有用於定義如何生成代碼的代碼的容器即可這和類(Class)類似Rename是這個生成器的名字就好像Client是類的名字一樣接下來定義了一個名為ChangeIt的生成(Generation)生成和方法類似每次調用它都會執行一些動作不同的是在調用生成的時候會通常產生代碼注意ChangeIt有一個目標(Target)在這裡是來自Client類的Messenger方法目標可以是任何(語言)構造並且還可以包括通配符和正則表達式來指定一組項目作為目標這表示由該生成所發出(Emit)的所有代碼都將被注入到Messenger方法中關鍵字pre規定了其後面花括號中定義的所有代碼都將被注入到Messenger方法體中定義的代碼之前關鍵字post規定了其後面花括號中定義的所有代碼都將被注入到Messenger方法體中定義的代碼之後因為用關鍵字static標記了這個生成因此代碼的實際注入是編譯過程的一部分理解這一點很重要程序員將無法看到Messenger方法的變化除非使用ildasm或Reflector來檢查Messenger方法此外還有一個目前還只是夢想的特性就是能夠生成動態的Region這樣在Visual 中就能打開它來檢查生成器都在客戶環境中生成了哪些代碼稍後我們將討論其他類型的生成 

     private void Messenger(string message)
  {
   // From ChangeIt pre block
   string oldMessage = message;
  
   // From ChangeIt pre block
   message = Hello G#;
  
   // From the Messenger method body
  
   ConsoleWriteLine(message);
  
   // From ChangIt post block
   message = oldMessage;
  } 

  這個方法因此將向控制台打印Hello G#然後再將message字符串改回最初傳入的消息注意在NET中字符串是不可變的因此實際上是不能改變一個字符串所包含的內容的因此通過在post塊中將message改回初始的消息以保護Messenger方法外的Hello World消息並不是必須的但是對於在Messenger方法體中執行的任何代碼來說後置的注入代碼都是很重要的這裡出現的一個邏輯問題是在後置條件(Post Condition)之後Messenger方法體中的代碼究竟什麼時候執行呢?這個問題完美地引出了下一節

  生成器的繼承
  
    我們上面的例子表明生成器就是生成的包容器但是其中還可以包含類能夠包含的所有成員(如方法屬性事件等等)此外可見性和其他修飾符如virtual也可以用於生成因此生成器是面向對象的並且可以彼此繼承這樣做的原因和類類似這允許基生成器定義一個基本的注入行為並由子生成器定義更多的特殊的行為

    public class Client
  
  {
   protected string message;
   public Client()
   {
    ssage = Hello World;
    Messenger(ssage);
   }
  
   private void Messenger(string message)
   {
    onsoleWriteLine(message);
   }
  }
  
  public generator Base
  {
   protected virtual generation ChangeIt : target ClientMessenger(*)
   {
    pre
    {
     string message = Hello G#;
    }
  
   post
   {
    ssage = message;
   }
  }
  
  }
  
  public generator Sub : Base
  {
   protected override generation ChangeIt : target ClientMessenger(string message)
   {
    pre
    {
     basepre();
     message = ssage;
    }
  
    post
    {
     ssage = message;
     basePost();
    }
   }
  } 

  下面給出了發出的Messenger方法我們來分解一下這些代碼Sub生成器從Base生成器派生而來並且重寫了基類中的方法ChangeIt基類中使用星號(*)定義了一個目標它可以被任何參數取代這意味著它的目標可以是Client類中Messenger的所有重載形式稍後我們將介紹定義目標的細節憑經驗就可以知道一個基本的規則是在重寫的生成中必須為目標指定更多的特性在代碼的另外一部分中我們使用了關鍵字base來訪問基生成器的pre和post因此我們可以決定是在Base生成器發出代碼之前還是之後發出Sub生成器的代碼

     private void Messenger(string message)
  {
   // Base
   string ssage = Hello G#;
   // Sub
  
   message = ssage;
   ConsoleWriteLine(message);
  
   // Sub
   ssage = message;
   // Base
   
   ssage = ssage;
  } 

  捕獲
  
    關鍵字capture用於引用在同一個生成的作用域中定義的變量即使這個變量定義在基生成器中能夠訪問這些變量的原因是所有生成的代碼都將位於相同的作用域中在訪問被捕獲(Capture)的變量時關鍵字capture並不是必需的但這裡的Messenger方法使用了同名的變量在這種情況下就需要關鍵字capture來解決混淆問題變量message定義在Base生成器的ChangeIt生成中而其目標Messenger方法中也有可能定義同名的參數因為我們在定義中使用了星號(*)通配符這種請況很可能發生因為生成中可以定義局部變量並且稍後在其目標方法的重載中也可以定義同名的局部變量如果G#不對其采取行動的話當目標方法中定義了和生成中的局部變量同名的變量時就會引發一個編譯錯誤
  
    分節符
  
    為了指出如何發出代碼G#提供了能夠通過執行代碼來取代發出代碼這通過§符號來實現該符號稱作分節符(Section Sign)該符號在Times New Roman字體中是這樣的§而在Courier New字體(譯注原文是Courier字體這裡為了同一代碼格式使用了Courier New字體兩者非常相似)中是這樣的§當在代碼中放置了§的時候其後的代碼將被執行而不是被發出

     pre
  {
   § for(int i = ; i < ; i++)
   § {
      ConsoleWriteLine(i);
   § }
  } 
   
    綠色高亮的代碼在編譯期間將被執行而不是被發出從這個pre塊發出的代碼是這樣的
  
  ConsoleWriteLine();
  
  ConsoleWriteLine();
  
  ConsoleWriteLine();
  
  ConsoleWriteLine();
  
  ConsoleWriteLine();
  
  ConsoleWriteLine();
  
  ConsoleWriteLine();
  
  ConsoleWriteLine();
  
  ConsoleWriteLine();
  
  ConsoleWriteLine();
  
  ConsoleWriteLine();
  
    注意當這幾行代碼被發出時i被它的整數值取代了G#知道如何注入基本類型如int和float的值但他無法發出類或其他自定義的復雜類型如果§後跟了一個方法該方法的返回值類型必須是基本類型void或emit如果是其他類型則編譯過程將會破壞返回的所有東西我們將在下一節裡解釋關鍵字emit我從來沒有見過哪個鍵盤上有§符號不過可以通過定義組合快捷鍵來產生這個符號我選擇Ctrl+l(小寫的L)來在Word裡輸出這個符號並且在Visual 中為這個快捷鍵組合寫了一個宏來輸出這個符號

  關鍵字emit
  
    我們已經討論了如何使用關鍵字pre和post來發出代碼但G#中有更豐富的方法來指定如何以及在哪裡發出代碼其中一種方法就是像使用pre和post那樣使用關鍵字emit

     emit
  {
   ConsoleWriteLine(Hello G#);
  } 

    代碼ConsoleWriteLine(Hello G#);會在哪裡發出?它將在其基生成的emit塊中發出[(That reminds be of the definition of a normal)]OK那麼pre和post實際上也是emit塊只不過它們定義了發出代碼的位置(方法體的前面和方法體的後面)對於上面的代碼片斷我們需要提供一個上下文環境來說明一下這些代碼是在哪裡發出的

     
  
  pre
  {
   § Counter();
  }
  
  
  
  void Counter()
  {
   emit
   {
    ConsoleWriteLine(The emit keyword in action);
   }
  } 

  當一個帶有該pre塊的生成被編譯時它會調用Counter方法因為Counter()的前面有§符號在Counter方法中關鍵字emit用於注入對ConsoleWriteLine的調用emit塊將會用塊中的代碼來取代對Counter()的調用一個方法中emit塊的數量沒有任何限制並且可以在emit塊中使用§
  
    此外emit只是對G#框架(G# Framework)中定義的Emit類型的一個映射因此我們可以創建emit的實例

     pre
  {
   § DisplayParts();
  }
  
  
  public emit DisplayParts()
  {
   emit partOne partTwo;
   partOne
   {
    § Injector(partTwo);
    ConsoleWriteLine(Part One);
    § partTwoEmit();
   }
   return partOneEmit();
  }
  
  private void Injector(emit target)
  {
   target
   {
    ConsoleWriteLine(Injection);
   }
  } 
 

    在上面的代碼片斷中我們在DisplayParts生成的定義中創建了兩個emit對象partOne和partTwo然後我們使用partOne加花括號定義了一個emit塊花括號之間的所有代碼都將被發出到partOne的局部存儲(Local Store)中當我們在partOne對象上調用Emit方法時將會返回這個局部存儲最後注意該代碼段的pre塊中調用了返回值類型為emt的DisplayParts[Since the emitted code is not caught it is emitted into the pre block]
  
    目標
  
    我們已經探討了當以一個方法為目標時如何使用關鍵字pre和post但除此之外G#還定義了一些關鍵字以使用其他語言構造作為目標下面的表格給出了其他能夠發出代碼的關鍵字和它們的描述為這些關鍵字指定目標構造時也可以使用通配符參見後面的示例
  
  關鍵字 描述
  class 注入目標命名空間中所有的類
  namespace 注入目標命名空間中所有的命名空間
  set | get 注入目標所定義的所有set和get區域
  generator 注入目標所定義的所有生成器
  generation 注入目標所定義的所有生成
  property 注入目標所定義的所有屬性
  method 注入目標所定義的所有方法

     public generator Base
  {
   protected virtual generation ChangeClient : target Client
   {
    property public string *
    {
     get
     {
      post
      {
       ConsoleWriteLine(value);
      }
     }
     set
     {
      pre
      {
       ConsoleWriteLine(value);
      }
     }
    }
  
    method (public | protected) * Cl*(*)
    {
     ConsoleWriteLine(Cl* Method Targeted);
    }
   }
  } 

  這裡我們注入了所有類型為string而名字任意的屬性我們還在get訪問器中使用了關鍵字value該關鍵字在G#中表示由目標代碼的get訪問器所返回的值在這裡使用pre和post與在方法中的用法無異接下來的關鍵字method定義了我們將要注入的所有公共的和受保護的方法其中兩個星號(*)分別表示返回值類型任意並且方法的名字是以Cl開頭後跟任意多個任意的字符(譯注實際上是個星號後面括號裡那個表示該方法能夠帶任意多的參數)在名字中還可以使用英鎊($)符號作為通配符表示任意的一個字符注意到這一點很重要Client類中所有滿足約束條件的成員都會被注入
  

  自適應生成
  
    第二種生成的類型是自適應生成(Adaptive Generation)只是簡單地把一個生成前面的關鍵字static換成adaptive自適應生成在運行時生成並且注入代碼因此它可以檢查對象的狀態以指導生成
  
    比起靜態生成自適應生成的優勢在於第三方也可以提供生成框架和組件第三方開發者可以通過創建幻象目標(Phantom Target)來以他們一無所知的代碼基作為目標幻象目標並不存在於生成框架或目標框架中當開發者希望使用一個第三方的生成器時他們可以加入幻象的命名空間方法並將生成的代碼重定位到他們的代碼基中適當的位置

    public class Client
  
  {
   protected string message;
   public Client()
   {
    ssage = Hello World;
    Messenger(ssage);
   }
   
   public string Message
   {
    get
    {
     return ssage;
    }
   }
  
   private void Messenger(string message)
   {
    ConsoleWriteLine(message);
   }
  }
  
  // Phantom Target
  
  namespace ThirdPartySecurity
  {
   public adaptive generator Input : target Client
   {}
  } 

  程序集

     // Third Party generator
  
  public generator Security
  {
   protected adaptive generation CheckInput
   : target ThirdPartySecurityInput
   {
    property public string *
    {
     get
     {
      pre
      {
       value = ValidateInput(value);
      }
     }
    }
  
    method public * *(all string *(input))
    {
     pre
     {
      input = ValidateInput(input);
     }
    }
   }
  } 
   
    在上面的代碼中我們定義了一個Client類一個第三方生成器Security和一個幻象目標命名空間ThirdPartySecurity類和幻象目標被定義在一個程序集中而第三方生成器在另外一個程序集中提供第三方定義了所有類型為string的公共屬性在返回之前都要調用ValidateInput方法它還定義了所有返回值類型為string的公共方法在執行任何代碼前都要對其類型為string的參數調用ValidateInputG#中的關鍵字all表示對於作用域內所有符合標准的參數都要做這件事情星號(*)表示參數的名字可以是任意的我們必須將想要引用的實參的名字放在圓括號中以告訴編譯器我們正在使用這個名字但我們不希望將它作為標准的一部分
  
    現在的CLR能夠在運行時動態地注入IL代碼這發生在程序集加載時通過Profiler API完成然而這種途徑還存在著一系列的安全問題因為它禁用了CAS因此還需要深入的研究才能找到一種切實可行的解決方案我們將在下面描述這是如何完成的 CAS和注入特性
  現在已經有望解決注入代碼所引發的安全問題了G#的安全模型能夠確保只有你希望他注入代碼的人才能注入代碼並且這些代碼只能限制在你所允許的代碼訪問安全(CASCode Access Security)許可中通過使用元數據你可以聲明你授予注入代碼的權限這仍需要定義一種語法並加入建議[Still need to define this syntax and open to suggestions]所有包含生成器和生成的程序集都必須被賦予一個強密鑰然後為目標程序集添加一個帶有該公共密鑰記號的Injector特性只有在Injector中指出了強密鑰的程序集才能運行和注入代碼
  
    總結
  
    代碼生成為我們提供了各種可能性我們希望G#能夠發展成為一個泛型的類型安全的代碼生成語言根據您的意見和建議G#的語法還會改變並且進一步精煉因此非常感謝您閱讀G#的相關文檔如果您有任何意見問題或想法請給Ernie Booth發emailgsharp@ernieboothname


From:http://tw.wingwit.com/Article/program/net/201311/11918.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.