【翻譯】C#編程語言和JAVA編程語言的比較(下)


原文地址:http://www.25hoursaday.com/CsharpVsJava.html

 

6、集合

 

許多有名的編程語言都會包含一個集合框架,框架一般由各種用於保存數據的數據結構和配套的操作對象的算法構成。集合框架的優勢是讓開發者可以不用寫數據結構和排序算法,把精力放在真正的業務邏輯上。還有就是可以讓不同的項目保持一致性,新的開發者也少了很多學習曲線。

C#集合框架大多位於System.Collections和System.Collections.Generic命名空間。Systems.Collections命名空間包含了表示抽象數據類型的接口和抽象類,比如IList, IEnumerable, IDictionary, ICollection, 和 CollectionBase,只要數據結構從抽象數據類型派生,開發者無需關心其內部如何實現。System.Collections命名空間還包含了很多數據結構的具體實現,包括ArrayList, Stack, Queue, HashTable 和SortedList。這四種結構都提供了同步包裝,可以在多線程程序中線程安全。System.Collections.Generic命名空間實現了System.Collections空間中主要數據結構的泛型版本,包括泛型的List<T>, Stack<T>,Queue<T>, Dictionary<K,T> 和SortedDictionary<K,T>類。

 

Java集合框架則在java.util包中包含許多類和接口。java.util包也同樣支持泛型,並沒有使用新的命名空間來放置泛型類型。Java集合框架和C#相似,不過可以認為是C#集合框架的超集,因為它實現了一些其它特性。

注意:后面作者的話我就不翻譯了,因為他提到的Java中有,而C#中沒有的集合在.NET 3.5和.NET 4.0中都已經支持

 

7、goto

 

和Java不同,C#包含的goto可以用來在代碼中進行跳轉,盡管goto被嘲笑,但是有的時候還是可以使用goto來減少代碼重復並增加可讀性。goto語句第二個用處是可以重用異常,因為異常拋出是無法跨越方法邊界的。

注意:在C#中goto無法跳轉到語句塊

 

C# Code
using System; 
using System.Net.Sockets; 

class GotoSample{


    public static void Main(string[] args){
    
    int num_tries = 0;
    
       retry: 

    try{             

        num_tries++;    
        Console.WriteLine("Attempting to connect to network. Number of tries =" + num_tries);

        //Attempt to connect to a network times out 
        //or some some other network connection error that 
        //can be recovered from
       
        throw new SocketException(); 

    }catch(SocketException){

        if(num_tries < 5)
        goto retry;                 
    }       
   
   }/* Main(string[]) */ 

}//GotoSample

8、虛方法

 

面向對象的一個主要特點就是多態。多態可以讓我們和繼承體系中的泛化類型而不是實際類型打交道。也就是一般在基類中實現的方法在派生類中重寫,我們即使持有基類類型的引用,但是其指向派生類型,在運行時而不是編譯時動態綁定的方法叫做虛方法。在Java中所有的方法都是虛方法,而在C#中,必須通過virtual關鍵字顯式指定方法為虛方法,默認不是虛方法。同樣可以用override關鍵字在子類中重寫虛方法或使用new關鍵字隱藏基類方法。在Java中可以通過標記方法為final關鍵字讓方法不能被派生類重寫,在C#中可以不標記virtual來實現。主要區別是,如果派生類也實現了相同方法,C#的可以通過把引用指向基類調用到基類的方法,而在Java中如果基類實現了final方法,派生類不允許再有同名的方法。

 

C# Code
using System; 

public class Parent{

    public void DoStuff(string str){
    Console.WriteLine("In Parent.DoStuff: " + str); 
    }

}

public class Child: Parent{

     public void DoStuff(int n){
    Console.WriteLine("In Child.DoStuff: " + n);    
    }

     public void DoStuff(string str){
    Console.WriteLine("In Child.DoStuff: " + str);  
    }
}


public class VirtualTest{

    public static void Main(string[] args){

    Child ch = new Child(); 

    ch.DoStuff(100); 
    ch.DoStuff("Test"); 

    ((Parent) ch).DoStuff("Second Test"); 
    }

}//VirtualTest

OUTPUT:
In Child.DoStuff: 100
In Child.DoStuff: Test
In Parent.DoStuff: Second Test
Java Code
class Parent{

    public void DoStuff(String str){
    System.out.println("In Parent.DoStuff: " + str);    
    }

}

class Child extends Parent{

     public void DoStuff(int n){
    System.out.println("In Child.DoStuff: " + n);   
    }

     public void DoStuff(String str){
    System.out.println("In Child.DoStuff: " + str); 
    }
}


public class VirtualTest{

    public static void main(String[] args){

    Child ch = new Child(); 

    ch.DoStuff(100); 
    ch.DoStuff("Test"); 

    ((Parent) ch).DoStuff("Second Test"); 
    }

}//VirtualTest

OUTPUT:
In Child.DoStuff: 100
In Child.DoStuff: Test
In Child.DoStuff: Second Test

C#的例子可以通過把基類DoStuff(string) 方法標記為virtual子類方法標記為override關鍵字來實現和Java相同輸出:

# Code

using System; 

public class Parent{

    public virtual void DoStuff(string str){
    Console.WriteLine("In Parent.DoStuff: " + str); 
    }

}

public class Child: Parent{

     public void DoStuff(int n){
    Console.WriteLine("In Child.DoStuff: " + n);    
    }

     public override void DoStuff(string str){
    Console.WriteLine("In Child.DoStuff: " + str);  
    }
}


public class VirtualTest{

    public static void Main(string[] args){

    Child ch = new Child(); 

    ch.DoStuff(100); 
    ch.DoStuff("Test"); 

    ((Parent) ch).DoStuff("Second Test"); 
    }

}//VirtualTest

如上例子可以修改子類的DoStuff(string)方法為如下以得到之前的結果:

public new void DoStuff(string str)

9、文件IO

 

兩種語言都通過Stream類支持IO操作,如下例子把input.txt的內容復制到output.txt中。

C# Code
using System;
using System.IO; 


public class FileIOTest {

    public static void Main(string[] args){

    FileStream inputFile  = new FileStream("input.txt", FileMode.Open);
    FileStream outputFile = new FileStream("output.txt", FileMode.Open);

        StreamReader sr     = new StreamReader(inputFile);
        StreamWriter sw     = new StreamWriter(outputFile);


    String str;

    while((str = sr.ReadLine())!= null)
        sw.Write(str);

        sr.Close();
        sw.Close();
    }

}//FileIOTest
Java Code
import java.io.*;

public class FileIO{

    public static void main(String[] args) throws IOException {

    File inputFile  = new File("input.txt");
    File outputFile = new File("output.txt");

        FileReader in     = new FileReader(inputFile);
    BufferedReader br = new BufferedReader(in);

        FileWriter out    = new FileWriter(outputFile);
    BufferedWriter bw = new BufferedWriter(out);

    String str;

    while((str = br.readLine())!= null)
        bw.write(str);

        br.close();
        bw.close();
    }

}//FileIOTest

10、對象序列化

 

對象持久化或叫序列化是通過諸如文件或網絡讀寫對象的能力。如果在使用程序的時候對象的狀態必須保存下來,那么對象持久化就很有用。有的時候以簡單文本形式保存數據不夠,以DBMS保存數據又勞師動眾了,那么可以使用序列化直接保存,還有的時候可以使用序列化來傳輸類型。C#中可序列化類型標記[Serializable]特性。如果C#的類的一些成員不需要在運行時序列化,可以標記[NonSerialized]特性。這些字段通常用於計算或是臨時的值,不需要保存下來。C#提供了兩種格式來序列化類,XML或二進制格式,前者對於人來說更可讀,后者更高效。當然我們也可以通過實現ISerializable接口實現自定義的序列化方式。

在Java中,對象序列化需要實現Serializable接口,而transient關鍵字用於標記不需要序列化的成員。默認情況下,Java支持序列化對象到二進制格式,但是提供了重寫標准序列化過程的方式。需要重寫默認序列化的對象需要實現如下方法簽名:

private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException;

private void writeObject(java.io.ObjectOutputStream stream) throws IOException

 

由於上面的方法是private的,使用readObject和writeObject來實現自定義序列化的話沒有要實現的接口,對於需要公開訪問的方法的類實現自定義序列化可以使用java.io.Externalizable接口,指定readExternal() 和writeExternal()。

C# Code

using System;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Soap;


[Serializable]
class SerializeTest{

    [NonSerialized]
    private int x; 

    private int y; 

    public SerializeTest(int a, int b){

    x = a; 
    y = b; 

    }

    public override String ToString(){

    return "{x=" + x + ", y=" + y + "}"; 

    }

    public static void Main(String[] args){

    SerializeTest st = new SerializeTest(66, 61); 
    Console.WriteLine("Before Binary Write := " + st);

    Console.WriteLine("\n Writing SerializeTest object to disk");
    Stream output  = File.Create("serialized.bin");
    BinaryFormatter bwrite = new BinaryFormatter(); 
    bwrite.Serialize(output, st); 
    output.Close(); 

    Console.WriteLine("\n Reading SerializeTest object from disk\n");
    Stream input  = File.OpenRead("serialized.bin");
    BinaryFormatter bread = new BinaryFormatter(); 
    SerializeTest fromdisk = (SerializeTest)bread.Deserialize(input); 
    input.Close(); 


    /* x will be 0 because it won't be read from disk since non-serialized */ 
    Console.WriteLine("After Binary Read := " + fromdisk);

    
    st = new SerializeTest(19, 99);  
    Console.WriteLine("\n\nBefore SOAP(XML) Serialization := " + st);

    Console.WriteLine("\n Writing SerializeTest object to disk");
    output  = File.Create("serialized.xml");
    SoapFormatter swrite = new SoapFormatter(); 
    swrite.Serialize(output, st); 
    output.Close(); 

    Console.WriteLine("\n Reading SerializeTest object from disk\n");
    input  = File.OpenRead("serialized.xml");
    SoapFormatter sread = new SoapFormatter(); 
    fromdisk = (SerializeTest)sread.Deserialize(input); 
    input.Close(); 


    /* x will be 0 because it won't be read from disk since non-serialized */ 
    Console.WriteLine("After SOAP(XML) Serialization := " + fromdisk);

    
    Console.WriteLine("\n\nPrinting XML Representation of Object");

    XmlDocument doc = new XmlDocument(); 
    doc.Load("serialized.xml"); 
    Console.WriteLine(doc.OuterXml);

    }

}

Java Code
import java.io.*; 

class SerializeTest implements Serializable{

    transient int x; 

    private int y; 

    public SerializeTest(int a, int b){

    x = a; 
    y = b; 

    }

    public String toString(){

    return "{x=" + x + ", y=" + y + "}"; 

    }

    public static void main(String[] args) throws Exception{

    SerializeTest st = new SerializeTest(66, 61); 
    System.out.println("Before Write := " + st);

    System.out.println("\n Writing SerializeTest object to disk");
    FileOutputStream out  = new FileOutputStream("serialized.txt");
    ObjectOutputStream so = new ObjectOutputStream(out);    
    so.writeObject(st);
    so.flush();

    System.out.println("\n Reading SerializeTest object from disk\n");
    FileInputStream in     = new FileInputStream("serialized.txt");
    ObjectInputStream si   = new ObjectInputStream(in); 
    SerializeTest fromdisk = (SerializeTest)si.readObject();

    /* x will be 0 because it won't be read from disk since transient */ 
    System.out.println("After Read := " + fromdisk);


    }

}

輸出結果:

Before Write := {x=66, y=61}

Writing SerializeTest object to disk

Reading SerializeTest object from disk

After Read := {x=0, y=61}

 

11、文檔生成

 

C#和Java都提供了從源文件提取特殊格式的注釋然后集中到一個文檔中。這些注釋一般是API規范,這是一種非常有用的方式來生成類庫文檔。生成的文檔也可以在設計者、開發者和QA之間分發。Javadoc是一種非常有用的工具用於從源代碼提取API文檔。Javadoc從源代碼注釋中提取內容生成HTML文檔。可以生成的描述信息包括包、類、成員級別。可以在類或成員變量的描述中提供對其它類或類成員的引用。

Javadoc允許方法有如下信息:

1)描述方法

2)方法拋出的異常

3)方法接收的參數

4)方法的返回值

5)關聯的方法和成員

6)API是否被棄用

7)方法首次提供的時間

廢棄信息對於編譯器也有用,如果在編譯的時候編譯器發現調用了廢棄的方法可以給予警告。

Javadoc自動提供如下信息:

1)繼承的API

2)派生類列表

3)實現的類或借口

4)類序列化格式

5)包繼承層次

由於Java生成HTML文檔,可以在Javadoc注釋中使用HTML。如下是一個例子:

Java Code

/**
 * Calculates the square of a number. 
 * @param num the number to calculate. 
 * @return the square of the number. 
 * @exception NumberTooBigException this occurs if the square of the number 
 * is too big to be stored in an int. 
 */
 public static int square(int num) throws NumberTooBigException{}

C#使用XML作為文檔格式。生成的文檔是XML文件,包含了用於提供的元數據以及少量自動生成的信息。C#的XML文檔在生成的時候不會包含有關繼承API列表、派生類或實現接口等元數據。

XML格式的主要優勢是可以以各種方式來用。可以用XSLT樣式來吧生成ASCII文本、HTML等。也可以作為一些工具的數據源來生成特殊的文檔。

如下是C# XML文檔的例子:

C# Code

///<summary>Calculates the square of a number.</summary>
///<param name="num">The number to calculate.</param>
///<return>The square of the number. </return>
///<exception>NumberTooBigException - this occurs if the square of the number 
///is too big to be stored in an int. </exception>
 public static int square(int num){}

 

12、單個文件中的多個類

 

兩種語言都可以在單個文件中定義多個類,但是有區別。在Java中,一個原文件中只可以有一個public訪問的類並且類名需要和不帶擴展名的源文件名保持一致。C#則對一個文件有多少個public類以及文件名是否和類名一致都沒有限制。

 

13、導入類庫

 

在應用程序中使用類庫有兩個步驟,首先需要在源文件中使用using或import關鍵字來引用空間或包,其次需要告訴編譯器哪里有需要的類庫。對於Java來說指定類庫位置可以使用CLASSPATH環境變量或使用-classpath編譯器選項,對於C#則在編譯的時候指定/r開關。

 

14、事件

 

所謂事件驅動編程就是一個對象可以進行注冊使得自己在別的獨享狀態修改或發生某個事件的時候被通知。事件驅動編程也被稱作發布訂閱模型或觀察者設計模式,並且在圖形用戶接口GUI編程上特別常見。Java和C#都有自己的機制實現事件。典型的發布訂閱模型是一個一對多的關系,也就是一個發布者對應多個訂閱者。訂閱者在發布者這里注冊要調用的方法,訂閱者通過內部集合保存訂閱者對象。如果訂閱者感興趣的狀態改變,發布者會調用一個方法遍歷訂閱者集合調用回調方法。

 

在Java中沒有通用的機制來實現事件,而是采用了GUI類中使用的設計模式。事件一般是java.util.EventObject類的子類,這個類具有設置或獲取事件來源的方法。在Java中訂閱者一般實現接口,並且以Listener結尾,比如MouseListener, ActionListener, KeyListener,包含一個回調方法用於在事件發生的時候被發布者調用。發布者一般包含add和Listerner名字組合而成的方法用於添加注冊的訂閱者,比如addMouseListener, addActionListener, addKeyListener。發布者還包含用於移除訂閱者的方法。這些結構構成了Java程序中的事件驅動模型。

 

C#使用委托來提供發布訂閱模型的顯式支持,事件一般是System.EventArgs類的子類。發布者具有protected的方法並以On為前綴,比如OnClick, OnClose, OnInit,在某個事件發生的時候調用這個方法,這個方法然后會調用委托並且傳入EventArgs對象的實例作為參數。這個方法作為protected的話派生類就可以直接調用,無需注冊委托。訂閱者的方法接收和事件委托相同的返回類型和參數。事件委托一般接收兩個參數,一個Object表示事件的源,一個EventArgs類表示發生的事件,並且委托是void返回值。在C#中event用於自動指定事件驅動中回調的訂閱者委托。在編譯的時候,編譯器會增加+=和-=,等同於Java的注冊和移除訂閱者的方法。

 

如下例子演示了一個類生成20個隨機數,然后在遇到偶數的時候觸發事件。

C# Code
using System; 

class EvenNumberEvent: EventArgs{

    /* HACK: fields are typically private, but making this internal so it
     * can be accessed from other classes. In practice should use properties.
     */ 
    internal int number; 

    public EvenNumberEvent(int number):base(){

    this.number = number;
    }

}

class Publisher{

    public delegate void EvenNumberSeenHandler(object sender, EventArgs e); 
    
    public event EvenNumberSeenHandler EvenNumHandler; 
    
    protected void OnEvenNumberSeen(int num){

    if(EvenNumHandler!= null)
        EvenNumHandler(this, new EvenNumberEvent(num));
    }
    

    //generates 20 random numbers between 1 and 20 then causes and 
    //event to occur if the current number is even. 
    public void RunNumbers(){
    
    Random r = new Random((int) DateTime.Now.Ticks); 

    for(int i=0; i < 20; i++){     
        int current = (int) r.Next(20); 

        Console.WriteLine("Current number is:" + current);

        //check if number is even and if so initiate callback call
        if((current % 2) == 0)
        OnEvenNumberSeen(current);

    }//for
    
    }
}//Publisher


public class EventTest{

    //callback function that will be called when even number is seen
    public static void EventHandler(object sender, EventArgs e){

    Console.WriteLine("\t\tEven Number Seen:" + ((EvenNumberEvent)e).number);
    
    }


    public static void Main(string[] args){

    Publisher pub = new Publisher(); 
    
    //register the callback/subscriber 
    pub.EvenNumHandler += new Publisher.EvenNumberSeenHandler(EventHandler); 
    
    pub.RunNumbers(); 

    //unregister the callback/subscriber 
    pub.EvenNumHandler -= new Publisher.EvenNumberSeenHandler(EventHandler); 
    
    }

}
Java Code
import java.util.*;

class EvenNumberEvent extends EventObject{

    public int number; 

    public EvenNumberEvent(Object source, int number){
    
    super(source); 
    this.number = number;
    }

}

interface EvenNumberSeenListener{

    void evenNumberSeen(EvenNumberEvent ene); 

}

class Publisher{

    Vector subscribers = new Vector(); 
    

    private void OnEvenNumberSeen(int num){

    for(int i=0, size = subscribers.size(); i < size; i++)
        ((EvenNumberSeenListener)subscribers.get(i)).evenNumberSeen(new EvenNumberEvent(this, num));
    }
    

    public void addEvenNumberEventListener(EvenNumberSeenListener ensl){

    subscribers.add(ensl); 

    }

     public void removeEvenNumberEventListener(EvenNumberSeenListener ensl){

    subscribers.remove(ensl); 
    }

    //generates 20 random numbers between 1 and 20 then causes and 
    //event to occur if the current number is even. 
    public void RunNumbers(){
    
    Random r = new Random(System.currentTimeMillis()); 

    for(int i=0; i < 20; i++){     
        int current = (int) r.nextInt() % 20; 

        System.out.println("Current number is:" + current);

        //check if number is even and if so initiate callback call
        if((current % 2) == 0)
        OnEvenNumberSeen(current);

    }//for
    
    }

}//Publisher


public class EventTest implements EvenNumberSeenListener{

    //callback function that will be called when even number is seen
    public void evenNumberSeen(EvenNumberEvent e){

    System.out.println("\t\tEven Number Seen:" + ((EvenNumberEvent)e).number);
    
    }


    public static void main(String[] args){

    EventTest et = new EventTest();

    Publisher pub = new Publisher(); 
    
    //register the callback/subscriber 
    pub.addEvenNumberEventListener(et); 
    
    pub.RunNumbers(); 

    //unregister the callback/subscriber 
    pub.removeEvenNumberEventListener(et); 
    
    }

}

 

運行結果

 

Current number is:19
Current number is:15
Current number is:1
Current number is:1
Current number is:-9
Current number is:-17
Current number is:-1
Current number is:-18
        Even Number Seen:-18
Current number is:0
        Even Number Seen:0
Current number is:1
Current number is:-2
        Even Number Seen:-2
Current number is:-9
Current number is:-4
        Even Number Seen:-4
Current number is:17
Current number is:-7
Current number is:1
Current number is:0
        Even Number Seen:0
Current number is:15
Current number is:-10
        Even Number Seen:-10
Current number is:-9

 

15、跨語言互操作

 

跨語言互操作是在一個語言中訪問另一個語言構造的能力。在Java中有很多跨語言互操作的方式。首先JNI機制允許Java程序調用C或C++甚至匯編語言寫的本機方法。本機方法可以使用JNI來訪問Java的特性,比如調用Java語言方法,初始化和修改Java類,拋出和捕獲異常,進行運行時類型檢查,動態加載Java類。要創建JNI程序可以進行如下步驟:

1)創建Java程序,把包含本機方法的聲明標記為native方法

2)寫一個main方法加載步驟6的類庫,然后使用本機方法

3)使用javac編譯器編譯包含native方法和main的類

4)使用javah編譯器和-jni開關來生產頭文件和本地方法

5) 使用你選擇的語言寫本機方法

6) 把頭文件和本機源文件編譯到共享類庫中,比如Windows的dll或UNIX的.so

 

Java還可以通過Java IDL來和CORBA的分布式對象交互。CORBA應用程序一般由對象請求代理ORB、客戶端和服務端構成。ORB負責匹配請求客戶端到服務端,使用對象引用來定位目標對象。ORB檢查對象引用的時候會檢查目標對象是否是遠程的。如果對象時本地的ORB進行進程內調用IPC,否則ORB封送參數並且把調用通過網絡路由到遠程ORB。遠程ORB然后在本地調用方法,通過網絡把結果發送回客戶端。CORBA有語言無關的接口定義語言IDL,各種語言都可以支持CORBA映射。Java IDL支持從Java對象到CORBA IDL對象的映射,各種ORB提供各種語言的CORBA語言綁定,包括C, C++, Java, Python, Lisp, Perl, 和Scheme。

 

在Java中最無縫方式進行跨語言交互的方式是直接把Java編譯成字節碼。Jython腳本語言是Python編程語言整合到Java平台的一個版本。如下例子演示了Jython如何創建一個Java的隨機數類型(java.util.Random)並且和這個類型的實例進行交互。

 

C:\jython>jython
Jython 2.0 on java1.2.1
Type "copyright", "credits" or "license" for more information.
>>> from java.util import Random
>>> r = Random()
>>> r.nextInt()
-790940041
>>> for i in range(5):
...     print r.nextDouble()
...
0.23347681506123852
0.8526595592189546
0.3647833839988137
0.3384865260567278
0.5514469740469587
>>>

 

C#和.NET運行時本來的一個設計目標就是無縫的跨語言交互。任何.NET公共語言運行時的語言都可以基於公共類型系統CTS互相交互。公共類型系統定義了類型如何聲明,確保各種語言可以共享類型信息。元數據是描述程序集、類型和應用程序定義的特性的二進制信息,它保存在CLR PE中,或者在程序集加載后保存在內存中。當前.NET運行時支持的語言包括APL, C#, C++, COBOL, Component Pascal, Eiffel, Haskel#/Mondrian, Java, Mercury, Oberon, Perl, Python, Scheme, Smalltalk, ML, 和Visual Basic。由於一種語言中具有的特性很可能在另外一種語言中沒有,.NET框架提供了CLS描述一組基本的語言特性和定義如何使用這些特性的規則。CLS規則是公共類型系統的子集,並且通過定義一組編程語言最常見的特性集合來確保跨語言互操作。C#編譯器是CLS兼容的編譯器,也就是說可以用於編譯符合CLS的代碼。C#編譯器可以檢查CLS規范並且在代碼使用了不符合CLS功能的時候給出錯誤。要讓C#編譯器檢查CLS規范可以使用[CLSCompliantAttribute(true)]特性。C#支持的另一種跨語言交互是基於COM的對象,這個機制允許開發者在C#中使用COM,反之亦然。在創建了包裝類后,C#對象可以使用COM對象,包裝類可以當做普通的C#對象來使用,.NET運行時會處理復雜的參數封送操作。可以使用tlbimp工具來自動創建包裝類。對於COM對象使用C#對象,必須創建描述C#對象的類型庫,可以使用tlbexp創建類型庫以COM的形式來描述C#對象。還可以使用regasm工具來注冊程序集。COM對象和C#對象交互的時候,運行時會負責COM和.NET之間數據的封送。C#程序還可以使用extern關鍵字和DllImport特性來使用任何DLL的功能,這么做的優勢是不需要針對C#的調用為方法作特殊處理,並且也不需要有包裝來調用既有的代碼。

 

 

 

第四部分:C#有但Java沒有的地方

 

 

1、對象清理

 

為了提供完全控制類使用的資源,C#提供了System.IDisposable接口,它包含Dispose()方法可以讓類的使用者在使用類之后釋放必要的資源。管理諸如數據庫或文件句柄的類可以從這種模式中收益。Dispose提供了一種確定的方式在類不使用的時候釋放資源,這和Java或C#的終結器不同。一般會在Dispose方法的實現中調用GC類的SupressFinalize 方法,因為我們一般通過Dispose方法顯式釋放資源而不需要運行時的終結器。C#還提供了諸如using關鍵字之類的語法糖通過Dispose方法釋放資源。如果類是Disposable的話,最好讓Dispose()方法是冪等的(也就是可以多次調用Dispose()),可以在Dispose()方法中設置一個標志位來檢查是否已經Dispose。如下例子演示了類保持打開文件,直到Dispose()方法調用后來表示文件不需要打開了。

 

C# Code
using System; 
using System.IO; 

public class MyClass : IDisposable {     

    bool disposed = false; 
    FileStream f; 
    StreamWriter sw; 
    private String name;
    private int numShowNameCalls = 0; 

    MyClass(string name){
             
        
    f = new FileStream("logfile.txt", FileMode.OpenOrCreate); 
    sw = new StreamWriter(f);
    this.name = name;
    Console.WriteLine("Created " + name);   
    }


    ~MyClass(){

    Dispose(false); 
    }

     public void Dispose(){

    if(!disposed){
        Dispose(true);
    }

    }


    
    private void Dispose(bool disposing){

    lock(this){ /* prevents multiple threads from disposing simultaneously */ 
        
        /* disposing variable is used to indicate if this method was called from a 
         * Dispose() call or during finalization. Since finalization order is not 
         * deterministic, the StreamWriter may be finalized before this object in 
         * which case, calling Close() on it would be inappropriate so we try to 
         * avoid that. 
         */
        if(disposing){     
        Console.WriteLine("Finalizing " + name);      
        sw.Close(); /* close file since object is done with */ 
        GC.SuppressFinalize(this);
        disposed = true; 
        }
        
    }

    }
    
   
    public string ShowName(){

    if(disposed)            
           throw new ObjectDisposedException("MyClass");

    numShowNameCalls++; 
    sw.Write("ShowName() Call #" + numShowNameCalls.ToString() + "\n");         
    
        return "I am " + name; 
    
    }

    public static void Main(string[] args){
    

    using (MyClass mc = new MyClass("A MyClass Object")){

        for(int i = 0; i < 10; i++){
      
        Console.WriteLine(mc.ShowName()); 
      
        } //for

    }/* runtime calls Dispose on MyClass object once "using" code block is exited, even if exception thrown  */ 
    
    }//Main 

}

如上的模式和C++方式的析構器很像,只不過不需要考慮內存分配。終結器這種不精確的特性一致被Java開發者詬病,有了Dispose后這不再是問題了。

注意:調用Dispose()方法不等同於要求對象被垃圾回收,只不過由於不需要終結器之后可以加速被回收。

 

2、委托

 

委托是提供回調函數的機制,委托和C或C++的函數指針相似。委托的一個用途就是根據算法使用的類型傳入操作到泛型算法。另一個用途就是為事件注冊處理程序。在Java中要使用C#委托中相同的功能,可以創建接口然后指定回調方法,比如Comparable接口,的缺點是方法只能是實例方法,其實一般是當做靜態方法來使用的。

要使用委托,首先聲明和要調用的回調方法返回值和相同參數的委托。然后定義接收以委托作為參數的方法。完成后,使用符合委托的方法來初始化委托的實例,然后把委托傳入接收委托作為參數的方法。委托可以接受靜態方法和實例方法,甚至同一時刻接收兩種,因為委托是多播的。如下演示了創建和使用實例委托的例子。

C# Code
using System;

/* Mammal class hierarchy used to show return type covariance */
public class Mammal {
    public Mammal(){;}

    public virtual void Speak(){;} 
}

public class Cat : Mammal{
    public Cat(){;}

    public override void Speak(){
    Console.WriteLine("Meow");
    }
}

public class Dog : Mammal{
    public Dog(){;}

    public override void Speak(){
    Console.WriteLine("Woof");
    } 
}


public class Test {

    // delegate declaration, similar to a function pointer declaration
    public delegate Mammal CallbackFunction(Dog d);  
    
    public static Cat BarkAndScareCat(Dog d) 
    {
    d.Speak(); 
    Cat c = new Cat();
    c.Speak(); 
    return c; 
    }

     public static Mammal BarkAndReturnHome(Dog d) 
    {
    d.Speak(); 
    return d; 
    }
 
    public static void Main(string[] args){
    
    Dog dog            = new Dog(); 

    //create delegate using delegate object (old way)
    CallbackFunction myCallback = new CallbackFunction(BarkAndReturnHome); 
    myCallback(dog);

    //create delegate using delegate inference (new way) 
    CallbackFunction myCallback2 = BarkAndScareCat;
    myCallback2(dog);

    }
} 

委托可以作為參數傳入方法,和C或C++的函數指針有點相似:

C# Code
using System;

//delegate base
public class HasDelegates
{
       
    // delegate declaration, similar to a function pointer declaration
    public delegate bool CallbackFunction(string a, int b);
        
    //method that uses the delegate 
    public bool execCallback(CallbackFunction doCallback, string x, int y)
    {   
    Console.WriteLine("Executing Callback function...");
    return doCallback(x, y);                    
    }

}


public class FunctionDelegates
{
   
    public static readonly HasDelegates.CallbackFunction BarFuncCallback = 
        new HasDelegates.CallbackFunction(FunctionBar); 

    public static bool FunctionBar(string a, int b)
    {   
    Console.WriteLine("Bar: {0} {1}", b, a);
    return true;

    }

}

public class DelegateTest {

  
    public static void Main(string[] args){
    
    HasDelegates MyDel = new HasDelegates();

    // with static delegate, no need to know how to create delegate
    MyDel.execCallback(FunctionDelegates.BarFuncCallback, "Thirty Three", 33);

    }
} // DelegateTest

 

3、值類型(結構)

 

在Java和C#中,堆上的東西只能等垃圾回收來收集而在棧上的對象會自動被系統回收。一般在棧上分配的內存會比在堆上略快。

在Java中,所有的類都在堆上創建而基元類型在棧上創建。如果對象很小並且很常用的話只能在堆上分配的話會造成一定的性能負擔,C#提供了一種機制可以讓某種類是基於棧分配的,叫做結構,其實C#內建的諸如int的基元類型就是使用結構來分配的。和類不同,值類型一般按值傳遞並且不會被垃圾收集。要使用基於棧的類,可以使用struct來替代class關鍵字。要創建C#結構可以使用和類一樣的new關鍵字。如果結構使用默認構造方法語法創建,那么結構的字段都會使用0初始化。但是,不可以為結構定義默認構造方法。

 

C# Code
using System; 

struct Point {

    public int x; 
    public int y; 

    public Point( int x, int y){
  
    this.x = x; 
    this.y = y;

    }
  
    public override string ToString(){

    return String.Format("({0}, {1})", x, y); 

    }


    public static void Main(string[] args){

    Point start = new Point(5, 9); 
    Console.WriteLine("Start: " + start);

    /* The line below wouldn't compile if Point was a class */ 
    Point end = new Point(); 
    Console.WriteLine("End: " + end);

    }

} // Point

 

4、運行時類型標識(as運算符)

 

C#的as運算符和C++的dynamic_cast結構一樣。as運算符的作用是嘗試把類型轉換為某種類型,如果不成功的話返回null。

C# Code
MyClass mc = o as MyClass; 

if(mc !=  null)      //check if cast successful 
   mc.doStuff(); 
注意:as不能用於值類型。

 

5、屬性

 

屬性可以避免直接訪問類的成員和Java的getters以及setters很像。可以使用屬性來訪問類的字段或成員屬性,但又避免使用方法。可以創建只讀、只寫或讀寫屬性,此外還可以創建屬性讓getter和setter具有不同的訪問性(比如public的getter和private的setter),如下是使用屬性的例子:

C# Code
using System; 

public class User {


    public User(string name){

    this.name = name;   

    }   

    private string name; 

    //property with public getter and private setter
    public string Name{

    get{
        return name; 
    }   

    private set { 
        name = value; 
    }
    }

    private static int minimum_age = 13;   

    //read-write property for class member, minimum_age
    public static int MinimumAge{
      
      get{
        return minimum_age; 
    }

    set{
      
      if(value > 0 && value < 100)
        minimum_age = value; 
      else 
        Console.WriteLine("{0} is an invalid age, so minimum age remains at {1}", value, minimum_age);

    }
    }

    public static void Main(string[] args){
  
    User newuser = new User("Bob Hope"); 

    User.MinimumAge = -5; /* prints error to screen since value invalid */ 
    User.MinimumAge = 18; 
    //newuser.Name = "Kevin Nash"; Causes compiler error since Name property is read-only 

    Console.WriteLine("Minimum Age: " + User.MinimumAge);       
    Console.WriteLine("Name: {0}", newuser.Name);
    }
} // User

 

6、多維度數組

 

如下代碼演示了多維數組和交錯數組的區別

C# Code
using System;


public class ArrayTest {

  
    public static void Main(string[] args){

    int[,] multi = { {0, 1}, {2, 3}, {4, 5}, {6, 7} };  

    for(int i=0, size = multi.GetLength(0); i < size; i++){

        for(int j=0, size2 = multi.GetLength(1); j < size2; j++){
        Console.WriteLine("multi[" + i + "," + j + "] = " + multi[i,j]);
        }

    }


    int[][] jagged = new int[4][];
    jagged[0] = new int[2]{0, 1};
    jagged[1] = new int[2]{2, 3};
    jagged[2] = new int[2]{4, 5};
    jagged[3] = new int[2]{6, 7};

    for(int i=0, size = jagged.Length; i < size; i++){

        for(int j=0, size2 = jagged[1].Length; j < size2; j++){
        Console.WriteLine("jagged[" + i + "][" + j + "] = " + jagged[i][j]);
        }

    }
    }

} // ArrayTest

 

7、索引器

 

索引器是重寫類[]運算符的語法。如果類包含另外一種對象的話,索引器就很有用。索引器的靈活之處在於支持任何類型,比如整數或字符串、還可以創建索引器允許多維數組語法,可以在索引器中混合和匹配不同的類型,最后,索引器可以重載。

C# Code
using System;
using System.Collections;

public class IndexerTest: IEnumerable, IEnumerator {

    private Hashtable list; 

    public IndexerTest (){

    index = -1; 
    list = new Hashtable();     
    }

    //indexer that indexes by number
    public object this[int column]{


    get{
    
        return list[column];
    
    }


    set{
      
        list[column] = value; 
    }

    }


    /* indexer that indexes by name */ 
    public object this[string name]{


    get{
    
        return this[ConvertToInt(name)];
    
    }

    set{
        this[ConvertToInt(name)] = value; 
    }

    }


    /* Convert strings to integer equivalents */
    private int ConvertToInt(string value){

    string loVal = value.ToLower(); 

    switch(loVal){
        
    case "zero": return 0;
    case "one": return 1;
    case "two": return 2;
    case "three":  return 3;
    case "four": return 4;
    case "five": return 5; 

    default:
        return 0; 
    }

    return 0; 

    }


    /** 
     * Needed to implement IEnumerable interface. 
     */
    public IEnumerator GetEnumerator(){ return (IEnumerator) this; }


    /** 
     * Needed for IEnumerator. 
     */ 
    private int index; 

    /** 
     * Needed for IEnumerator. 
     */ 
    public bool MoveNext(){

    index++;
    if(index >= list.Count)
        return false; 
    else
        return true; 
    }

    /** 
     * Needed for IEnumerator. 
     */ 
    public void Reset(){
    index = -1; 
    }


    /** 
     * Needed for IEnumerator. 
     */ 
    public object Current{

    get{
        return list[index];
    }
    }


    public static void Main(string[] args){

    IndexerTest it = new IndexerTest(); 
    it[0] = "A"; 
    it[1] = "B";
    it[2] = "C";
    it[3] = "D"; 
    it[4] = "E";


    Console.WriteLine("Integer Indexing: it[0] = " + it[0]);    
    Console.WriteLine("String  Indexing: it[\"Three\"] = " + it["Three"]);

    Console.WriteLine("Printing entire contents of object via enumerating through indexer :");

    foreach( string str in it){
        Console.WriteLine(str);
    }

    }

} // IndexerTest

 

8、預處理指令

 

C#包含預處理器,相當於C/C++預處理器的有限子集。C#預處理器沒有#include文件的能力也沒有使用#define進行文本替換的能力。主要的能力在於使用#define和#undef標識符以及通過#if和#elif以及#else選擇編譯某段代碼的能力。#error和#warning指示器可以在編譯的時候讓指示器之后的錯誤或警告消息顯示出來。#pragma指示器用於處理屏蔽編譯器警告消息。最后,#line指示器可以用於編譯器發現錯誤的時候指定源文件行號。

C# Code
#define DEBUG /* #define must be first token in file */ 
using System; 

#pragma warning disable 169 /* Disable 'field never used' warning */

class PreprocessorTest{

    int unused_field; 

    public static void Main(string[] args){
    
    #if DEBUG
      Console.WriteLine("DEBUG Mode := On");
    #else
      Console.WriteLine("DEBUG Mode := Off");
    #endif

    }

}

 

9、別名

 

using關鍵字可以用於為完全限定名設置別名,和C/C++的typedef相似。如果類的完全限定名需要解決命名空間沖突的話,這就很有用了。

C# Code
using Terminal = System.Console; 

class Test{

     public static void Main(string[] args){

       Terminal.WriteLine("Terminal.WriteLine is equivalent to System.Console.Writeline"); 

     }

}

 

10、運行時代碼生成

 

Reflection.Emit命名空間包含了一些類可以用於生成.NET中間語言,以及在運行時在內存中構建類甚至把PE文件寫到磁盤上。這類似Java的那些通過生成Java字節碼寫入磁盤,用於在運行時創建Java類然后被程序使用的類庫。Reflection.Enmit命名空間主要的用戶是編譯器或腳本引擎的作者。比如,System.Text.RegularExpressions使用Reflection.Emit類庫來為每一個編譯后的表達式生成自定義匹配引擎。

 

 

11、指針和不安全的代碼

 

盡管C#和Java一樣,不能使用指針類型,但是如果C#代碼在unsafe上下文中執行的話就可以使用指針類型。如果C#代碼在unsafe上下文中執行,那么就會禁止許多運行時檢查,程序也必須在所運行的機器上有完全信任權限。寫unsafe代碼的語法和語義和在C和C++中使用指針的語法和語義相似。寫unsafe代碼時,必須使用unsafe關鍵字把代碼塊指定為unsafe的,並且程序必須使用/unsafe編譯器開關編譯。由於垃圾回收期可能會在程序執行的過程中重新分配托管變量,所以在fixed代碼塊中使用托管變量的時候需要使用fixed關鍵字來固定變量地址。如果沒有fixed關鍵字的話,標記和壓縮垃圾回收期可能會在回收的過程中移動變量的地址。

 

C# Code
using System; 

class UnsafeTest{


    public static unsafe void Swap(int* a, int*b){

    int temp = *a; 
    *a = *b; 
    *b = temp; 
    }


    public static unsafe void Sort(int* array, int size){

    for(int i= 0; i < size - 1; i++)
        for(int j = i + 1; j < size; j++)
        if(array[i] > array[j])
            Swap(&array[i], &array[j]); 

    }

    public static unsafe void Main(string[] args){

    int[] array = {9, 1, 3, 6, 11, 99, 37, 17, 0, 12}; 

    Console.WriteLine("Unsorted Array:"); 
    
    foreach(int x in array)
        Console.Write(x + " "); 

    fixed( int* iptr = array ){  // must use fixed to get address of array

        Sort(iptr, array.Length);
        
    }//fixed 
    
    Console.WriteLine("\nSorted Array:"); 
    
    foreach(int x in array)
        Console.Write(x + " "); 

    }

}

 

12、按引用傳遞

 

在Java中,傳給方法的參數是按值傳遞的,方法操作的是復制的數據而不是原來的數據。在C#中,可以指定參數按照引用傳遞而不是數據拷貝。有的時候如果希望方法返回超過一個對象的時候就有用。指定參數按引用傳遞的關鍵字是ref和out。區別是使用ref的話傳入參數必須初始化,而out則不需要。

Java Code
class PassByRefTest{

    public static void changeMe(String s){

    s = "Changed"; 

    }

    public static void swap(int x, int y){

    int z = x;
    x = y;
    y = z;

    }

    public static void main(String[] args){

    int a = 5, b = 10; 
    String s = "Unchanged"; 

    swap(a, b); 
    changeMe(s); 

    System.out.println("a := " + a + ", b := " + b + ", s = " + s);
    }
}

OUTPUT
a := 5, b := 10, s = Unchanged

C# Code

using System; 

class PassByRefTest{

    public static void ChangeMe(out string s){

    s = "Changed"; 

    }

    public static void Swap(ref int x, ref int y){

    int z = x;
    x = y;
    y = z;

    }

    public static void Main(string[] args){

    int a = 5, b = 10; 
    string s; 

    Swap(ref a, ref b); 
    ChangeMe(out s); 

    Console.WriteLine("a := " + a + ", b := " + b + ", s = " + s);
    }
}

OUTPUT
a := 10, b := 5, s = Changed

 

13、逐字字符串

 

C#提供了一種方式來避免在字符串常量中使用轉移序列。唯一的例外是雙引號需要使用兩個雙引號,可以在字符串聲明的時候使用@來聲明逐字字符串。

C# Code
using System; 

class VerbatimTest{

    public static void Main(){

    //verbatim string 
    string filename  = @"C:\My Documents\My Files\File.html"; 
    
    Console.WriteLine("Filename 1: " + filename);

    //regular string 
    string filename2 =  "C:\\My Documents\\My Files\\File.html"; 

    Console.WriteLine("Filename 2: " + filename2);

    string snl_celebrity_jeopardy_skit = @"
        Darrell Hammond (Sean Connery) : I'll take ""Swords"" for $400
        Will Farrell    (Alex Trebek)  : That's S-Words, Mr Connery.
        ";

    Console.WriteLine(snl_celebrity_jeopardy_skit);

    }

}

 

14、溢出檢查

 

C#提供了顯式檢測或忽略溢出條件的選項。溢出條件檢測到之后會拋出System.OverflowException。由於溢出檢查會帶來性能損失,所以需要通過/checked+編譯選項顯式啟用。可以通過在代碼塊聲明checked來表示代碼總是進行溢出檢查,或是使用unchecked表示總是取消溢出檢查。

C# Code
using System; 

class CheckedTest{


    public static void Main(){

    int num = 5000; 

    /* OVERFLOW I */
    byte a  = (byte) num; /* overflow detected only if /checked compiler option on */

    /* OVERFLOW II */
    checked{
                
        byte b = (byte) num; /* overflow ALWAYS detected */     
    }

    /* OVERFLOW III */
    unchecked{
                
        byte c = (byte) num; /* overflow NEVER detected */      
    }

    }//Main
}

 

15、顯式接口實現

 

有的時候在實現接口的時候可能會遇到沖突,比如FileRepresentation類可能會實現IWindow和IFileHandler接口,他媽兩個接口都有Close方法,IWindow的Close方法表示關閉GUI窗口,而IFileHandler 的Close方法表示關閉文件。在Java中除了只寫一個Close方法之外別無他法,而在C#中可以為每一個接口寫一個實現。比如對於之前提到的例子,FileRepresentation類可以有兩個不同的Close方法。注意,顯式接口方法是private的,並且只有轉換成相應類型之后才能進行方法調用。

C# Code
using System;

interface IVehicle{
    
    //identify vehicle by model, make, year
    void IdentifySelf();    

}

interface IRobot{

    //identify robot by name
    void IdentifySelf();

}

class TransformingRobot : IRobot, IVehicle{

    string model; 
    string make;
    short  year; 
    string name;
    

    TransformingRobot(String name, String model, String make, short year){

    this.name  = name;
    this.model = model; 
    this.make  = make; 
    this.year  = year; 

    }

    
    void IRobot.IdentifySelf(){

    Console.WriteLine("My name is " + this.name);
    }

    void IVehicle.IdentifySelf(){

    Console.WriteLine("Model:" + this.model + " Make:" + this.make + " Year:" + this.year);
    }


    public static void Main(){

    TransformingRobot tr = new TransformingRobot("SedanBot", "Toyota", "Corolla", 2001); 

    // tr.IdentifySelf(); ERROR 

    IVehicle v = (IVehicle) tr; 

    IRobot r   = (IRobot) tr; 

    v.IdentifySelf(); 
    
    r.IdentifySelf();   
   
   }
}

OUTPUT
Model:Toyota Make:Corolla Year:2001
My name is SedanBot

 

16、友元程序集

 

友元程序集特性允許內部類型或內部方法被其它程序集訪問。可以使用[InternalsVisibleToAttribute]特性來實現友元程序集。如下代碼演示了2個源文件編譯成2個程序集來使用友元程序集特性。

C# Code
// friend_assembly_test.cs
// compile with: /target:library
using System.Runtime.CompilerServices;
using System;

[assembly:InternalsVisibleTo("friend_assembly_test_2")]

// internal by default
class Friend 
{
    public void Hello() 
    {
        Console.WriteLine("Hello World!");
    }
}

// public type with internal member
public class Friend2 
{
 internal string secret = "I like jelly doughnuts";  
}

C# Code 2
// friend_assembliy_test_2.cs
// compile with: /reference:friend_assembly_test.dll /out:friend_assembly_test_2.exe
using System;

public class FriendFinder 
{
    static void Main() 
    {
        // access an internal type
        Friend f = new Friend();
        f.Hello();

        Friend2 f2 = new Friend2();
        // access an internal member of a public type
        Console.WriteLine(f2.secret);
    }
}

 

17、命名空間限定符

 

項目越大越有可能發生命名空間沖突。C#有::運算符來指定命名空間的作用域。運算符左邊的操作數表示的是解決沖突的作用域,右邊的操作數表示的是要解決沖突的名字。左操作數可以是關鍵字global指代全局作用域或是命名空間的別名。

C# Code
using System;
using sys = System; 

namespace TestLib{

class Test{

 public class System {} 

 static DateTime Console = DateTime.Now;   

    public static void Main(){
    //Console.WriteLine("Hello world"); doesn't work due to static member variable named 'Console'
    //System.Console.WriteLine("Hello world"); doesn't work due to nested class named 'System'
        
    global::System.Console.WriteLine("The time is " + Console); 

    sys::Console.WriteLine("Hello again"); 
    }
 }

 

18、迭代器

 

 

對於支持foreach循環的數據結構,必須實現或返回System.Collections.IEnumerable的實例。枚舉器寫起來還是有點麻煩的,yield關鍵字可以把任何方法或屬性轉換為枚舉器。可以使用yield return語句來一個一個返回內容,可以使用yield break來表示結束了序列。方法或屬性必須返回IEnumerable, IEnumerable<T>, IEnumerator 或IEnumerator<T>中的一個。

C# Code
using System;
using System.Collections;

class Test{

 public static string[] fruit = {"banana", "apple", "orange", "pear", "grape"};
 public static string[] vegetables = {"lettuce", "cucumber", "peas", "carrots"};

 public static IEnumerable FruitAndVeg{
 
  get{
   
   foreach(string f in fruit){
    yield return f; 
   }

   foreach(string v in vegetables){
    yield return v; 
   }

   yield break; //optional
  }
 
 }

 public static void Main(){
 
   foreach (string produce in Test.FruitAndVeg){
     Console.WriteLine(produce);
   }
 }

}

 

19、部分類

 

部分類特性使得我們可以在多個源文件中定義單個類、接口或接口。對於自動生成的代碼特別有用。在這個特性出現之前,我們可能會修改自動生成的代碼,因為手寫代碼和自動生成的代碼位於一個文件中。而有了這個特性,就減少了出現這種情況的可能。可以通過在類聲明中使用partial關鍵字來啟動部分類,如下代碼演示了定義在兩個源文件中的部分類。注意,一個源文件中的方法和屬性可以引用另一個源文件中定義的方法和屬性。

C# Code
using System;

partial class Test{

 public static string[] fruit = {"banana", "apple", "orange", "pear", "grape"};
 public static string[] vegetables = {"lettuce", "cucumber", "peas", "carrots"};
 
 public static void Main(){
 
   foreach (string produce in Test.FruitAndVeg){
     Console.WriteLine(produce);
   }
 }

}
C# Code 2
using System;
using System.Collections; 

partial class Test{

 public static IEnumerable FruitAndVeg{
 
  get{
   foreach(string f in fruit){
    yield return f; 
   }
   foreach(string v in vegetables){
    yield return v; 
   }
  }
 
 }

需要注意的是類上的特性會進行合並,因此矛盾的特性是不允許的,比如一個文件中類聲明的是private另一個文件聲明的是public的。

 

20、可空類型

 

可空類型System.Nullable類型的實例。可空類型可以表示底層值類型的值也可以表示空值。例如,Nullable<bool>可以表示值true、false和null。可空類型的變量可以使用值的類型加上?運算符來聲明。bool?等價於Nullable<bool>。每一個可空類型都有HasValue屬性來表示是否具有有效的值或是null。可空類型真實的值保存在其Value屬性中。GetValueOrDefault()方法可以返回可空類型的值或如果是null的話返回底層值類型的默認值。

可空類型在用於把C#對象映射到關系型數據庫的時候很有用,因為在SQL數據庫中可以存在null的有效值。

C# Code
using System;

public class Test{

 public static void Main(string[] args){

  int? x = 5; 
 
  if(x.HasValue){
   Console.WriteLine("The value of x is " + x.Value);
  }

  x = null; 

  //prints 0 
  Console.WriteLine(x.GetValueOrDefault());
 }

}

??運算符叫做空結合運算符,用於測試可空類型的值,並且在值是空的情況下返回另外一個值。因為x??y等價於x==(nulll ? y : x)。

C# Code
using System;

public class Test{

 public static void Main(string[] args){

  int? x = null; 
  int  y = x ?? 5;

  //prints 5 
  Console.WriteLine(y);
 }

}

 

 

21、匿名方法

 

匿名方法是和委托相關的一個特性。匿名方法是用於以匿名形式聲明委托的方法,而不需要一個獨立的方法。如下代碼比較了使用匿名方法和具名方法的委托:

C# Code
using System;

public class Test {

    // delegate declaration, similar to a function pointer declaration
    public delegate void CallbackFunction(string a, int b);
  
    public static void PrintString(string a, int b){
      for(int i = 0; i < b ; i++)
    Console.WriteLine("{0}.) {1}", i + 1, a);
    }

    public static void Main(string[] args){
    
    /* anonymous code block */
    CallbackFunction cf = delegate (string a, int b){ 
                             for(int i = 0; i < b ; i++)
                               Console.WriteLine("{0}.) {1}", i + 1, a);
                          };

    cf("Thirty Three", 10); 


    /* using a named delegate function */
    CallbackFunction cf2 = new CallbackFunction(Test.PrintString); 
    cf2("Twenty Two", 5); 
    }
}

在使用的時候要記住匿名方法有一些限制,比如諸如break、goto和contiune之類的跳轉語句不能用於從匿名方法跳轉到外部。匿名方法也不能引用定義在外部方法的ref或out參數。

 

 

(其實還有很多,感嘆C#的偉大)

 

 

 

第五部分:Java有但C#沒有的地方

 

1、受檢查的異常

 

在異常這個概念出現之前,大多數異常處理都是通過返回代碼進行。異常對於返回值來說有很多優勢:

1)提供了一致的模型來處理錯誤和其它非預期的情況

2)如果沒有在當前上下文中處理異常的話可以向上傳播

3)開發者可以把處理錯誤的代碼和普通業務邏輯分離

Java創建了額外的機制來處理受檢查的異常和不受檢查的異常。對於受檢查的異常,調用的方法必須捕獲異常,或通過throws聲明異常必須被其調用方法處理。從另外一方面來說,不受檢查的異常不需要catch也或用throws子句聲明,不受檢查的異常和返回代碼一樣不會讓編譯器出警告或錯誤,但是如果在運行時忽略異常的話同樣會終止程序。受檢查的異常一般用於告訴調用者如何和為什么會發生調用失敗。不受檢查的異常是一般程序中大部分地方都會發生的異常,如果都要進行顯式檢查的話開銷比價值大。比如空對象引用的異常或是數組越界的異常,如果是收檢查的異常話,就需要在每一個訪問對象或訪問數組的時候都進行try catch,因此比較適合不受檢查的異常。

 

在C#中所有異常都是未收檢查的異常,也沒有throws子句。這么做的主要劣勢是API只能通過文檔來告訴調用者自己會拋出哪些異常。比如對於如下的代碼,唯一知道下面方法會出現哪些異常的方法是查看所有調用方法的源代碼或這些方法的源代碼。

 

public string GetMessageFromServer(string server) 
 {
    //Set up variables and String to write to the server
    Encoding ASCII = Encoding.ASCII;
    string Get = "GET / HTTP/1.1\r\nHost: " + server + 
                 "\r\nConnection: Close\r\n\r\n";
    Byte[] ByteGet = ASCII.GetBytes(Get);
    Byte[] RecvBytes = new Byte[256];
    String strRetPage = null;
 
    // IPAddress and IPEndPoint represent the endpoint that will
    //   receive the request
    // Get first IPAddress in list return by DNS
    IPAddress hostadd = Dns.Resolve(server).AddressList[0];
    IPEndPoint EPhost = new IPEndPoint(hostadd, 80);
 
    //Create the Socket for sending data over TCP
    Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
       ProtocolType.Tcp );
 
    // Connect to host using IPEndPoint
    s.Connect(EPhost);
    if (!s.Connected)
    {
       strRetPage = "Unable to connect to host";
       return strRetPage;
    }
 
    // Sent the GET text to the host
    s.Send(ByteGet, ByteGet.Length, 0);
 
    // Receive the page, loop until all bytes are received
    Int32 bytes = s.Receive(RecvBytes, RecvBytes.Length, 0);
    strRetPage = "Default HTML page on " + server + ":\r\n";
    strRetPage = strRetPage + ASCII.GetString(RecvBytes, 0, bytes);
 
    while (bytes > 0)
    {
       bytes = s.Receive(RecvBytes, RecvBytes.Length, 0);
       strRetPage = strRetPage + ASCII.GetString(RecvBytes, 0, bytes);
    }
 
    return strRetPage;
 }

上面的代碼取自.NET框架Beta2文檔的Socket類。注意到,在這段代碼中沒有捕獲異常。如下是根據文檔得出的這個方法可能拋出的異常:

image

(后面關於有無這個特性好壞的爭論就不翻譯了)

 

2、跨平台移植

 

Java技術一個很大的賣點就是Java寫的應用程序一次編寫到處運行。Sun官方支持Linux、Solaris 和 Windows,其它一些公司也實現了OS/2, AIX 和MacOS平台的Java。.NET也通過Mono項目和Ximian提供了移植性的支持。

 

3、擴展

 

Java擴展機制允許開發者擴展核心Java平台。開發者可以創建讓Java運行時當做認為是核心Java類的類和包,比如java.lang, java.util, java.net等。

 

4、strictfp

 

在Java中strictfp是可以用於類、方法或借口聲明的修飾符,用於確保符合IEEE 754的精確浮點數算術。當對一個類或接口使用 strictfp 關鍵字時,該類中的所有代碼,包括嵌套類型中的初始設定值和代碼,都將嚴格地進行計算。嚴格約束意味着所有表達式的結果都必須是 IEEE 754 算法對操作數預期的結果,以單精度和雙精度格式表示。

 

Java Code

public class FPTest {

    static strictfp double halfOfSquareFP(double n){

    return n * 4.0  * 0.5;
    }

    static double halfOfSquareNFP(double n){

    return n * 4.0 * 0.5;
    }

    public static void main(String[] args) {
    
    double d = 6.6e+307;
    
    System.out.println(halfOfSquareFP(d));

    System.out.println(halfOfSquareNFP(d)); 
    }

}//FPTest

 

5、動態類加載

 

Java中在運行時動態加載類的能力非常強大,動態類加載使得Java應用程序可以下載目標機器上沒有的class文件。在一個機器上的對象類型可以無縫傳輸到其它機器。新的類型可以引入遠程機器,可以在運行時擴展遠程應用程序的行為。如下例子演示了遠程應用程序接收類型實現某個接口:

Java Code

public class MyRMIServer extends UnicastRemoteObject
    implements SomeInterface {
    
    public MyRMIServer() throws RemoteException{ super();}

      
    public String obtainName(IStockTicker ticker){

    String stock_ticker = ticker.getTicker(); 

    if(stock_ticker.equalsIgnoreCase("MSFT"))
        return "Microsoft Corporation"; 
    else  if(stock_ticker.equalsIgnoreCase("SUNW")) 
            return "Sun Microsystems"; 
    else
            return "Unknown Stock Ticker"; 

    }/* obtainName(IStockTicker) */
    
}

obtainName() 遠程方法接收實現IStockTicker接口的類型,遠程客戶端可以調用這個方法然后傳入實現IStockTicker的類型,例如NASDAQStock,如果MyRMIServer 所謂遠程機器沒有類的話,整個NASDAQStock 需要的類都會自動傳輸到遠程機器。

 

6、包含字段的接口

 

在Java中,接口中可以聲明常量,在實現的類中可以使用這個常量,這在C#中是沒有的。這其實無關緊要,因為原先要這么這么用的主要原因是模擬枚舉。

 

7、匿名內部類

 

匿名內部類是類的聲明位於類創建實例內的類。匿名內部類一般用於在應用程序中只會有一個類的實例,最常用的就是在GUI類庫中指定回調。如下是使用匿名內部類來實現狀態設計模式的例子:

Java Code

 /* An instance of this class represents the current state of a ClientView GUI. */
  
public abstract class ClientState{

     // This instance of the class is used to signify that the user is not logged in.
     // The only thing a user can do in this state is login and exit.  
     
    public static ClientState NOT_LOGGED_IN = new ClientState() {
        public void setMenuState(ClientView cv) {
            
        cv.setMenuEnabledState(false); /* disable all menus */
        cv.menuLogin.setEnabled(true); 
        cv.menuExit.setEnabled(true); 

        //can't type 
        cv.textArea.setEnabled(false); 
        }

        public String toString(){
        return "ClientState: NOT_LOGGED_IN"; 
        }
    };
    
    
     // This instance of the class is used to signify that the user is logged in
     // but has not yet created a document to work with. The user cannot type or save 
     // anything in this mode. 
     
    public static ClientState NO_OPEN_DOCUMENT = new ClientState() {
        public void setMenuState(ClientView cv) {
            
        cv.setMenuEnabledState(false); /* disable all menus */
        cv.menuLogin.setEnabled(true); 
        cv.menuExit.setEnabled(true); 
        cv.menuOpenFile.setEnabled(true);
        cv.menuNewFile.setEnabled(true);

        //can't type 
        cv.textArea.setEnabled(false); 

        }

        public String toString(){
        return "ClientState: NO_OPEN_DOCUMENT"; 
        }
    }; 
    
    
     // This instance of the class is used to signify that the user is editting a file. 
     // In this mode the user can use any functionality he/she sees fit. 
     
    public static ClientState EDITTING_DOCUMENT = new ClientState() {
        public void setMenuState(ClientView cv) {
            
        cv.setMenuEnabledState(true);   /* enable all menus             
        cv.textArea.setEnabled(true); 
        }

        public String toString(){
        return "ClientState:EDITTING_DOCUMENT"; 
        }
    }; 

   
     
     // Default constructor private to stop people from directly creating instances 
     // of the class.        
    private ClientState() {;}

    
     // This disables various elements of the ClientView's menu dependent on which
     // ClientState object this is.      
    public abstract void setMenuState(ClientView cv);

} // ClientState

如下是使用ClientState類的例子

    bool loginUser(String username, String passwd) {

        //check if already logged in
        if(myGUI.state == ClientState.NOT_LOGGED_IN)  
          return true; 

        //enable parts of the GUI if the user authenticates
        if(userAuthenticated(username, passwd)){

              myGUI.state = ClientState.NO_OPEN_DOCUMENT; 
              myGUI.state.setMenuState(myView); 
              return true; 

    }

        return false; 
    
 
   }/* loginUser(String, String) */

 

8、靜態導入

 

靜態導入特性使我們可以訪問類靜態成員的時候不需要指定類名,這個特性可以讓我們減少代碼的冗余,特別對於某些幫助類型來說很方便。靜態導入和普通的import語句很像,只不過多了static關鍵字,導入的是類而不是包名。

Java Code
import static java.awt.Color.*; 

public class Test{

    public static void main(String[] args) throws Exception{

    //constants not qualified thanks to static import
    System.out.println(RED + " plus " + YELLOW + " is " + ORANGE);
    }
}

輸出:

java.awt.Color[r=255,g=0,b=0] plus java.awt.Color[r=255,g=255,b=0] is java.awt.Color[r=255,g=200,b=0]

 

 

參考

  1. Eckel, Bruce. Thinking In Java. Prentice Hall, 2000.
  2. Gunnerson, Eric. A Programmer's Introduction To C#. Apress, 2001.
  3. Sun Microsystems. The Java™ Tutorial.<http://java.sun.com/docs/books/tutorial/>
  4. Microsoft Corporation. .NET Framework Programming. < http://msdn2.microsoft.com/en-us/library/ms229284(VS.80).aspx>
  5. Microsoft Corporation. C# Language Reference. < http://msdn2.microsoft.com/en-us/library/618ayhy6(VS.80).aspx>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM