[摘要]在GameTest里,我们分别创建了一个game和一个监视game的referee,然后,然后我们改变game的Score去看看referee对此有何反应。在这个系统里,game没有referee的任何知识,任何类都可以监听并对game的score变化产生反应。关键字event隐藏了除了+=和-=...
在GameTest里,我们分别创建了一个game和一个监视game的referee,然后,然后我们改变game的Score去看看referee对此有何反应。在这个系统里,game没有referee的任何知识,任何类都可以监听并对game的score变化产生反应。关键字event隐藏了除了+=和-=之外的所有委托方法。这两个操作符允许你添加(或移去)处理该事件的多个事件处理器。
【译注:我们以下例说明后面这句话的意思:
public class Game
{
public event ScoreChangeEventHandler ScoreChange;
protected void OnScoreChange()
{
if (ScoreChange != null) ScoreChange(30, ref true);//在类内,可以这么使用
}
,但在这个类外,ScoreChange就只能出现在运算符+=和-=的左边】
你可能首先会在图形用户界面框架里遇到这个系统。game好比是用户界面的某个控件,它根据用户输入触发事件,而referee则类似于一个窗体,它负责处理该事件。
【作者注:委托第一次被微软Visual J++引入也是Anders Hejlsberg设计的,同时它也是造成Sun和微软在技术和法律方面争端的起因之一。James Gosling,Java的设计者,对Anders Hejlsberg曾有过一个故作谦虚听起来也颇为幽默的评论,说他因为和Delphi藕断丝连的感情应该叫他“方法指针先生”。在研究Sun对委托的争执后,我觉得称呼Gosling为“一切都是一个类先生”好像公平些J 过去的这几年里,在编程界,“做努力模拟现实的抽象”已经被很多人代之以“现实是面向对象的,所以,我们应该用面向对象的抽象来模拟它”。
Sun和微软关于委托的争论可以在这儿看到:
http://www.Javasoft.com/docs/white/delegates.html http://msdn.microsoft.com/visualj/technical/articles/delegates/truth.asp 】
6.枚举 枚举使你能够指定一组对象,例如:
声明:
public enum Direction {North, East, West, South};
使用:
Direction wall = Direction.North;
这真是个优雅的概念,这也是C#为什么会决定保留它们的原因,但是,为什么Java却选择了抛弃?在Java中,你不得不这么做:
声明:
public class Direction
{
public final static int NORTH = 1;
public final static int EAST = 2;
public final static int WEST = 3;
public final static int SOUTH = 4;
}
使用:
int wall = Direction.NORTH;
看起来好像Java版的更富有表达力,但事实并非如此。它不是类型安全的,你可能一不小心会把任何int型的值赋给wall而编译器不会发出任何抱怨【译注:你显然不可以这么写:Direction wall = Direction.NORTH;】。坦白地说,在我的Java编程经历里,我从未因为该处非类型安全而花费太多的时间写一些额外的东西来捕捉错误。但是,能拥有枚举是一件快事。C#带给你的一个惊喜是—当你调试程序时,如果你在使用枚举变量的地方设置断点,调试器将自动译解direction并给你一个可读的信息,而不是一个你自己不得不译解的数值:
声明:
public enum Direction {North=1, East=2, West=4, South=8};
使用:
Direction direction = Direction.North Direction.West;
if ((direction & Direction.North) != 0)
//....
如果你在if语句上设置断点,你将得到一个你可读的direction而不是数值5。
【译注:这个例子改一下,会更有助于理解:
声明:
public enum Direction {North=1, East=2, West=4, South=8, Middle = 5/*注意此处代码*/};
使用:
Direction direction = Direction.North Direction.West;
if ((direction & Direction.North) != 0)
//....
如果你在if语句上设置断点,你将得到一个你可读的direction(即Middle)而不是数值5】
【作者注:枚举被Java抛弃的原因极有可能是因为它可以用类代替。正如我上面提到的,单单用类我们不能够象用别的概念一样更好地表达某个特性。Java的“如果它可以用类处理,那就不引入一个新的结构”的哲学的优点何在?看起来最大的优点是简单—较短的学习曲线,并且无需程序员去考虑做同一件事的多种方式。实际上,Java语言在很多方面都以简化为目标来改进C++,比如不用指针,不用头文件,以及单根对象层次等。所有这些简化的共性是它们实际上使得编程—唔—简单了,可是,没有我们刚才提到的枚举、属性和事件等等,反而使你的代码更加复杂了】
7.集合和foreach语句 C#提供一个for循环的捷径,而且它还促进了集合类更为一致:
在Java或C++中:
1. while (! collection.isEmpty())
{
Object o = collection.get();
collection.next()
//...
2. for (int i = 0; i < array.length; i++)
//...
在 C#中:
1.foreach (object o in collection)
//...
2.foreach (int i in array)
//...
C#的for循环将工作于集合对象上(数组实现一个集合)。集合对象有一个GetEnumerator()方法,该方法返回一个Enumerator对象。Enumerator对象有一个MoveNext()方法和一个Current属性。
8.结构 把C#的结构视为使语言的类型系统更为优雅而不仅是一种“如果你需要的话可以利用之写出真正有效率的代码”的概念更好些。
在C++中,结构和类(对象)都可分配在栈或堆上。在C#中,结构永远创建在栈上,类(对象)则永远创建在堆上。使用结构实际上可以生成更有效率的代码:
public struct Vector
{
public float direction;
public int magnitude;
}
Vector[] vectors = new Vector [1000];
这将把1000个Vector分配在一块空间上,这比我们把Vector声明为类并使用for循环去实例化1000个独立的Vector来得有效率得多。【译注:因怀疑原文有误,此处故意漏译一句,但不应影响你对这节内容的理解】:
int[] ints = new ints[1000];//【译注:此处代码有误,应为int[] ints = new int[1000];】
C#完全允许你扩展内建在语言中的基本类型集。实际上,C#所有的基本类型都以结构方式实现的。int型只不过是System.Int32结构的别名,long型不过是System.Int64结构的别名等等。这些基本类型当然可被编译器特别处理,但是语言本身并无区别【译注:意思是语言自身对处理所有类型提供了一致的方法】。在下一节中,我们可看到C#是如何做到这一点的。
9.类型一致 大多数语言都有基本类型(int、long等等)。高级类型最终是由基本类型构成的。能以同样的方式处理基本类型和高级类型通常来说是有用处的。例如,如果集合可以象包容sting那样包容int是有用的。为此,Smalltalk通过牺牲些许效率象处理string或Form一样来处理int和long。Java试图避免这个效率损失,它象C和C++那样处理基本类型,但又为每一个基本类型提供了相应的包装类—int包装为Integer,double包装为Double。C++模板参数可接受任何类型,只要该类型提供了模板定义的操作的实现。
【译注:在Java中,你可以这么写:
int i = 1;
double d = 1.1;
Integer iObj = new Integer(1);
Double dObj = new Double(1.1);
以下写法是错误的:
int I = new int(1);
Integer iObj = 1;
】
C#对该问题提供了一个不同的解决方案。在上一节里,我介绍了C#中的结构,指出基本类型不过是结构的一个别名而已。既然结构拥有所有对象类型拥有的方法,那代码就可以这么写:
int i = 5;
System.Console.WriteLine (i.ToString());
如果我们想象使用一个对象那样使用一个结构,C#将为你装箱该结构为对象,当你再次需要使用结构时,可以通过拆箱实现:
Stack stack = new Stack ();
stack.Push (i); // 装箱
int j = (int) stack.Pop(); //拆箱
拆箱不仅是类型转换的需要,它也是一个无缝处理结构和类之间关系的方式。你要清楚装箱是做了创建包装类的工作,尽管CLR可以为被装箱的对象提供附加的优化。
【译注:可以这么认为,在C#中,对于任何值(结构)类型,都存在如下的包装类:
class T_Box //T代表任何值类型
{
T Value;
T_Box(T t){Value = t;}
}
当装箱时,比如:
int n = 1;
object box = n;
概念上相当于:
int n = 1;
object box = new int_Box(i);
当拆箱时,比如:
object box = 1;
int n = (int)box;
概念上相当于:
object box = new int_Box(1);
int n = ((int_Box)box).Value;】
【作者注:C#的设计者在设计过程中应该考虑过模板。我怀疑未采用模板有两个原因:第一个是混乱,模板可能很难和面向对象的特性融合在一起,它为程序员的带来了太多的(混乱)设计可能性,而且它很难和反射一起工作;第二点是,如果.NET库(例如集合类)没有使用模板的话,模板将不会太有用。不过,果真.NET类使用了它们,那将有20多种使用.NET类的语言不得不也要能和模板一起工作,这在技术上是非常难以实现的。
注意到模板(泛型)已经被Java社团考虑纳入Java语言规范之中是一件有意思的事。或许每个公司都会各唱各的调—Sun说“.NET患了最小公分母综合症”,而微软则说“Java不支持多语言”。
(8月10日致歉)看了一个对Anders Hejlsberg的专访后(windows.oreilly.com/news/hejlsberg_0800.html" target=_blank>http://windows.oreilly.com/news/hejlsberg_0800.html),感觉似乎模板已浮出地平线,但第一版没有,正因我们上面提到的种种困难。看到IL规范是如此写法使得IL码可以展现模板(用一个非破坏的方式以让反射可以很好的工作)而字节码则不可以是一件很有趣的事。在此,我还给出了一个关于Java社团考虑要加入泛型的链接:http://jcp.org/jsr/detail/014.jsp 】
【译注:此处是上文提到的对Anders Hejlsberg采访的中文版链接:http://www.csdn.net/develop/article/11/11580.shtm。另外,如欲了解更多关于泛型编程知识,请参见此处链接:http://www.csdn.net/develop/article/11/11440
……