Edzy_Java

            BlogJava :: 首頁(yè) ::  ::  ::  :: 管理 ::
            58 隨筆 :: 12 文章 :: 11 評(píng)論 :: 0 Trackbacks

          什么是G#

            G#是我在過(guò)去幾個(gè)月里構(gòu)思出來(lái)的一種新的程序設(shè)計(jì)語(yǔ)言。其目的是生成類型安全的代碼,這些代碼能夠在編譯時(shí)或運(yùn)行時(shí)被注入(Inject)到一個(gè)代碼基(Code Base)中。其語(yǔ)法是C# 2.0的一個(gè)超集。和其他代碼生成技術(shù)與工具(如CodeSmith,一種偉大的工具/語(yǔ)言)不同,G#并不打算生成用作起始點(diǎn)(Starting Point)或用于消費(fèi)(Consumption)的代碼。取而代之,G#使用了面向方面的程序設(shè)計(jì)(AOP)技術(shù)來(lái)向客戶代碼中注入代碼。我們會(huì)快速地介紹一下AOP,因?yàn)樗鼘?duì)很多開(kāi)發(fā)者來(lái)說(shuō)還是嶄新的。

            AOP

            AOP或稱面向方面的軟件開(kāi)發(fā)(AOSD)于1997年在Xerox Parc創(chuàng)建,是一種相對(duì)先進(jìn)的軟件典范(Paradigm)。其思想很簡(jiǎn)單,通過(guò)使開(kāi)發(fā)者每次只關(guān)注一個(gè)問(wèn)題域來(lái)降低軟件開(kāi)發(fā)的復(fù)雜性。換句話說(shuō),人們?cè)趪L試解決一個(gè)業(yè)務(wù)問(wèn)題(比如在互聯(lián)網(wǎng)上銷售產(chǎn)品)時(shí)無(wú)需考慮安全、線程、登錄、數(shù)據(jù)訪問(wèn)和其他領(lǐng)域的問(wèn)題。這被稱為關(guān)注點(diǎn)的分離(Separation of Concerns)。通過(guò)分離這些領(lǐng)域或者方面,某一特殊方面的專家可以開(kāi)發(fā)能夠解決該方面問(wèn)題的最好的解決方案,因此開(kāi)發(fā)者無(wú)需再去掌握所有的行業(yè)。這樣就有望產(chǎn)生健壯并且功能完善的軟件,因?yàn)殚_(kāi)發(fā)者只需做一名“軟件問(wèn)題域”的專家。

            AOP通過(guò)定義方面(也就是一組行為)來(lái)開(kāi)始,然后將代碼注入到適當(dāng)?shù)姆椒ㄖ腥ァC總€(gè)代碼注入點(diǎn)都被稱作是一個(gè)結(jié)合點(diǎn)(Join Point)。讓我們以安全為例。“所有的輸入都是邪惡的”是安全界的一條曼特羅(Mantra,咒語(yǔ))。對(duì)抗這一難題的一種做法是,要求所有的開(kāi)發(fā)者編寫(xiě)代碼時(shí)都要在使用數(shù)據(jù)之前檢查是否有惡意的輸入。開(kāi)發(fā)者們很可能會(huì)開(kāi)發(fā)一個(gè)輔助方法用來(lái)解決這一問(wèn)題,然后所有的開(kāi)發(fā)者都會(huì)在他們的代碼中簡(jiǎn)單地調(diào)用這個(gè)輔助方法。AOP可以解決這一問(wèn)題,它抽取這些相同的輔助方法并創(chuàng)建一個(gè)方面,然后將其注入到需要對(duì)用戶輸入進(jìn)行檢查的地方。這個(gè)過(guò)程稱為編排(Weaving)。我們沒(méi)有簡(jiǎn)單地定義一個(gè)將會(huì)收到“邪惡輸入”方面的位置列表,而是定義了將要使用的一組標(biāo)準(zhǔn)(Criteria)。既然是這樣,我們就希望除方面之外能夠注入所有帶有參數(shù)的公共屬性、方法和構(gòu)造器。比起創(chuàng)建一個(gè)列表,這樣做的好處是開(kāi)發(fā)者們無(wú)需再憑借他們的記憶來(lái)將需要對(duì)輸入進(jìn)行檢查的方法添加到列表中。

            相對(duì)于你所熟悉的AOP語(yǔ)言如AspectJ,G#并沒(méi)有單獨(dú)的編排文件:編排被集成到了語(yǔ)法當(dāng)中。對(duì)于大多數(shù)程序員來(lái)說(shuō),別人可以將代碼注入到他們的代碼基之中,這無(wú)疑是一種容易引起恐慌的建議。為了解決這一問(wèn)題,G#包含了一個(gè)用來(lái)處理這一問(wèn)題的安全模型,并且允許程序員來(lái)控制哪些人可以注入代碼以及可以注入什么樣的代碼,這將放在后面進(jìn)行討論。在我們深入之前先來(lái)看一些基礎(chǔ)要素:

            基礎(chǔ)

          public class Client
          {
           public Client()
           {
            Messenger(“Hello World”);
           }

           private void Messenger(string message)
           {
            Console.WriteLine(message);
           }
          }

          public generator Rename
          {
           static generation ChangeIt : target Client.Messenger(string message)
           {
            pre
            {
             string oldMessage = message;
             message = “Hello G#”;
            }

            post
            {
             message = oldMessage;
            }
           }
          }

            盡管這個(gè)例子沒(méi)有任何用途,但它演示了G#的大量特性。首先,Client類使用了標(biāo)準(zhǔn)的C#語(yǔ)法——這在G#中是有效的,它只是簡(jiǎn)單地向控制臺(tái)輸出了消息“Hello World”。這個(gè)類定義下面是G#中新增的語(yǔ)言構(gòu)造,稱作生成器(Generator)。現(xiàn)在只需認(rèn)為生成器是所有用于定義“如何生成代碼”的代碼的容器即可,這和類(Class)類似。Rename是這個(gè)生成器的名字,就好像Client是類的名字一樣。接下來(lái)定義了一個(gè)名為ChangeIt的生成(Generation)。生成和方法類似,每次調(diào)用它都會(huì)執(zhí)行一些動(dòng)作,不同的是在調(diào)用生成的時(shí)候會(huì)通常產(chǎn)生代碼。注意ChangeIt有一個(gè)目標(biāo)(Target),在這里是來(lái)自Client類的Messenger方法。目標(biāo)可以是任何(語(yǔ)言)構(gòu)造,并且還可以包括通配符和正則表達(dá)式來(lái)指定一組項(xiàng)目作為目標(biāo)。這表示由該生成所發(fā)出(Emit)的所有代碼都將被注入到Messenger方法中。關(guān)鍵字pre規(guī)定了其后面花括號(hào)中定義的所有代碼都將被注入到Messenger方法體中定義的代碼之前。關(guān)鍵字post規(guī)定了其后面花括號(hào)中定義的所有代碼都將被注入到Messenger方法體中定義的代碼之后。因?yàn)橛藐P(guān)鍵字static標(biāo)記了這個(gè)生成,因此代碼的實(shí)際注入是編譯過(guò)程的一部分,理解這一點(diǎn)很重要。程序員將無(wú)法看到Messenger方法的變化,除非使用ildasm或Reflector來(lái)檢查Messenger方法。此外還有一個(gè)目前還只是夢(mèng)想的特性,就是能夠生成動(dòng)態(tài)的Region,這樣在Visual Studio .NET中就能打開(kāi)它來(lái)檢查生成器都在客戶環(huán)境中生成了哪些代碼。稍后我們將討論其他類型的生成。

          private void Messenger(string message)
          {
           // From ChangeIt pre block.
           string oldMessage = message;

           // From ChangeIt pre block.
           message = “Hello G#”;

           // From the Messenger method body.

           Console.WriteLine(message);

           // From ChangIt post block.
           message = oldMessage;
          }


            這個(gè)方法因此將向控制臺(tái)打印“Hello G#”,然后再將message字符串改回最初傳入的消息。注意在.NET中字符串是不可變的,因此實(shí)際上是不能改變一個(gè)字符串所包含的內(nèi)容的。因此通過(guò)在post塊中將message改回初始的消息以保護(hù)Messenger方法外的“Hello World”消息并不是必須的,但是對(duì)于在Messenger方法體中執(zhí)行的任何代碼來(lái)說(shuō),后置的注入代碼都是很重要的。這里出現(xiàn)的一個(gè)邏輯問(wèn)題是,在后置條件(Post Condition)之后,Messenger方法體中的代碼究竟什么時(shí)候執(zhí)行呢?這個(gè)問(wèn)題完美地引出了下一節(jié)。

            生成器的繼承

            我們上面的例子表明,生成器就是生成的包容器,但是其中還可以包含類能夠包含的所有成員(如方法、屬性、域、事件等等)。此外可見(jiàn)性和其他修飾符如virtual也可以用于生成。因此,生成器是面向?qū)ο蟮模⑶铱梢员舜死^承。這樣做的原因和類類似:這允許基生成器定義一個(gè)基本的注入行為,并由子生成器定義更多的特殊的行為。
          public class Client
          {
           protected string message;
           public Client()
           {
            this.message = “Hello World”;
            Messenger(this.message);
           }

           private void Messenger(string message)
           {
            onsole.WriteLine(message);
           }
          }

          public generator Base
          {
           protected virtual generation ChangeIt : target Client.Messenger(*)
           {
            pre
            {
             string message = “Hello G#”;
            }

           post
           {
            this.message = message;
           }
          }

          }

          public generator Sub : Base
          {
           protected override generation ChangeIt : target Client.Messenger(string message)
           {
            pre
            {
             base.pre();
             message = capture.message;
            }

            post
            {
             capture.message = message;
             base.Post();
            }
           }
          }

            下面給出了發(fā)出的Messenger方法。我們來(lái)分解一下這些代碼。Sub生成器從Base生成器派生而來(lái),并且重寫(xiě)了“基類”中的“方法”ChangeIt。“基類”中使用星號(hào)(*)定義了一個(gè)目標(biāo),它可以被任何參數(shù)取代,這意味著它的目標(biāo)可以是Client類中Messenger的所有重載形式。稍后我們將介紹定義目標(biāo)的細(xì)節(jié)。憑經(jīng)驗(yàn)就可以知道一個(gè)基本的規(guī)則是,在重寫(xiě)的生成中必須為目標(biāo)指定更多的特性。在代碼的另外一部分中,我們使用了關(guān)鍵字base來(lái)訪問(wèn)基生成器的pre和post,因此我們可以決定是在Base生成器發(fā)出代碼之前還是之后發(fā)出Sub生成器的代碼。

          private void Messenger(string message)
          {
           // Base
           string capture.message = “Hello G#”;
           // Sub

           message = capture.message;
           Console.WriteLine(message);

           // Sub
           capture.message = message;
           // Base
           
           this.message = capture.message;
          }


            捕獲

            關(guān)鍵字capture用于引用在同一個(gè)生成的作用域中定義的變量,即使這個(gè)變量定義在基生成器中。能夠訪問(wèn)這些變量的原因是,所有生成的代碼都將位于相同的作用域中。在訪問(wèn)被捕獲(Capture)的變量時(shí),關(guān)鍵字capture并不是必需的,但這里的Messenger方法使用了同名的變量,在這種情況下,就需要關(guān)鍵字capture來(lái)解決混淆問(wèn)題。變量message定義在Base生成器的ChangeIt生成中,而其目標(biāo)Messenger方法中也有可能定義同名的參數(shù),因?yàn)槲覀冊(cè)诙x中使用了星號(hào)(*)通配符。這種請(qǐng)況很可能發(fā)生,因?yàn)樯芍锌梢远x局部變量,并且稍后在其目標(biāo)方法的重載中也可以定義同名的局部變量。如果G#不對(duì)其采取行動(dòng)的話,當(dāng)目標(biāo)方法中定義了和生成中的局部變量同名的變量時(shí),就會(huì)引發(fā)一個(gè)編譯錯(cuò)誤。

            分節(jié)符

            為了指出如何發(fā)出代碼,G#提供了能夠通過(guò)執(zhí)行代碼來(lái)取代發(fā)出代碼。這通過(guò)“§”符號(hào)來(lái)實(shí)現(xiàn),該符號(hào)稱作分節(jié)符(Section Sign)。該符號(hào)在Times New Roman字體中是這樣的:§,而在Courier New字體(譯注:原文是Courier字體,這里為了同一代碼格式使用了Courier New字體,兩者非常相似)中是這樣的:§。當(dāng)在代碼中放置了§的時(shí)候,其后的代碼將被執(zhí)行,而不是被發(fā)出:

          pre
          {
           § for(int i = 0; i < 10; i++)
           § {
              Console.WriteLine(i);
           § }
          }

            綠色高亮的代碼在編譯期間將被執(zhí)行而不是被發(fā)出。從這個(gè)pre塊發(fā)出的代碼是這樣的:

          Console.WriteLine(0);

          Console.WriteLine(1);

          Console.WriteLine(2);

          Console.WriteLine(3);

          Console.WriteLine(4);

          Console.WriteLine(5);

          Console.WriteLine(6);

          Console.WriteLine(7);

          Console.WriteLine(8);

          Console.WriteLine(9);

          Console.WriteLine(10);

            注意當(dāng)這幾行代碼被發(fā)出時(shí),“i”被它的整數(shù)值取代了。G#知道如何注入基本類型如int和float的值,但他無(wú)法發(fā)出類或其他自定義的復(fù)雜類型。如果§后跟了一個(gè)方法,該方法的返回值類型必須是基本類型、void或emit,如果是其他類型,則編譯過(guò)程將會(huì)破壞返回的所有東西。我們將在下一節(jié)里解釋關(guān)鍵字emit。我從來(lái)沒(méi)有見(jiàn)過(guò)哪個(gè)鍵盤上有§符號(hào),不過(guò)可以通過(guò)定義組合快捷鍵來(lái)產(chǎn)生這個(gè)符號(hào),我選擇“Ctrl+l”(小寫(xiě)的L)來(lái)在Word里輸出這個(gè)符號(hào),并且在Visual Studio .NET中為這個(gè)快捷鍵組合寫(xiě)了一個(gè)宏來(lái)輸出這個(gè)符號(hào)。

            關(guān)鍵字emit

            我們已經(jīng)討論了如何使用關(guān)鍵字pre和post來(lái)發(fā)出代碼,但G#中有更豐富的方法來(lái)指定如何以及在哪里發(fā)出代碼。其中一種方法就是像使用pre和post那樣使用關(guān)鍵字emit:

          emit
          {
           Console.WriteLine(“Hello G#”);
          }

            代碼“Console.WriteLine(“Hello G#”);”會(huì)在哪里發(fā)出?它將在其基生成的emit塊中發(fā)出。[(That reminds be of the definition of a normal)]OK,那么pre和post實(shí)際上也是emit塊,只不過(guò)它們定義了發(fā)出代碼的位置(方法體的前面和方法體的后面)。對(duì)于上面的代碼片斷,我們需要提供一個(gè)上下文環(huán)境來(lái)說(shuō)明一下這些代碼是在哪里發(fā)出的。

          ...

          pre
          {
           § Counter();
          }

          ...

          void Counter()
          {
           emit
           {
            Console.WriteLine(“The emit keyword in action”);
           }
          }

            當(dāng)一個(gè)帶有該pre塊的生成被編譯時(shí),它會(huì)調(diào)用Counter方法,因?yàn)镃ounter()的前面有§符號(hào)。在Counter方法中,關(guān)鍵字emit用于注入對(duì)Console.WriteLine的調(diào)用。emit塊將會(huì)用塊中的代碼來(lái)取代對(duì)Counter()的調(diào)用。一個(gè)方法中emit塊的數(shù)量沒(méi)有任何限制,并且可以在emit塊中使用§。

            此外,emit只是對(duì)G#框架(G# Framework)中定義的Emit類型的一個(gè)映射,因此我們可以創(chuàng)建emit的實(shí)例。

          pre
          {
           § DisplayParts();
          }
          ...

          public emit DisplayParts()
          {
           emit partOne, partTwo;
           partOne
           {
            § Injector(partTwo);
            Console.WriteLine(“Part One”);
            § partTwo.Emit();
           }
           return partOne.Emit();
          }

          private void Injector(emit target)
          {
           target
           {
            Console.WriteLine(“Injection...”);
           }
          }

            在上面的代碼片斷中,我們?cè)贒isplayParts生成的定義中創(chuàng)建了兩個(gè)emit對(duì)象partOne和partTwo。然后我們使用partOne加花括號(hào)定義了一個(gè)emit塊。花括號(hào)之間的所有代碼都將被發(fā)出到partOne的局部存儲(chǔ)(Local Store)中,當(dāng)我們?cè)趐artOne對(duì)象上調(diào)用Emit方法時(shí),將會(huì)返回這個(gè)局部存儲(chǔ)。最后,注意該代碼段的pre塊中調(diào)用了返回值類型為emt的DisplayParts。[Since the emitted code is not caught it is emitted into the pre block.]

            目標(biāo)

            我們已經(jīng)探討了當(dāng)以一個(gè)方法為目標(biāo)時(shí)如何使用關(guān)鍵字pre和post,但除此之外,G#還定義了一些關(guān)鍵字以使用其他語(yǔ)言構(gòu)造作為目標(biāo)。下面的表格給出了其他能夠發(fā)出代碼的關(guān)鍵字和它們的描述。為這些關(guān)鍵字指定目標(biāo)構(gòu)造時(shí)也可以使用通配符,參見(jiàn)后面的示例:

          關(guān)鍵字 描述
          class 注入目標(biāo)命名空間中所有的類
          namespace 注入目標(biāo)命名空間中所有的命名空間
          set | get 注入目標(biāo)所定義的所有set和get區(qū)域
          generator 注入目標(biāo)所定義的所有生成器
          generation 注入目標(biāo)所定義的所有生成
          property 注入目標(biāo)所定義的所有屬性
          method 注入目標(biāo)所定義的所有方法

          public generator Base
          {
           protected virtual generation ChangeClient : target Client
           {
            property public string *
            {
             get
             {
              post
              {
               Console.WriteLine(value);
              }
             }
             set
             {
              pre
              {
               Console.WriteLine(value);
              }
             }
            }

            method (public | protected) * Cl*(*)
            {
             Console.WriteLine(“Cl* Method Targeted”);
            }
           }
          }

            這里我們注入了所有類型為string而名字任意的屬性。我們還在get訪問(wèn)器中使用了關(guān)鍵字value,該關(guān)鍵字在G#中表示由目標(biāo)代碼的get訪問(wèn)器所返回的值。在這里使用pre和post與在方法中的用法無(wú)異。接下來(lái)的關(guān)鍵字method定義了我們將要注入的所有公共的和受保護(hù)的方法,其中兩個(gè)星號(hào)(*)分別表示返回值類型任意并且方法的名字是以“Cl”開(kāi)頭、后跟任意多個(gè)任意的字符。(譯注:實(shí)際上是3個(gè)星號(hào),后面括號(hào)里那個(gè)表示該方法能夠帶任意多的參數(shù)。)在名字中還可以使用“英鎊($)”符號(hào)作為通配符,表示任意的一個(gè)字符。注意到這一點(diǎn)很重要:Client類中所有滿足約束條件的成員都會(huì)被注入。

            自適應(yīng)生成

            第二種生成的類型是自適應(yīng)生成(Adaptive Generation),只是簡(jiǎn)單地把一個(gè)生成前面的關(guān)鍵字static換成adaptive。自適應(yīng)生成在運(yùn)行時(shí)生成并且注入代碼,因此它可以檢查對(duì)象的狀態(tài)以指導(dǎo)生成。

            比起靜態(tài)生成,自適應(yīng)生成的優(yōu)勢(shì)在于第三方也可以提供生成框架和組件。第三方開(kāi)發(fā)者可以通過(guò)創(chuàng)建幻象目標(biāo)(Phantom Target)來(lái)以他們一無(wú)所知的代碼基作為目標(biāo)。幻象目標(biāo)并不存在于生成框架或目標(biāo)框架中。當(dāng)開(kāi)發(fā)者希望使用一個(gè)第三方的生成器時(shí),他們可以加入幻象的命名空間、類、方法并將生成的代碼重定位到他們的代碼基中適當(dāng)?shù)奈恢谩?public class Client

          {
           protected string message;
           public Client()
           {
            this.message = “Hello World”;
            Messenger(this.message);
           }
           
           public string Message
           {
            get
            {
             return this.message;
            }
           }

           private void Messenger(string message)
           {
            Console.WriteLine(message);
           }
          }

          // Phantom Target

          namespace ThirdParty.Security
          {
           public adaptive generator Input : target Client
           {}
          }

            程序集:

          ?

          // Third Party generator

          public generator Security
          {
           protected adaptive generation CheckInput
           : target ThirdParty.Security.Input
           {
            property public string *
            {
             get
             {
              pre
              {
               value = ValidateInput(value);
              }
             }
            }

            method public * *(all string *(input))
            {
             pre
             {
              input = ValidateInput(input);
             }
            }
           }
          }

            在上面的代碼中,我們定義了一個(gè)Client類、一個(gè)第三方生成器Security和一個(gè)幻象目標(biāo)命名空間ThirdParty.Security。類和幻象目標(biāo)被定義在一個(gè)程序集中,而第三方生成器在另外一個(gè)程序集中提供。第三方定義了所有類型為string的公共屬性在返回之前都要調(diào)用ValidateInput方法。它還定義了所有返回值類型為string的公共方法在執(zhí)行任何代碼前都要對(duì)其類型為string的參數(shù)調(diào)用ValidateInput。G#中的關(guān)鍵字all表示對(duì)于作用域內(nèi)所有符合標(biāo)準(zhǔn)的參數(shù)都要做這件事情。星號(hào)(*)表示參數(shù)的名字可以是任意的,我們必須將想要引用的實(shí)參的名字放在圓括號(hào)中,以告訴編譯器我們正在使用這個(gè)名字,但我們不希望將它作為標(biāo)準(zhǔn)的一部分。

            現(xiàn)在的CLR能夠在運(yùn)行時(shí)動(dòng)態(tài)地注入IL代碼,這發(fā)生在程序集加載時(shí),通過(guò)Profiler API完成。然而這種途徑還存在著一系列的安全問(wèn)題,因?yàn)樗昧薈AS,因此還需要深入的研究才能找到一種切實(shí)可行的解決方案。我們將在下面描述這是如何完成的。 CAS和注入特性
          現(xiàn)在已經(jīng)有望解決注入代碼所引發(fā)的安全問(wèn)題了。G#的安全模型能夠確保只有你希望他注入代碼的人才能注入代碼,并且這些代碼只能限制在你所允許的代碼訪問(wèn)安全(CAS,Code Access Security)許可中。通過(guò)使用元數(shù)據(jù),你可以聲明你授予注入代碼的權(quán)限。這仍需要定義一種語(yǔ)法并加入建議[Still need to define this syntax and open to suggestions.]。所有包含生成器和生成的程序集都必須被賦予一個(gè)強(qiáng)密鑰,然后為目標(biāo)程序集添加一個(gè)帶有該公共密鑰記號(hào)的Injector特性。只有在Injector中指出了強(qiáng)密鑰的程序集才能運(yùn)行和注入代碼。

            總結(jié)

            代碼生成為我們提供了各種可能性,我們希望G#能夠發(fā)展成為一個(gè)泛型的、類型安全的代碼生成語(yǔ)言。

          posted on 2006-11-20 18:33 lbfeng 閱讀(173) 評(píng)論(0)  編輯  收藏 所屬分類: 新技術(shù)雜談
          主站蜘蛛池模板: 九寨沟县| 南召县| 贡嘎县| 井陉县| 中西区| 永新县| 谷城县| 弥勒县| 万盛区| 灵川县| 宜黄县| 乌审旗| 边坝县| 贺州市| 金寨县| 西盟| 枝江市| 赞皇县| 马鞍山市| 巍山| 蚌埠市| 远安县| 青铜峡市| 依安县| 岳阳市| 平泉县| 太和县| 华容县| 从化市| 宜州市| 边坝县| 绥阳县| 陕西省| 都江堰市| 金沙县| 清新县| 清镇市| 灵璧县| 白山市| 琼结县| 从化市|