技术开发 频道

详谈C# 语言中的 static 关键字

    【IT168 技术文档】ZipInteger 结构中有以下构造函数:

  /// <summary>
  
/// 使用字节数组中的值初始 ZipInteger 结构的新实例
  
/// 注意:本构造函数会破坏传入的 bits 参数的值。
  
/// </summary>
  
/// <param name="bits">顺序为 big-endian 的字节值的数组</param>
  
public ZipInteger(byte[] bits)
  {
    
if (bits == null) throw new ArgumentNullException("bits");
    
if (bits.Length < 1 || bits.Length > 9) throw new ArgumentException("Invalid length", "bits");
    
byte[] mask = { 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0x00 };
    
if (bits.Length > 1 && bits.Length < 9) bits[0] &= mask[bits.Length - 1];
    
Array.Reverse(bits);
    
Array.Resize(ref bits, 8);
    
if (!BitConverter.IsLittleEndian) Array.Reverse(bits);
    data
= Decode(BitConverter.ToInt64(bits, 0));
  }

 

  注意上述源程序第 10 行的 mask 数组其实只需要初始化一次就行了,而不需要每次调用该构造函数时都进行初始化。也就是说,应该将 mask 变量声明为 static 的,如下所示:

static readonly byte[] mask = { 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0x00 };

 

  愿望是美好的,现实是残酷的,加上 static 关键字后,再编译时 C# 编译器会报告以下错误:

error CS0106: 修饰符“static”对该项无效

 

  也就是说,C# 语言不允许使用 static 修饰符来声明方法内部的变量。但是在 C/C++ 语言中是允许这么做的。

  如果把该构造函数内部的 mask 变量提升到构造函数外部,成为 ZipInteger 结构的字段成员,就可以声明为 static 的。但是这样一样,读这段代码的人就不容易弄清楚 mask 字段只在这个构造函数内部使用,成为代码的“坏味道”,一点也不优雅了。

  好了,让我们写一小段程序来测试一下加上 static 后对运行效率的影响:

  using System;
  using System.IO;
  using System.Diagnostics;
  using Skyiv.Numerics;
  
  namespace Skyiv.Tester
  {
    sealed class StaticTester
    {
      static void Main()
      {
        try
        {
          
new StaticTester().Run(100000000);
        }
        catch (Exception ex)
        {
          Console.WriteLine(ex);
        }
      }
  
      void Run(
int count)
      {
        Console.WriteLine(
"  OS Version: " + Environment.OSVersion);
        Console.WriteLine(
" CLR Version: " + Environment.Version);
        using (var reader
= new MemoryStream(GetBytes(count)))
        {
          var watch
= Stopwatch.StartNew();
          var n
= 0;
          
for (var i = 0; i < 10; i++)
          {
            reader.Seek(
0, SeekOrigin.Begin);
            
while (ZipInteger.Read(reader).HasValue) n++;
          }
          watch.Stop();
          Console.WriteLine(
"       Count: " + n.ToString("N0"));
         Console.WriteLine(
"Milliseconds: " + watch.ElapsedMilliseconds.ToString("N0"));
        }
      }
  
      
byte[] GetBytes(int count)
     {
        var bits
= new byte[count];
        var rand
= new Random(123456);
        rand.NextBytes(bits);
        return bits;
      }
    }
}

 

  上述程序中第 44 行使用固定的种子初始化 Random 类的新实例,从而产生相同的随机数序列,以便用相同的测试用例来进行测试,使测试结果具有可比性。注意,在上述程序中如果 count 值和随机数的种子选取得不好,执行到第 33 行的 ZipInteger.Read 方法时是有可能抛出 EndOfStreamException 异常的。

  这个测试程序在 Windows Vista 操作系统的 .NET Framework 4 环境下的运行结果如下所示:

OS Version: Microsoft Windows NT 6.0.6002 Service Pack 2
CLR Version:
4.0.30319.1
       Count:
500,990,730
Milliseconds:
181,886

 

  将这个测试程序对 mask 变量是非静态的和静态的两种情况分别运行五次,得到的 Milliseconds 值如下表所示:

1
 

  经过简单的计算得知,静态情况下比非静态情况下的平均运行效率提高了 7.7%,还是很可观的。

  这个测试程序在 Ubuntu 10.10 操作系统的 mono 2.6.7 环境的运行结果如下所示:

1
 

  经过简单的计算得知,静态情况下比非静态情况下的运行效率提高了 11.6%,更是有了较大的改进。

  我们来看看这两种情况下 C# 编译器生成的 IL 代码,在非静态的情况下的该实例构造函数(.ctor)的 IL 代码:

001:  .method public hidebysig specialname rtspecialname
002:          instance void  .ctor(uint8[] bits) cil managed
003:  {
004:    // 代码大小       181 (0xb5)
005:    .maxstack  5
006:    .locals init (uint8[] V_0,
007:             bool V_1)
008:    IL_0000:  nop
009:    IL_0001:  ldarg.1
010:    IL_0002:  ldnull
011:    IL_0003:  ceq
012:    IL_0005:  ldc.i4.0
013:    IL_0006:  ceq
014:    IL_0008:  stloc.1
015:    IL_0009:  ldloc.1
016:    IL_000a:  brtrue.s   IL_0017
017:    IL_000c:  ldstr      "bits"
018:    IL_0011:  newobj     instance void [mscorlib]System.ArgumentNullException::.ctor(string)
019:    IL_0016:  throw
020:    IL_0017:  ldarg.1
021:    IL_0018:  ldlen
022:    IL_0019:  conv.i4
023:    IL_001a:  ldc.i4.1
024:    IL_001b:  blt.s      IL_0029
025:    IL_001d:  ldarg.1
026:    IL_001e:  ldlen
027:    IL_001f:  conv.i4
028:    IL_0020:  ldc.i4.s   9
029:    IL_0022:  cgt
030:    IL_0024:  ldc.i4.0
031:    IL_0025:  ceq
032:    IL_0027:  br.s       IL_002a
033:    IL_0029:  ldc.i4.0
034:    IL_002a:  stloc.1
035:    IL_002b:  ldloc.1
036:    IL_002c:  brtrue.s   IL_003e
037:    IL_002e:  ldstr      "Invalid length"
038:    IL_0033:  ldstr      "bits"
039:    IL_0038:  newobj     instance void [mscorlib]System.ArgumentException::.ctor(string,
040:                                                                                 string)
041:    IL_003d:  throw
042:    IL_003e:  ldc.i4.8
043:    IL_003f:  newarr     [mscorlib]System.Byte
044:    IL_0044:  dup
045:    IL_0045:  ldtoken    field int64 '<PrivateImplementationDetails>
046:                           {78063CDC-E5EE-4C6B-A62D-FD0F919F6111}'::'$$method0x6000006-1'
047:    IL_004a:  call       void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::
048:                           InitializeArray(class [mscorlib]System.Array,
049:                             valuetype [mscorlib]System.RuntimeFieldHandle)
050:    IL_004f:  stloc.0
051:    IL_0050:  ldarg.1
052:    IL_0051:  ldlen
053:    IL_0052:  conv.i4
054:    IL_0053:  ldc.i4.1
055:    IL_0054:  ble.s      IL_0062
056:    IL_0056:  ldarg.1
057:    IL_0057:  ldlen
058:    IL_0058:  conv.i4
059:    IL_0059:  ldc.i4.s   9
060:    IL_005b:  clt
061:    IL_005d:  ldc.i4.0
062:    IL_005e:  ceq
063:    IL_0060:  br.s       IL_0063
064:    IL_0062:  ldc.i4.1
065:    IL_0063:  stloc.1
066:    IL_0064:  ldloc.1
067:    IL_0065:  brtrue.s   IL_0082
068:    IL_0067:  ldarg.1
069:    IL_0068:  ldc.i4.0
070:    IL_0069:  ldelema    [mscorlib]System.Byte
071:    IL_006e:  dup
072:    IL_006f:  ldobj      [mscorlib]System.Byte
073:    IL_0074:  ldloc.0
074:    IL_0075:  ldarg.1
075:    IL_0076:  ldlen
076:    IL_0077:  conv.i4
077:    IL_0078:  ldc.i4.1
078:    IL_0079:  sub
079:    IL_007a:  ldelem.u1
080:    IL_007b:  and
081:    IL_007c:  conv.u1
082:    IL_007d:  stobj      [mscorlib]System.Byte
083:    IL_0082:  ldarg.1
084:    IL_0083:  call       void [mscorlib]System.Array::Reverse(class [mscorlib]System.Array)
085:    IL_0088:  nop
086:    IL_0089:  ldarga.s   bits
087:    IL_008b:  ldc.i4.8
088:    IL_008c:  call       void [mscorlib]System.Array::Resize<uint8>(!!0[]&,
089:                                                                    int32)
090:    IL_0091:  nop
091:    IL_0092:  ldsfld     bool [mscorlib]System.BitConverter::IsLittleEndian
092:    IL_0097:  stloc.1
093:    IL_0098:  ldloc.1
094:    IL_0099:  brtrue.s   IL_00a2
095:    IL_009b:  ldarg.1
096:    IL_009c:  call       void [mscorlib]System.Array::Reverse(class [mscorlib]System.Array)
097:    IL_00a1:  nop
098:    IL_00a2:  ldarg.0
099:    IL_00a3:  ldarg.1
100:    IL_00a4:  ldc.i4.0
101:    IL_00a5:  call       int64 [mscorlib]System.BitConverter::ToInt64(uint8[],
102:                                                                      int32)
103:    IL_00aa:  call       int64 Skyiv.Numerics.ZipInteger::Decode(int64)
104:    IL_00af:  stfld      int64 Skyiv.Numerics.ZipInteger::data
105:    IL_00b4:  ret
106:  } // end of method ZipInteger::.ctor

 

  上述 IL 代码的第 42 行到第 49 行对 mask 数组进行初始化,紧接着在第 50 行用“stloc.0”指令保存起来,然后在第 73 行用“ldloc.0”指令引用。

  再看看静态的情况下该实例构造函数(.ctor)的 IL 代码:

01:  .method public hidebysig specialname rtspecialname
02:          instance void  .ctor(uint8[] bits) cil managed
03:  {
04:    // 代码大小       167 (0xa7)
05:    .maxstack  5
06:    .locals init (bool V_0)
07:    IL_0000:  nop
08:    IL_0001:  ldarg.1
09:    IL_0002:  ldnull
10:    IL_0003:  ceq
11:    IL_0005:  ldc.i4.0
12:    IL_0006:  ceq
13:    IL_0008:  stloc.0
14:    IL_0009:  ldloc.0
15:    IL_000a:  brtrue.s   IL_0017
16:    IL_000c:  ldstr      "bits"
17:    IL_0011:  newobj     instance void [mscorlib]System.ArgumentNullException::.ctor(string)
18:    IL_0016:  throw
19:    IL_0017:  ldarg.1
20:    IL_0018:  ldlen
21:    IL_0019:  conv.i4
22:    IL_001a:  ldc.i4.1
23:    IL_001b:  blt.s      IL_0029
24:    IL_001d:  ldarg.1
25:    IL_001e:  ldlen
26:    IL_001f:  conv.i4
27:    IL_0020:  ldc.i4.s   9
28:    IL_0022:  cgt
29:    IL_0024:  ldc.i4.0
30:    IL_0025:  ceq
31:    IL_0027:  br.s       IL_002a
32:    IL_0029:  ldc.i4.0
33:    IL_002a:  stloc.0
34:    IL_002b:  ldloc.0
35:    IL_002c:  brtrue.s   IL_003e
36:    IL_002e:  ldstr      "Invalid length"
37:    IL_0033:  ldstr      "bits"
38:    IL_0038:  newobj     instance void [mscorlib]System.ArgumentException::.ctor(string,
39:                                                                                 string)
40:    IL_003d:  throw
41:    IL_003e:  ldarg.1
42:    IL_003f:  ldlen
43:    IL_0040:  conv.i4
44:    IL_0041:  ldc.i4.1
45:    IL_0042:  ble.s      IL_0050
46:    IL_0044:  ldarg.1
47:    IL_0045:  ldlen
48:    IL_0046:  conv.i4
49:    IL_0047:  ldc.i4.s   9
50:    IL_0049:  clt
51:    IL_004b:  ldc.i4.0
52:    IL_004c:  ceq
53:    IL_004e:  br.s       IL_0051
54:    IL_0050:  ldc.i4.1
55:    IL_0051:  stloc.0
56:    IL_0052:  ldloc.0
57:    IL_0053:  brtrue.s   IL_0074
58:    IL_0055:  ldarg.1
59:    IL_0056:  ldc.i4.0
60:    IL_0057:  ldelema    [mscorlib]System.Byte
61:    IL_005c:  dup
62:    IL_005d:  ldobj      [mscorlib]System.Byte
63:    IL_0062:  ldsfld     uint8[] Skyiv.Numerics.ZipInteger::mask
64:    IL_0067:  ldarg.1
65:    IL_0068:  ldlen
66:    IL_0069:  conv.i4
67:    IL_006a:  ldc.i4.1
68:    IL_006b:  sub
69:    IL_006c:  ldelem.u1
70:    IL_006d:  and
71:    IL_006e:  conv.u1
72:    IL_006f:  stobj      [mscorlib]System.Byte
73:    IL_0074:  ldarg.1
74:    IL_0075:  call       void [mscorlib]System.Array::Reverse(class [mscorlib]System.Array)
75:    IL_007a:  nop
76:    IL_007b:  ldarga.s   bits
77:    IL_007d:  ldc.i4.8
78:    IL_007e:  call       void [mscorlib]System.Array::Resize<uint8>(!!0[]&,
79:                                                                    int32)
80:    IL_0083:  nop
81:    IL_0084:  ldsfld     bool [mscorlib]System.BitConverter::IsLittleEndian
82:    IL_0089:  stloc.0
83:    IL_008a:  ldloc.0
84:    IL_008b:  brtrue.s   IL_0094
85:    IL_008d:  ldarg.1
86:    IL_008e:  call       void [mscorlib]System.Array::Reverse(class [mscorlib]System.Array)
87:    IL_0093:  nop
88:    IL_0094:  ldarg.0
89:    IL_0095:  ldarg.1
90:    IL_0096:  ldc.i4.0
91:    IL_0097:  call       int64 [mscorlib]System.BitConverter::ToInt64(uint8[],
92:                                                                      int32)
93:    IL_009c:  call       int64 Skyiv.Numerics.ZipInteger::Decode(int64)
94:    IL_00a1:  stfld      int64 Skyiv.Numerics.ZipInteger::data
95:    IL_00a6:  ret
96:  } // end of method ZipInteger::.ctor

 

  上述 IL 代码中就没有对 mask 数组进行初始化,而是在第 63 行用“ldsfld”指令直接引用 ZipInteger 结构的静态字段,即已经初始化好了的 mask 数组。这个 mask 数组是在 ZipInteger 结构的静态构造函数(.cctor)中初始化的,如下所示:

01:  .method private hidebysig specialname rtspecialname static
02:          void  .cctor() cil managed
03:  {
04:    // 代码大小       61 (0x3d)
05:    .maxstack  8
06:    IL_0000:  ldc.i8     0x8000000000000000
07:    IL_0009:  call       valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::op_Implicit(int64)
08:    IL_000e:  stsfld     valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::MinValue
09:    IL_0013:  ldc.i8     0x7fffffffffffffff
10:    IL_001c:  call       valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::op_Implicit(int64)
11:    IL_0021:  stsfld     valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::MaxValue
12:    IL_0026:  ldc.i4.8
13:    IL_0027:  newarr     [mscorlib]System.Byte
14:    IL_002c:  dup
15:    IL_002d:  ldtoken    field int64 '<PrivateImplementationDetails>
16:                           {CDCDEB38-994E-4730-8D14-55B1DBDE4B1B}'::'$$method0x6000016-1'
17:    IL_0032:  call       void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::
18:                           InitializeArray(class [mscorlib]System.Array,
19:                             valuetype [mscorlib]System.RuntimeFieldHandle)
20:    IL_0037:  stsfld     uint8[] Skyiv.Numerics.ZipInteger::mask
21:    IL_003c:  ret
22:  } // end of method ZipInteger::.cctor

 

  可以看出,在上述 IL 代码中的第 12 到第 20 行对静态的 mask 数组进行了初始化。

  相对应的非静态情况下的 ZipInteger 结构的静态构造函数(.cctor)就没有对 mask 数组进行初始化的语句:

01:  .method private hidebysig specialname rtspecialname static
02:          void  .cctor() cil managed
03:  {
04:    // 代码大小       39 (0x27)
05:    .maxstack  8
06:    IL_0000:  ldc.i8     0x8000000000000000
07:    IL_0009:  call       valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::op_Implicit(int64)
08:    IL_000e:  stsfld     valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::MinValue
09:    IL_0013:  ldc.i8     0x7fffffffffffffff
10:    IL_001c:  call       valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::op_Implicit(int64)
11:    IL_0021:  stsfld     valuetype Skyiv.Numerics.ZipInteger Skyiv.Numerics.ZipInteger::MaxValue
12:    IL_0026:  ret
13:  } // end of method ZipInteger::.cctor

 

  我们知道,静态构造函数总共只执行一次,而实例构造函数在每次实例化时都要执行一行。也就是说,在我们的测试用例中,mask 数组在静态的情况下只要初始化一次,而在非静态的情况下要初始化 500,990,730 次。所以运行效率出现这么大的差别就很好理解了。

  除此之外,将方法内部的变量声明为 static 的,还可以起着方法内置状态的作用,这也是很有好处的。具体例子请参见“隐约有歌”大大的博文“技术面试题:f(f(n)) == -n”。

  综上所述,我强烈要求在 C# 5.0 中增加支持将方法内部的变量声明为 static 的。这在 C/C++ 语言中早就已经支持了,我想这应该不难实现,因为 Microsoft 的 C/C++ 也支持这一特性。

0
相关文章