天天看點

C#玩轉指針(二):預處理器、using、partial關鍵字與region的妙用

欲練神功,引刀自宮。為了避免記憶體管理的煩惱,Java咔嚓一下,把指針砍掉了。當年.Net也追随潮流,咔嚓了一下,化名小桂子,登堂入室進了皇宮。康熙往下面一抓:咦?還在?——原來是假太監韋小寶。

打開unsafe選項,C#指針就biu的一下子蹦出來了。指針很強大,沒必要抛棄這一強大的工具。誠然,在大多數情況下用不上指針,但在特定的情況下還是需要用到的。比如:

(1)大規模的運算中使用指針來提高性能;

(2)與非托管代碼進行互動;

(3)在實時程式中使用指針,自行管理記憶體和對象的生命周期,以減少GC的負擔。

C#下使用指針有兩大限制:

(1)使用指針隻能操作struct,不能操作class;

(2)不能在泛型類型代碼中使用未定義類型的指針。

第一個限制沒辦法突破,是以需要将指針操作的類型設為struct。struct + 指針,恩,就把C#當更好的C來用吧。對于第二個限制,寫一個預處理器來解決問題。

下面是我寫的簡單的C#預處理器的代碼,不到200行:

代碼

然後編譯為 Csmacro.exe ,放入系統路徑下。在需要使用預處理器的項目中添加 Pre-build event command lind:

Csmacro.exe $(ProjectDir)

Visual Studio 有個很好用的關鍵字 “region” ,我們就把它當作我們預處理器的關鍵字。include 一個檔案的文法是:

#region include "xxx.cs"  #endregion

一個檔案中可以有多個 #region include 塊。

被引用的檔案不能全部引用,因為一個C#檔案中一般包含有 using,namespace … 等,全部引用的話會報編譯錯誤。是以,在被引用檔案中,需要通過關鍵字來規定被引用的内容:

#region mixin …

這個預處理器比較簡單。被引用的檔案中隻能存在一個 #region mixin 塊,且在這個region的内部,不能有其它的region塊。

預處理器 Csmacro.exe 的作用就是找到所有 cs 檔案中的 #region include 塊,根據 #region include  路徑找到被引用檔案,将該檔案中的 #region mixin 塊 取出,替換進 #region include 塊中,生成一個以_Csmacro.cs結尾的新檔案 。

由于C#的兩個文法糖“partial” 和 “using”,預處理器非常好用。如果沒有這兩個文法糖,預處理器會很醜陋不堪。(誰說文法糖沒價值!一些小小的文法糖,足以實作新的程式設計範式。)

partial 關鍵字 可以保證一個類型的代碼存在幾個不同的源檔案中,這保證了預處理器的執行,您可以像寫正常的代碼一樣編寫公共部分代碼,并且正常編譯。

using 關鍵字可以為類型指定一個的别名。這是一個不起眼的文法糖,卻在本文中非常重要:它可以為不同的類型指定一個相同的類型别名。之是以引入預處理器,就是為了複用包含指針的代碼。我們可以将代碼抽象成兩部分:變化部分和不變部分。一般來說,變化部分是類型的型别,如果還有其它非類型的變化,我們也可以将這些變化封裝成新的類型。這樣一來,我們可以将變化的類型放在源檔案的頂端,使用using 關鍵字,命名為固定的别名。然後把不變部分的代碼,放在 #region mixin 塊中。這樣的話,讓我們需要 #region include 時,隻需要在 #region include  塊的前面(需要在namespace {} 的外部)為類型别名指定新的類型。

舉例說明,位圖根據像素的格式可以分為很多種,這裡假設有兩種圖像,一種是像素是一個Byte的灰階圖像ImageU8,一個是像素是一個Argb32的彩色圖像ImageArgb32。ImageU8代碼如下:

 在 ImageArgb32 中,我們也要寫重複的代碼:

對于 Width和Height屬性,我們可以建立基類來進行抽象和複用,然而,對于m_pointer和SetValue方法,如果放在基類中,則需要抹去類型資訊,且變的十分醜陋。由于C#不支援泛型類型的指針,也無法提取為泛型代碼。

使用 Csmacro.exe 預處理器,我們就可以很好的處理。

首先,建立一個模闆檔案 Image_Template.cs 

然後建立一個基類 BaseImage,再從BaseImage派生ImageU8和ImageArgb32。兩個派生類都是 partial 類:

下面我們建立一個 ImageU8_ClassHelper.cs 檔案,來 #region include 引用上面的模闆檔案:

<a></a>

 1 using TPixel = System.Byte; 

 2 

 3 using System; 

 4 namespace XXX 

 5 { 

 6     public partial class ImageU8 

 7     { 

 8         #region include "Image_Template.cs" 

 9         #endregion 

10     } 

11 }

編譯,編譯器會自動生成檔案 “ImageU8_ClassHelper_Csmacro.cs” 。将這個檔案引入項目中,編譯通過。這個檔案内容是:

對于 ImageArgb32 類也可以進行類似操作。

從這個例子可以看出,使用 partial 關鍵字,能夠讓原代碼、模闆代碼、ClassHelper代碼三者共存。使用 using 關鍵字,可以分離出代碼中變化的部分出來。

下面是我寫的圖像操作的一些模闆代碼:

(1)通過模闆提供指針和索引器:

(2)通過模闆提供常用的操作和Lambda表達式支援

配合lambda表達式,用起來很爽。在方法“FindTemplate”中,有這一句:

if (pattern &gt;= 0 &amp;&amp; srcStart[rr * stride + cc] != pattern)

其中 srcStart[rr * stride + cc] 是 TPixel 不定類型,而 pattern 是 int 類型,兩者之間需要進行比較,但是并不是所有的類型都提供和整數之間的 != 操作符。為此,我建立了個新的模闆 TPixel_Template。

(3)通過模闆提供 != 操作符 的定義

這裡,在 #region mixin  塊被注釋掉了,不注釋掉編譯器會報錯。注釋之後,不會影響程式預處理。

通過 ClassHelper類來使用模闆:

由于 Argb32 未提供和 int 之間的比較,是以,在這裡 #region include "TPixel_Template.cs"。而Byte可以與int比較,是以,在ImageU8中,就不需要#region include "TPixel_Template.cs": 

是不是很有意思呢?強大的指針,結合C#強大的文法和快速編譯,至少在圖像處理這一塊是很好用的。

本文轉自xiaotie部落格園部落格,原文連結http://www.cnblogs.com/xiaotie/archive/2010/07/19/1780440.html如需轉載請自行聯系原作者

xiaotie 集異璧實驗室(GEBLAB)