Head First Design Patterns


最近经一个同事介绍,在读《Head First Design Patterns》这本书,感觉其风格很是有趣,本来想写篇介绍的,但Google了一下,发现这位老兄的介绍非常全面,那我就偷懒了吧。

这本书里的编码用的是 Java,Mark McFadden 把它们转换成了C# [来源:Darrell Norton [MVP]]。

在用Google查询时,发现Sahil Malik [MVP C#] 用 A** first design pattern 为题在贬Oracle的高级.NET Web Service 例子,甚是好笑,  。回复中有个连接,更是好玩,叫 The Daily WTF,上面登的都是有问题或风格很差的编码,可以跟 Web Pages That Suck 媲美了

也做个比较(2)

对于

6。 整数交换

Java: 
        int x = 1984;
        int y = 2001;
        x ^= y ^= x ^= y;
        System.out.println(“x = ” + x + “; y = ” + y);

C#:
        int x = 1984;
        int y = 2001;
        x ^= y ^= x ^= y;
        System.Console.WriteLine(“x = ” + x + “; y = ” + y);

输出结果一样

Java x = 0; y = 1984
C# x = 0; y = 1984

fancyf 在上个post的回复中建议这个是Bug,其实不然,因为根据Specifications,

x ^= y ^= x ^= y;

其运算是这么做的(起码当前版本是如此):

“…The order in which operands in an expression are evaluated, is left to right. [Example: For example, in F(i) + G(i++) * H(i), method F is called using the old value of i, then method G is called with the old value of i, and, finally, method H is called with the new value of i. This is separate from and unrelated to operator precedence. end example]…” (见C# Language Specification, 14.2;至于Java中的表达式运算次序,参考 Java Language Specification, 15.7)

^=处,即对于 a ^= expr ,做运算时,a = a ^ expr,右边的a用的是运算expr的值,这样

y ^= x ^= y;  ==> x=1984^2001 =>17, y = 2001 ^ 17 => 1984
x ^= y ^= x ^= y; ==> x = 1984 ^ 1984 => 0

让我们再来看一下对应下面C#编码的IL码,观察stack的变化

public class CleverSwap {
    public static void Main(string[] args) {
        int x = 1984;
        int y = 2001;
        x ^= y ^= x ^= y;
        System.Console.WriteLine(“x={0}, y={1}”,x,y);
    }
}

.method public hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       49 (0x31)
  .maxstack  4
  .locals init (int32 V_0,
           int32 V_1)
  IL_0000:  nop
  IL_0001:  ldc.i4     0x7c0  //stack: 0x7c0
  IL_0006:  stloc.0  //stack:  <==>x=0x7c0
  IL_0007:  ldc.i4     0x7d1 //stack: 0x7d1 
  IL_000c:  stloc.1  //stack:  <==>x=0x7c0,y=0x7d1
  IL_000d:  ldloc.0  //stack: 0x7c0 
  IL_000e:  ldloc.1  //stack: 0x7c0,0x7d1
  IL_000f:  ldloc.0  //stack: 0x7c0,0x7d1,0x7c0
  IL_0010:  ldloc.1  //stack: 0x7c0,0x7d1,0x7c0,0x7d1
  IL_0011:  xor   //stack: 0x7c0,0x7d1,0x11
  IL_0012:  dup   //stack: 0x7c0,0x7d1,0x11,0x11 
  IL_0013:  stloc.0  //stack: 0x7c0,0x7d1,0x11     <==>x=0x11,y=0x7d1
  IL_0014:  xor   //stack: 0x7c0,0x7c0     <==>x=0x11,y=0x7d1
  IL_0015:  dup   //stack: 0x7c0,0x7c0,0x7c0     <==>x=0x11,y=0x7d1
  IL_0016:  stloc.1  //stack: 0x7c0,0x7c0     <==>x=0x11,y=0x7c0
  IL_0017:  xor   //stack: 0x00     <==>x=0x11,y=0x7c0
  IL_0018:  stloc.0  //stack:  <==>x=0x00,y=0x7c0
  IL_0019:  ldstr      “x={0}, y={1}”
  IL_001e:  ldloc.0
  IL_001f:  box        [mscorlib]System.Int32
  IL_0024:  ldloc.1
  IL_0025:  box        [mscorlib]System.Int32
  IL_002a:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object,
                                                                object)
  IL_002f:  nop
  IL_0030:  ret
} // end of method CleverSwap::Main

至于C/C++,根据Joshua Bloch/Neal Gafter

“…… In C and C++, the order of expression evaluation is not specified. When compiling the expression x ^= expr, many C and C++ compilers sample the value of x after evaluating expr, which makes the idiom work. Although it may work, it runs afoul of the C/C++ rule that you must not modify a variable repeatedly between successive sequence points [ISO-C]. Therefore, the behavior of this idiom is undefined even in C and C++…..”

而Java/C#则明确规定了表达式中operand的evaluation次序,所以结果不同,起码当前版本是如此

也做个比较

Joshua Bloch 是Java语言组的设计师,去年离开Sun加盟Google,他的《Effective Java》一书在Java界影响很大。最近与Google的同事Neal Gafter (也是前Sun雇员) 合写了《Java Puzzlers: Traps, Pitfalls, and Corner Cases》。该书的几个条目以及全部Source Code可以在www.javapuzzlers.com下载到

我不想介入Java与C#间的比较,但还是不禁想比较一下里面的例子在C#里的行为。

下面是第二章里的几个例子的比较,是在.NET 2.0.50215 和Java 1.5.0下做的,至于结果为什么不一样,建议参考2门语言的Specifications。

1。奇偶性 

Java:

    public static boolean isOdd(int i) {
        return i % 2 == 1;
    }

C#:
    public static bool isOdd(int i) {
        return i % 2 == 1;
    }

输出结果是一样的,这里涉及负整数的余数的问题,但输出结果也许跟你想的也许不一样

isOdd i=-2 i=-1 i=0 i=1 i=2
Java false false false true false
C# False False False True False

2。浮点数的减法

Java: System.out.println(2.00 – 1.10);

C#: System.Console.WriteLine(2.00 – 1.10);

输出结果不一样

Java 0.8999999999999999
C# 0.9

对C#这个结果有点怀疑,大概是格式化的原因,因为如果用ILDASM看的话,是这样的

  IL_0000:  ldc.r8     0.89999999999999991
  IL_0009:  call       void [mscorlib]System.Console::WriteLine(float64)

3。大整数除法

Java:
        final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
        final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;

        System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY);

C#:
        const long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
        const long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;

        System.Console.WriteLine(MICROS_PER_DAY / MILLIS_PER_DAY);

在C#里编译出错,使用unchecked后输出相同,但输出结果也许跟你想象的输出结果不一样

Java 5
C# 5

4。16位数的加法

Java: System.out.println(Long.toHexString(0x100000000L + 0xcafebabe));

C#:  System.Console.WriteLine(“{0:x}”, 0x100000000L + 0xcafebabe);

输出结果不一样

Java cafebabe
C# 1cafebabe

5。多重转换

Java: System.out.println((int) (char) (byte) -1);

C#: 

 unchecked
 {
         System.Console.WriteLine((int) (char) (byte) -1);
 }

输出结果不一样

Java 65535
C# 255

6。 整数交换

Java: 
        int x = 1984;
        int y = 2001;
        x ^= y ^= x ^= y;
        System.out.println(“x = ” + x + “; y = ” + y);

C#:
        int x = 1984;
        int y = 2001;
        x ^= y ^= x ^= y;
        System.Console.WriteLine(“x = ” + x + “; y = ” + y);

输出结果一样

Java x = 0; y = 1984
C# x = 0; y = 1984

7。条件运算符

Java:
        char x = ‘X’;
        int i = 0;
        System.out.print(true  ? x : 0);
        System.out.print(false ? i : x);

C#:
        char x = ‘X’;
        int i = 0;
        System.Console.Write(true  ? x : 0);
        System.Console.Write(false ? i : x);

输出结果不一样

Java X88
C# 8888