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行:

ExpandedBlockStart.gif 代码
  1  using  System; 
  2  using  System.Collections.Generic; 
  3  using  System.IO; 
  4  using  System.Text; 
  5  using  System.Text.RegularExpressions; 
  6 
  7  namespace  Orc.Util.Csmacro 
  8 
  9       class  Program 
 10      { 
 11           static  Regex includeReg  =   new  Regex( " #region\\s+include.+\\s+#endregion " ); 
 12           static  Regex mixinReg  =   new  Regex( " (?<=#region\\s+mixin\\s)[\\s|\\S]+(?=#endregion) " ); 
 13           ///   <summary>  
 14           ///  Csmacro [dir|filePath] 
 15           ///  
 16           ///  语法: 
 17           ///      #region include "" 
 18           ///      #endregion 
 19           ///      
 20           ///   </summary>  
 21           ///   <param name="args"></param>  
 22           static   void  
 23               #region  include<> 
 24                  Main 
 25               #endregion  
 26              ( string [] args) 
 27          { 
 28               if  (args.Length  !=   1
 29              { 
 30                  PrintHelp(); 
 31                   return
 32              } 
 33 
 34              String filePath  =  args[ 0 ]; 
 35 
 36              Path.GetDirectoryName(filePath); 
 37              String dirName  =  Path.GetDirectoryName(filePath); 
 38  #if  DEBUG 
 39              Console.WriteLine( " dir: "   +  dirName); 
 40  #endif  
 41              String fileName  =  Path.GetFileName(filePath); 
 42  #if  DEBUG 
 43              Console.WriteLine( " file: "   +  fileName); 
 44  #endif  
 45 
 46               if  (String.IsNullOrEmpty(fileName)) 
 47              { 
 48                  Csmacro( new  DirectoryInfo(dirName)); 
 49              } 
 50               else  
 51              { 
 52                   if  (fileName.EndsWith( " .cs " ==   false
 53                  { 
 54                      Console.WriteLine( " Csmacro只能处理后缀为.cs的源程序. " ); 
 55                  } 
 56                   else  
 57                  { 
 58                      Csmacro( new  FileInfo(fileName)); 
 59                  } 
 60              } 
 61 
 62              Console.WriteLine( " [Csmacro]:处理完毕. " ); 
 63 
 64  #if  DEBUG 
 65              Console.ReadKey(); 
 66  #endif  
 67          } 
 68 
 69           static   void  Csmacro(DirectoryInfo di) 
 70          { 
 71              Console.WriteLine( " [Csmacro]:进入目录 "   +  di.FullName); 
 72 
 73               foreach  (FileInfo fi  in  di.GetFiles( " *.cs " , SearchOption.AllDirectories)) 
 74              { 
 75                  Csmacro(fi); 
 76              } 
 77          } 
 78 
 79           static   void  Csmacro(FileInfo fi) 
 80          { 
 81              String fullName  =  fi.FullName; 
 82               if  (fi.Exists  ==   false
 83              { 
 84                  Console.WriteLine( " [Csmacro]:文件不存在- "   +  fullName); 
 85              } 
 86               else   if  (fullName.EndsWith( " _Csmacro.cs " )) 
 87              { 
 88                   return
 89              } 
 90               else  
 91              { 
 92                  String text  =  File.ReadAllText(fullName); 
 93 
 94                  DirectoryInfo parrentDirInfo  =  fi.Directory; 
 95 
 96                  MatchCollection mc  =  includeReg.Matches(text); 
 97                   if  (mc  ==   null   ||  mc.Count  ==   0 return
 98                   else  
 99                  { 
100                      Console.WriteLine( " [Csmacro]:处理文件 "   +  fullName); 
101 
102                      StringBuilder sb  =   new  StringBuilder(); 
103 
104                      Int32 from  =   0
105                       foreach  (Match item  in  mc) 
106                      { 
107                          sb.Append(text.Substring(from, item.Index  -  from)); 
108                          from  =  item.Index  +  item.Length; 
109                          sb.Append(Csmacro(parrentDirInfo, item.Value)); 
110                      } 
111 
112                      sb.Append(text.Substring(from, text.Length  -  from)); 
113 
114                      String newName  =  fullName.Substring( 0 , fullName.Length  -   3 +   " _Csmacro.cs "
115                       if  (File.Exists(newName)) 
116                      { 
117                          Console.WriteLine( " [Csmacro]:删除旧文件 "   +  newName); 
118                      } 
119                      File.WriteAllText(newName, sb.ToString()); 
120                      Console.WriteLine( " [Csmacro]:生成文件 "   +  newName); 
121                  } 
122              } 
123          } 
124 
125           static  String Csmacro(DirectoryInfo currentDirInfo, String text) 
126          { 
127              String outfilePath  =  text.Replace( " #region " , String.Empty).Replace( " #endregion " , String.Empty).Replace( " include " ,String.Empty).Replace( " \ "" ,String.Empty).Trim(); 
128               try  
129              { 
130                   if  (Path.IsPathRooted(outfilePath)  ==   false
131                  { 
132                      outfilePath  =  currentDirInfo.FullName  +   @" \ "   +  outfilePath; 
133                  } 
134                  FileInfo fi  =   new  FileInfo(outfilePath); 
135                   if  (fi.Exists  ==   false
136                  { 
137                      Console.WriteLine( " [Csmacro]:文件 "   +  fi.FullName  +   " 不存在. " ); 
138                       return  text; 
139                  } 
140                   else  
141                  { 
142                       return  GetMixinCode(File.ReadAllText(fi.FullName)); 
143                  } 
144              } 
145               catch  (Exception ex) 
146              { 
147                  Console.WriteLine( " [Csmacro]:出现错误( "   +  outfilePath  +   " )- "   +  ex.Message); 
148              } 
149               finally  
150              { 
151              } 
152               return  text; 
153          } 
154 
155           static  String GetMixinCode(String txt) 
156          { 
157              Match m  =  mixinReg.Match(txt); 
158               if  (m.Success  ==   true
159              { 
160                   return  m.Value; 
161              } 
162               else   return  String.Empty; 
163          } 
164 
165           static   void  PrintHelp() 
166          { 
167              Console.WriteLine( " Csmacro [dir|filePath] " ); 
168          } 
169      } 
170  }

然后编译为 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代码如下:

ExpandedBlockStart.gif 代码
 1  public   class  ImageU8
 2  {
 3       public  Int32 Width {  get set ; }
 4       public  Int32 Height {  get set ; }
 5 
 6       public   unsafe  Byte *  Pointer;
 7       public   unsafe   void  SetValue(Int32 row, Int32 col, Byte value)
 8      {
 9          Pointer[row  *  Width  +  col]  =  value;
10      }
11  }

 在 ImageArgb32 中,我们也要写重复的代码:

 

ExpandedBlockStart.gif 代码
 1  public   class  ImageArgb32
 2  {
 3       public  Int32 Width {  get set ; }
 4       public  Int32 Height {  get set ; }
 5 
 6       public   unsafe  Argb32 *  Pointer;
 7       public   unsafe   void  SetValue(Int32 row, Int32 col, Argb32 value)
 8      {
 9          Pointer[row  *  Width  +  col]  =  value;
10      }
11  }

 

对于 Width和Height属性,我们可以建立基类来进行抽象和复用,然而,对于m_pointer和SetValue方法,如果放在基类中,则需要抹去类型信息,且变的十分丑陋。由于C#不支持泛型类型的指针,也无法提取为泛型代码。

使用 Csmacro.exe 预处理器,我们就可以很好的处理。

首先,建立一个模板文件 Image_Template.cs 

ExpandedBlockStart.gif 代码
 1  using  TPixel  =  System.Byte; 
 2 
 3  using  System; 
 4 
 5  namespace  XXX.Hidden 
 6 
 7       class  Image_Template 
 8      { 
 9           public  Int32 Width {  get set ; } 
10           public  Int32 Height {  get set ; } 
11 
12           #region  mixin 
13 
14           public   unsafe  TPixel *  Pointer; 
15           public   unsafe   void  SetValue(Int32 row, Int32 col, TPixel value) 
16          { 
17              Pointer[row  *  Width  +  col]  =  value; 
18          } 
19 
20           #endregion  
21      } 
22  }

然后建立一个基类 BaseImage,再从BaseImage派生ImageU8和ImageArgb32。两个派生类都是 partial 类:

ExpandedBlockStart.gif 代码
 1  using  System; 
 2  using  System.Collections.Generic; 
 3  using  System.Text; 
 4 
 5  namespace  XXX 
 6 
 7       public   class  BaseImage 
 8      { 
 9           public  Int32 Width {  get set ; } 
10           public  Int32 Height {  get set ; } 
11      } 
12 
13       public   partial   class  ImageU8 : BaseImage 
14      { 
15      } 
16 
17       public   partial   class  ImageArgb32 : BaseImage 
18      { 
19      } 
20  }

下面我们建立一个 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” 。将这个文件引入项目中,编译通过。这个文件内容是:

ExpandedBlockStart.gif 代码
 1  using  TPixel  =  System.Byte; 
 2 
 3  using  System; 
 4  namespace  XXX 
 5 
 6       public   partial   class  ImageU8 
 7      {
 8 
 9           public   unsafe  TPixel *  Pointer; 
10           public   unsafe   void  SetValue(Int32 row, Int32 col, TPixel value) 
11          { 
12              Pointer[row  *  Width  +  col]  =  value; 
13          } 
14 
15      } 
16  }

对于 ImageArgb32 类也可以进行类似操作。

 

从这个例子可以看出,使用 partial 关键字,能够让原代码、模板代码、ClassHelper代码三者共存。使用 using 关键字,可以分离出代码中变化的部分出来。

 

下面是我写的图像操作的一些模板代码:

(1)通过模板提供指针和索引器:

ExpandedBlockStart.gif 代码
 1  using  TPixel  =  System.Byte; 
 2  using  TCache  =  System.Int32; 
 3  using  TKernel  =  System.Int32; 
 4 
 5  using  System; 
 6  using  System.Collections.Generic; 
 7  using  System.Text; 
 8 
 9  namespace  Orc.SmartImage.Hidden 
10 
11       public   abstract   class  Image_Template : UnmanagedImage < TPixel >  
12      { 
13           private  Image_Template() 
14              :  base ( 1 , 1
15          { 
16               throw   new  NotImplementedException(); 
17          } 
18 
19           #region  mixin 
20 
21           public   unsafe  TPixel *  Start {  get  {  return  (TPixel * ) this .StartIntPtr; } } 
22 
23           public   unsafe  TPixel  this [ int  index] 
24          { 
25               get  
26              { 
27                   return  Start[index]; 
28              } 
29               set  
30              { 
31                  Start[index]  =  value; 
32              } 
33          } 
34 
35           public   unsafe  TPixel  this [ int  row,  int  col] 
36          { 
37               get  
38              { 
39                   return  Start[row  *   this .Width  +  col]; 
40              } 
41               set  
42              { 
43                  Start[row  *   this .Width  +  col]  =  value; 
44              } 
45          } 
46 
47           public   unsafe  TPixel *  Row(Int32 row) 
48          { 
49               if  (row  <   0   ||  row  >=   this .Height)  throw   new  ArgumentOutOfRangeException( " row " ); 
50               return  Start  +  row  *   this .Width; 
51          } 
52 
53           #endregion  
54      } 
55  }

 

(2)通过模板提供常用的操作和Lambda表达式支持

ExpandedBlockStart.gif 代码
  1  using  TPixel  =  System.Byte; 
  2  using  TCache  =  System.Int32; 
  3  using  TKernel  =  System.Int32; 
  4 
  5  using  System; 
  6  using  System.Collections.Generic; 
  7  using  System.Text; 
  8 
  9  namespace  Orc.SmartImage.Hidden 
 10 
 11       static   class  ImageClassHelper_Template 
 12      { 
 13           #region  mixin 
 14 
 15           public   unsafe   delegate   void  ActionOnPixel(TPixel *  p); 
 16           public   unsafe   delegate   void  ActionWithPosition(Int32 row, Int32 column, TPixel *  p); 
 17           public   unsafe   delegate  Boolean PredicateOnPixel(TPixel *  p); 
 18 
 19           public   unsafe   static   void  ForEach( this  UnmanagedImage < TPixel >  src, ActionOnPixel handler) 
 20          { 
 21              TPixel *  start  =  (TPixel * )src.StartIntPtr; 
 22              TPixel *  end  =  start  +  src.Length; 
 23               while  (start  !=  end) 
 24              { 
 25                  handler(start); 
 26                   ++ start; 
 27              } 
 28          } 
 29 
 30           public   unsafe   static   void  ForEach( this  UnmanagedImage < TPixel >  src, ActionWithPosition handler) 
 31          { 
 32              Int32 width  =  src.Width; 
 33              Int32 height  =  src.Height; 
 34 
 35              TPixel *  p  =  (TPixel * )src.StartIntPtr; 
 36               for  (Int32 r  =   0 ; r  <  height; r ++
 37              { 
 38                   for  (Int32 w  =   0 ; w  <  width; w ++
 39                  { 
 40                      handler(w, r, p); 
 41                      p ++
 42                  } 
 43              } 
 44          } 
 45 
 46           public   unsafe   static   void  ForEach( this  UnmanagedImage < TPixel >  src, TPixel *  start,  uint  length, ActionOnPixel handler) 
 47          { 
 48              TPixel *  end  =  start  +  src.Length; 
 49               while  (start  !=  end) 
 50              { 
 51                  handler(start); 
 52                   ++ start; 
 53              } 
 54          } 
 55 
 56           public   unsafe   static  Int32 Count( this  UnmanagedImage < TPixel >  src, PredicateOnPixel handler) 
 57          { 
 58              TPixel *  start  =  (TPixel * )src.StartIntPtr; 
 59              TPixel *  end  =  start  +  src.Length; 
 60              Int32 count  =   0
 61               while  (start  !=  end) 
 62              { 
 63                   if  (handler(start)  ==   true ) count ++
 64                   ++ start; 
 65              } 
 66               return  count; 
 67          } 
 68 
 69           public   unsafe   static  Int32 Count( this  UnmanagedImage < TPixel >  src, Predicate < TPixel >  handler) 
 70          { 
 71              TPixel *  start  =  (TPixel * )src.StartIntPtr; 
 72              TPixel *  end  =  start  +  src.Length; 
 73              Int32 count  =   0
 74               while  (start  !=  end) 
 75              { 
 76                   if  (handler( * start)  ==   true ) count ++
 77                   ++ start; 
 78              } 
 79               return  count; 
 80          } 
 81 
 82           public   unsafe   static  List < TPixel >  Where( this  UnmanagedImage < TPixel >  src, PredicateOnPixel handler) 
 83          { 
 84              List < TPixel >  list  =   new  List < TPixel > (); 
 85 
 86              TPixel *  start  =  (TPixel * )src.StartIntPtr; 
 87              TPixel *  end  =  start  +  src.Length; 
 88               while  (start  !=  end) 
 89              { 
 90                   if  (handler(start)  ==   true ) list.Add( * start); 
 91                   ++ start; 
 92              } 
 93 
 94               return  list; 
 95          } 
 96 
 97           public   unsafe   static  List < TPixel >  Where( this  UnmanagedImage < TPixel >  src, Predicate < TPixel >  handler) 
 98          { 
 99              List < TPixel >  list  =   new  List < TPixel > (); 
100 
101              TPixel *  start  =  (TPixel * )src.StartIntPtr; 
102              TPixel *  end  =  start  +  src.Length; 
103               while  (start  !=  end) 
104              { 
105                   if  (handler( * start)  ==   true ) list.Add( * start); 
106                   ++ start; 
107              } 
108 
109               return  list; 
110          } 
111 
112           ///   <summary>  
113           ///  查找模板。模板中值代表实际像素值。负数代表任何像素。返回查找得到的像素的左上端点的位置。 
114           ///   </summary>  
115           ///   <param name="template"></param>  
116           ///   <returns></returns>  
117           public   static   unsafe  List < System.Drawing.Point >  FindTemplate( this  UnmanagedImage < TPixel >  src,  int [,] template) 
118          { 
119              List < System.Drawing.Point >  finds  =   new  List < System.Drawing.Point > (); 
120               int  tHeight  =  template.GetUpperBound( 0 +   1
121               int  tWidth  =  template.GetUpperBound( 1 +   1
122               int  toWidth  =  src.Width  -  tWidth  +   1
123               int  toHeight  =  src.Height  -  tHeight  +   1
124               int  stride  =  src.Width; 
125              TPixel *  start  =  (TPixel * )src.SizeOfType; 
126               for  ( int  r  =   0 ; r  <  toHeight; r ++
127              { 
128                   for  ( int  c  =   0 ; c  <  toWidth; c ++
129                  { 
130                      TPixel *  srcStart  =  start  +  r  *  stride  +  c; 
131                       for  ( int  rr  =   0 ; rr  <  tHeight; rr ++
132                      { 
133                           for  ( int  cc  =   0 ; cc  <  tWidth; cc ++
134                          { 
135                               int  pattern  =  template[rr, cc]; 
136                               if  (pattern  >=   0   &&  srcStart[rr  *  stride  +  cc]  !=  pattern) 
137                              { 
138                                   goto  Next; 
139                              } 
140                          } 
141                      } 
142 
143                      finds.Add( new  System.Drawing.Point(c, r)); 
144 
145                  Next: 
146                       continue
147                  } 
148              } 
149 
150               return  finds; 
151          } 
152 
153           #endregion  
154      } 
155  }

配合lambda表达式,用起来很爽。在方法“FindTemplate”中,有这一句:

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

其中 srcStart[rr * stride + cc] 是 TPixel 不定类型,而 pattern 是 int 类型,两者之间需要进行比较,但是并不是所有的类型都提供和整数之间的 != 操作符。为此,我建立了个新的模板 TPixel_Template。

 

(3)通过模板提供 != 操作符 的定义

ExpandedBlockStart.gif 代码
 1  using  TPixel  =  System.Byte; 
 2  using  System; 
 3 
 4  namespace  Orc.SmartImage.Hidden 
 5 
 6       public   struct  TPixel_Template 
 7      { 
 8           /*  
 9          #region mixin 
10 
11          public static Boolean operator ==(TPixel lhs, int rhs) 
12          { 
13              throw new NotImplementedException(); 
14          } 
15 
16          public static Boolean operator !=(TPixel lhs, int rhs) 
17          { 
18              throw new NotImplementedException(); 
19          } 
20 
21          public static Boolean operator ==(TPixel lhs, double rhs) 
22          { 
23              throw new NotImplementedException(); 
24          } 
25 
26          public static Boolean operator !=(TPixel lhs, double rhs) 
27          { 
28              throw new NotImplementedException(); 
29          } 
30 
31          public static Boolean operator ==(TPixel lhs, float rhs) 
32          { 
33              throw new NotImplementedException(); 
34          } 
35 
36          public static Boolean operator !=(TPixel lhs, float rhs) 
37          { 
38              throw new NotImplementedException(); 
39          } 
40 
41          #endregion 
42 
43           */  
44      } 
45  }

这里,在 #region mixin  块被注释掉了,不注释掉编译器会报错。注释之后,不会影响程序预处理。

 

通过 ClassHelper类来使用模板:

ExpandedBlockStart.gif 代码
 1  using  System; 
 2  using  System.Collections.Generic; 
 3  using  System.Text; 
 4 
 5  namespace  Orc.SmartImage 
 6 
 7       using  TPixel  =  Argb32; 
 8       using  TCache  =  System.Int32; 
 9       using  TKernel  =  System.Int32; 
10 
11       public   static   partial   class  ImageArgb32ClassHelper 
12      { 
13           #region  include "ImageClassHelper_Template.cs" 
14           #endregion  
15      } 
16 
17       public   partial   class  ImageArgb32 
18      { 
19           #region  include "Image_Template.cs" 
20           #endregion  
21      } 
22 
23       public   partial   struct  Argb32 
24      { 
25           #region  include "TPixel_Template.cs" 
26           #endregion  
27      } 
28 

 

由于 Argb32 未提供和 int 之间的比较,因此,在这里 #region include "TPixel_Template.cs"。而Byte可以与int比较,因此,在ImageU8中,就不需要#region include "TPixel_Template.cs": 

ExpandedBlockStart.gif 代码
 3  using  System; 
 4  using  System.Collections.Generic; 
 5  using  System.Text; 
 6 
 7  namespace  Orc.SmartImage 
 8 
 9       using  TPixel  =  System.Byte; 
10       using  TCache  =  System.Int32; 
11       using  TKernel  =  System.Int32; 
12 
13       public   static   partial   class  ImageU8ClassHelper 
14      { 
15           #region  include "ImageClassHelper_Template.cs" 
16           #endregion  
17      } 
18 
19       public   partial   class  ImageU8 
20      { 
21           #region  include "Image_Template.cs" 
22           #endregion  
23      } 
24  }

 

是不是很有意思呢?强大的指针,结合C#强大的语法和快速编译,至少在图像处理这一块是很好用的。

 

转载于:https://www.cnblogs.com/xiaotie/archive/2010/07/19/1780440.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值