C#玩转指针(二):预处理器、using、partial关键字与region的妙用

简介:

欲练神功,引刀自宫。为了避免内存管理的烦恼,Java咔嚓一下,把指针砍掉了。当年.Net也追随潮流,咔嚓了一下,化名小桂子,登堂入室进了皇宫。康熙往下面一抓:咦?还在?——原来是假太监韦小宝。

打开unsafe选项,C#指针就biu的一下子蹦出来了。指针很强大,没必要抛弃这一强大的工具。诚然,在大多数情况下用不上指针,但在特定的情况下还是需要用到的。比如:

(1)大规模的运算中使用指针来提高性能;

(2)与非托管代码进行交互;

(3)在实时程序中使用指针,自行管理内存和对象的生命周期,以减少GC的负担。

目前使用指针的主要语言是C和C++。但是由于语法限制,C和C++中的指针的玩法很单调,在C#中,可以进行更优雅更好玩的玩法。本文是《重新认识C#: 玩转指针》一文的续篇,主要是对《重新认识C#: 玩转指针》内容进行总结和改进。

 

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

#endregion

这个预处理器比较简单。被引用的文件中只能存在一个 #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 引用上面的模板文件:

复制代码
 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 >= 0 && 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)

相关文章
|
7月前
|
存储 安全 编译器
【C++专栏】C++入门 | auto关键字、范围for、指针空值nullptr
【C++专栏】C++入门 | auto关键字、范围for、指针空值nullptr
74 0
|
2月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
2月前
|
存储 编译器 程序员
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(二)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
5月前
|
开发框架 .NET C#
【Azure Developer】C# / .NET 静态函数中this关键字的作用
在C#中,`this`关键字用于扩展方法,允许向已有类型添加功能而不修改其源代码。扩展方法必须在静态类中定义,且第一个参数使用`this`修饰,如`public static XElement AcquireElement(this XContainer container, string name, bool addFirst = false)`。这种方式增强了代码的可读性和类型的安全性,尤其在处理第三方库时。
|
5月前
|
存储 安全 编译器
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
|
6月前
|
存储 安全 编译器
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
67 5
|
6月前
|
存储 安全 编译器
C++进阶之路:何为引用、内联函数、auto与指针空值nullptr关键字
C++进阶之路:何为引用、内联函数、auto与指针空值nullptr关键字
50 2
|
6月前
|
编译器 C# C++
【.NET Core】C#预处理器指令
【.NET Core】C#预处理器指令
71 1
|
6月前
|
开发框架 安全 .NET
C#关键字概览
C#关键字概览
|
7月前
|
存储 编译器 Linux
【C++】C++入门第二课(函数重载 | 引用 | 内联函数 | auto关键字 | 指针空值nullptr)
【C++】C++入门第二课(函数重载 | 引用 | 内联函数 | auto关键字 | 指针空值nullptr)