目錄
- 介紹(Introduction)
- 相似點(Similarities)
- 編譯單位(Compiled Units)
- 名稱空間(Namespaces)
- 頂層成員(型別)(Top Level Elements(Types))
- 基礎型別(Basic Types)
- 類(Classes)
- 結構體(Structures)
- 介面(Interfaces)
- 泛型(Generic Types)
- 委託(Delegates)
- 列舉(Enumerations)
- 型別訪問級別(Type Visibilities)
- 繼承(Inheritance)
- 巢狀類(Inner Classes)
- 抽象類(Abstract Classes)
- 密封類(Sealed Classes)
- 靜態類(Static Classes)
- 可空型別(Nullable Types)
- 部分類(Partial Classes)
- 匿名類(Anonymous Classes)
- 型別成員(Type Members)
- 靜態構造方法(Static Constructors)
- 析構方法(Destructors)
- 靜態成員(Static Members)
- 屬性(Properties)
- 事件(Events)
- 欄位與屬性的自動初始化(Automatic Initialization of Fields and Properties)
- 成員訪問級別(Member Visibilities)
- 虛擬成員(Virtual Members)
- 密封成員(Sealed Members)
- 抽象成員(Abstract Members)
- 泛型成員(Generic Members)
- 只讀欄位(Read-Only and Constant Fields)
- 技術審閱(Technical Review)
我很多朋友或者同事都以為我不喜歡使用Java,他們都認為我是一個純粹的.NET技術愛好者,其實事實並不是這樣子的:)。我也喜歡使用Java,並已經從事Java開發很多年了。
網上已經有很多有關C#與Java之間“異同”的文章,但是我認為並沒有哪一篇文章的內容讓我非常滿意。在接下來我的幾篇部落格裡,我將會竭盡全力地去闡述它們兩者之間的“異同之處”。雖然我並不想挑起“口水仗”,但是我還是會以我的角度去闡述誰好誰不好(毫不留情地)。我儘可能地覆蓋各個方面,但是並不會去深入比較兩者的API,相反我會將重點放在各自語言特性上。
第一篇部落格主要集中在各自的頂層結構上,比如名稱空間、型別等。這裡兩者的版本分別是Java8和C#5.0。
兩種語言都是大小寫敏感的,嚴格意義上的“面嚮物件語言”,支援類、列舉以及介面,並且只允許單繼承,所以的型別定義必須放在名稱空間(namespace/package)中。同時,都支援註釋,方法以及欄位(包括靜態的)。兩者的最基本型別均是Object。兩者都有相同的基本運算子、相似的異常處理機制。兩者的程式啟動方法均是一個名叫Main/main的靜態方法。
一個Java類編譯之後會生成一個class檔案,這些檔案雖然可以單獨存在,但是實際中它們通常和一些清單檔案一起被打包進jar,war或者ear檔案中,這樣做是為了方便管理。jar(或其他格式)檔案中還可以包含一些其他資源,比如圖片,文字檔案等。
C#類通常存在於一個程式集中,程式集有兩種格式:
- dll:一個庫,不能獨自執行;
- exe:一個可以獨自執行的可執行檔案,可以是一個Console程式,或者Winform,也可以是一個wpf程式。
程式集同樣可以包含一些後設資料、嵌入的資源。C#/.NET中其實還定義了另外一個編譯單元:模組(module)。但是通常情況下,一個module匹配一個assembly。
C#和Java中都有namespace和package的概念。在C#中,namespace必須在它所有其他型別的最外部,一個原始檔中,可以存在多個namespace,甚至巢狀形式的。
1 namespace MyNamespace1 2 { 3 public class Class1 4 { 5 } 6 } 7 namespace MyNamespace2 8 { 9 public class Class2 10 { 11 } 12 namespace MyNamespace3 13 { 14 public class Class3 15 { 16 } 17 } 18 }
在Java中,package的定義在原始檔最頂部位置,換句話說,一個原始檔只能有一個package定義。
1 package myPackage; 2 public class MyClass 3 { 4 }
這裡有一個非常大的不同之處,在Java中,package的定義必須與物理檔案目錄一致,也就是說,一個類如果屬於a.b 這個package,那麼這個類檔案必須存在於a\b這個目錄下,否則,編譯不會通過。編譯產生的.class檔案也必須放在同一個目錄之下,比如a\b\MyClass.class。
Java和C#中都可以通過匯入名稱空間的方式去訪問其他名稱空間的型別,比如Java中可以這樣匯入所有型別(使用*匹配所有型別),也可以一次匯入一個。
1 import java.io.*; 2 import java.lang.reflect.Array;
C#中不支援單獨匯入一個型別,只能匯入名稱空間中的全部型別。
1 using System; 2 using System.IO;
不過C#中允許我們使用using關鍵字為一個型別定義一個別名。
1 using date = System.DateTime; 2 public class MyClass 3 { 4 public date GetCurrentDate() 5 { 6 //... 7 } 8 }
這種方式跟單獨匯入一個型別其實差不多意思。
Java和C#提供相似的語法,從某種程度上講,如果我們忽略它們是兩種不同的語言,那麼有時候是難區分它們有什麼不同,當然即使這樣,它們之間還是有一些重要不同之處的。
Java中提供了以下幾種頂級成員,除了package之外:
- 類(包括泛型)
- 介面(包括泛型)
- 列舉
C#中要多幾個:
- 類(包括泛型)
- 介面(包括泛型)
- 列舉
- 結構體
- 委託(包括泛型)
兩種語言中有以下基礎型別(C#/Java):
- Object/object(C#簡寫:object)
- String/string(C#簡寫:string)
- Byte/byte(C#簡寫:byte)
- SByte/N/A(C#簡寫:sbyte)
- Boolean/boolean(C#簡寫:bool)
- Char/char(C#簡寫:char)
- Int16/short(C#簡寫:short)
- UInt16/N/A(C#簡寫:unit)
- Int32/int(C#簡寫:int)
- UInt32/N.A(C#簡寫:uint)
- Int64/long(C#簡寫:long)
- UInt64/N/A(C#簡寫:ulong)
- Single/float(C#簡寫:float)
- Double/double(C#簡寫:double)
- Decimal/N/A(C#簡寫:decimal)
- dynamic/N/A
- Arrays
如你所見,對於所有的整型型別,C#提供有符號和無符號兩種,並且還提供精度更高的Decimal型別。(譯者注:上面列表中,“/”前面是C#中的結構體寫法,Int32其實是System.Int32,每種型別都提供一種簡寫方式,System.Int32對應的簡寫方式是int。Java中不存在結構體,只有一種寫法)
C#中提供三種陣列:
- 一維陣列:int[] numbers(每個元素都為int)
- 多維陣列:int[,] matrix (每個元素都為int)
- 陣列的陣列,又叫鋸齒陣列:int[][] matrix ((int[])[] 每個元素都是一個int[])
Java中也提供一維陣列和鋸齒陣列,但是並沒有多維陣列。(譯者注:在某種意義上講,鋸齒陣列可以代替多維陣列)
C#允許我們使用var關鍵字來定義一個變數並且初始化它,這是一種初始化變數的簡寫方式:
1 var i = 10; //int 2 var s = "string"; //string 3 var f = SomeMethod(); //method's return type, except void
與Java一樣,C#同樣允許我們在一個數字後面新增字尾來標明它是什麼型別的資料:
- 10n:integer
- 10l:long
- 10f:float
- 10d:double
- 10u:unsigned int(僅C#)
- 10ul:unsigned long(僅C#)
- 10m:decimal(僅C#)
大小寫均可作為字尾。
C#和Java中,類都分配在堆中。一個類只允許單繼承自另外一個類,如果沒有指定,預設繼承自Object。每個類均可以實現多個介面。(單繼承,多實現)
C#中有一套完整的型別系統,也就是說,所有基本型別(比如int、bool等)均和其他型別一樣遵循同一套型別規則。這和Java明顯不同,在Java中,int和Integer沒有關係(雖然它們之間可以相互轉換)。在C#中,所有的基本型別均是結構體(非class),它們均分配在棧中。在Java中,基本型別(int、long等)同樣分配在棧中,但是它們並不是結構體,同樣,Java中我們並不能自己定義一種分配在棧中的資料型別。C#中的結構體不能顯式地繼承自任何一個類,但是可以實現介面。
1 public struct MyStructure : IMyInterface 2 { 3 public void MyMethod() 4 { 5 6 } 7 }
在C#中,結構體和列舉被稱為“值型別”,類和介面被稱為“引用型別”。由於C#(.NET)的統一型別系統,結構體隱式繼承自System.ValueType。
(譯者注:嚴格意義上講,Java並非完全物件導向。Java中的型別存在特殊,比如基礎型別int、long、bool等,這些型別完全脫離了主流規則。此外,在C#中我們可以定義一種分配在棧中的型別,比如結構體)
在C#中,一個介面可以包含:
- 例項方法宣告
- 例項屬性宣告
- 例項事件宣告
當然它們也可以是泛型的。類和結構體均可實現介面,一個介面可以被賦值為NULL,因為它是引用型別。
在Java中,情況有一點不同。因為介面中可以有靜態成員、方法的實現:
- 例項方法宣告
- 欄位(靜態)附帶一個初始化值
- 預設方法:包含預設實現,使用default關鍵字標記
它們同樣可以是泛型的。在Java中,一個介面中的方法可以存在訪問級別,也就是說,不一定總是public。
在Java中如果一個介面僅僅包含一個方法宣告(同時可以包含一個或多個“預設方法”),那麼這個介面可以被標記為“函式式介面”(Funcitional Interface),它可以用在lambda中,介面中的方法被隱式地呼叫(參見後面有關委託部分)(譯者注:可以將一個lambda表示式賦給函式式介面,然後通過該介面去執行lambda表示式。預設方法、函式式介面、lambda表示式均屬於Java8中新增加內容)。
C#和Java中的泛型有很大的不同。雖然兩者都支援泛型類、泛型介面等,但是在C#中,泛型得到了更好的支援,而Java中泛型一旦經過編譯後,型別引數就不存在了。也就是說在Java中,List<String>在執行階段就會變成List型別,泛型引數String會被抹去,這樣設計主要是為了與Java更老版本進行相容。Java中的這種情況並不會發生在C#中,C#中我們可以通過反射得到一個泛型類的所有資訊,當然也包括它的引數。
兩種語言都支援多個泛型引數,並且都有一些限制。C#中的限制如下:
- 基類、結構體、介面:可以強制泛型引數繼承自一個特定的類(或實現特定的介面);
- 具備無參構造方法的非抽象類:只允許非抽象並且具備無參構造方法的型別作為泛型引數;
- 引用型別和值型別:泛型引數要麼被指定為引用型別(類、介面),要麼被指定為值型別(結構體、列舉)。
比如:
1 public class GenericClassWithReferenceParameter<T> where T : class 2 { 3 4 } 5 public class GenericClassWithValueParameter<T> where T : struct 6 { 7 8 } 9 public class GenericClassWithMyClassParameter<T> where T : MyClass 10 { 11 12 } 13 public class GenericClassWithPublicParameterlessParameter<T> where T : new() 14 { 15 16 } 17 public class GenericClassWithRelatedParameters<K, V> where K : V 18 { 19 20 } 21 public class GenericClassWithManyConstraints<T> where T : IDisposable where T : new() where T : class 22 { 23 24 }
Java中有以下限制:
- 基類:泛型引數必須繼承自指定的基類;
- 實現介面:泛型引數必須實現指定的介面;
- 不受限制的泛型型別:泛型引數必須實現/繼承某一個泛型型別。
一些示例:
1 public class GenericClassWithBaseClassParameter<T extends BaseClass> 2 { 3 4 } 5 public class GenericClassWithInterfaceParameter<T extends Interface> 6 { 7 8 } 9 public class GenericClassWithBaseMatchingParameter<T, ? super T> 10 { 11 12 } 13 public class GenericClassWithManyInterfaceParameters<T implements BaseInterface1 & BaseInterface2> 14 { 15 16 }
在C#中,委託是一類方法的簽名,由以下組成:
- 名稱;
- 返回值;
- 引數列表。
一個委託可以指向一個靜態的、或者一個例項的甚至一個匿名(lambda表示式)的方法,只要這些方法的簽名與委託一致即可。
1 public delegate double Operation(double v1, double v2); 2 //a delegate pointing to a static method 3 Operation addition = Operations.Add; 4 //a delegate pointing to an instance method 5 Operation subtraction = this.Subtract 6 //a delegate pointing to an anonymous method using lambdas 7 Operation subtraction = (a, b) => 8 { 9 return a + b; 10 };
當然,委託也可以是泛型的,比如:
1 public delegate void Action<T>(T item);
委託預設繼承自System.Delegate型別,所以它們對動態以及非同步呼叫均有了預設支援。
Java中有一個與委託類似的結構:函式式介面(譯者注:見前面有關介面的內容),它們指那些只包含一個方法宣告的介面(可以有其他預設方法)。函式式介面可以用來呼叫lambda表示式,比如:
1 public interface MyWorkerFunction 2 { 3 @FunctionalInterface 4 public void doSomeWork(); 5 } 6 public void startThread(MyWorkerFunction fun) 7 { 8 fun.doSomeWork(); 9 } 10 public void someMethod() 11 { 12 startThread(() -> System.out.println("Running...")); 13 }
如果一個被標記為“函式式介面”的介面包含了不止一個方法的宣告,那麼編譯不會通過。
(譯者注:C#中通常使用委託去實現觀察者模式,而Java中使用介面去實現觀察者模式)
Java中的列舉可以包含多種成員(構造方法、欄位以及方法等),甚至可以實現介面,而這些在C#中是不允許的。
1 public enum MyEnumeration implements MyInterface 2 { 3 A_VALUE(1), 4 ANOTHER_VALUE(2); 5 private int value; 6 private MyEnumeration(int value) 7 { 8 this.value = value; 9 } 10 public static String fromInt(int value) 11 { 12 if (value == A_VALUE.value) return ("A_VALUE"); 13 else return ("ANOTHER_VALUE"); 14 } 15 }
在C#中,列舉不包含方法,也不實現介面。但是我們可以定義一個列舉型別,讓其繼承自一個基礎型別(比如int)
1 public enum MyEnumeration : uint 2 { 3 AValue = 1, 4 AnotherValue = 2 5 }
每個列舉型別隱式地繼承自System.Enum。
無論是C#還是Java中,我們都可以為每個列舉項指定一個特殊的值,如果不指定,預設被分配一個連續的值。
Java中訪問級別:
- package:包內其他型別可訪問(預設訪問級別);
- public:所有人可以訪問。
C#中的:
- internal:程式集中其他型別可訪問(預設訪問級別);
- public:所有人可以訪問。
C#中的繼承類與實現介面的語法是一樣的:
1 public class MyClass : BaseClass, IInterface 2 { 3 }
但是在Java中,繼承類和實現介面的語法不一樣,分別使用extends和implements:
1 public class MyClass extends BaseClass implements Interface 2 { 3 4 } 5 public interface DerivedInterface extends BaseInterface1, BaseInterface2 6 { 7 8 }
兩者中,都只允許單繼承、多實現。並且介面可以繼承自其他介面。
在C#中,實現介面有兩種方式:
- 隱式實現:介面中的成員直接可以通過實現該介面的類來訪問;
- 顯式實現:介面中的成員並不能直接通過實現該介面的類來訪問,必須先將類例項轉換成介面。
下面看一下在C#中,顯式實現IMyInterface1介面和隱式實現IMyinterface2介面:
1 public class MyClass : IMyInterface1, IMyInterface2 2 { 3 void IMyInterface1.MyMethod1() 4 { 5 6 } 7 public void MyMethod2() 8 { 9 10 } 11 }
顯式實現的成員總是私有的,並且不能是虛擬的也不能是抽象的。如果我們要呼叫介面中的方法,必須先將型別例項轉換成介面:
1 MyClass c = new MyClass(); 2 IMyInterface1 i = (IMyInterface1) c; 3 i.MyMethod();
Java中只有隱式實現介面的概念:
1 public class MyClass implements MyInterface 2 { 3 public void myMethod() 4 { 5 6 } 7 }
在Java和C#中都支援多層巢狀類的定義,但是在Java中,這些巢狀類既可以是靜態巢狀類也可以是例項巢狀類:
1 public class MyClass 2 { 3 public static class MyStaticInnerClass 4 { 5 6 } 7 public class MyInnerClass 8 { 9 10 } 11 }
例項巢狀類的例項化必須通過它的外層類例項來完成(注意這裡奇怪的語法):
1 MyClass.MyStaticInnerClass c1 = new MyClass.MyStaticInnerClass(); 2 MyClass c2 = new MyClass(); 3 MyClass.MyInnerClass c3 = c2.new MyInnerClass();
在C#中,所有的巢狀類在任何時候都可以被例項化,並不需要通過它的外層類例項完成(只要訪問級別允許):
1 public class MyClass 2 { 3 public class MyInnerClass 4 { 5 6 } 7 } 8 MyClass.MyInnerClass c = new MyClass.MyInnerClass();
C#中巢狀類的訪問級別有以下幾種:
- internal:同一程式集中的其他型別可以訪問(預設);
- protected:子類可以訪問(包括自己);
- protected internal:同一程式集或其子類可以訪問(譯者注:這裡取的是並集);
- private:自己可以訪問;
- public:所有人均可以訪問。
然而Java中巢狀型別的訪問級別如下:
- package:同一包內可訪問;
- private:自己可訪問;
- protected:子類可訪問(包括自己);
- public:所有人可訪問。
C#和Java中都有抽象類的概念,定義抽象類的語法也是相同的:
1 public abstract class MyClass 2 { 3 public abstract void myMethod(); 4 }
C#中的結構體不能是抽象的。
兩種語言中都允許將一個類宣告為sealed/final(密封類),我們不能從密封類派生出新的型別:
1 public sealed class MyClass 2 { 3 //a C# sealed class 4 } 5 public final class MyClass 6 { 7 //a Java final class 8 }
C#中的結構體總是sealed的。
在C#中,我們可以定義靜態類,靜態類同時也屬於抽象類(不能例項化)、密封類(不能被繼承)。靜態類中只能包含靜態成員(屬性、方法、欄位以及事件):
1 public static class MyClass 2 { 3 public static void MyMethod() 4 { 5 } 6 public static string MyField; 7 public static int MyProperty { get; set; } 8 public static event EventHandler MyEvent; 9 }
Java中沒有靜態類的概念。(譯者注:注意Java中可以有巢狀靜態類)
在C#中,結構體、列舉等變數(值型別)均被分配在棧(stack)中,因此它們任何時候都代表了一個具體的值,它們不能為null,但是我們可以使用某種語法建立一個可空的值型別,可以將null賦給它:
1 int ? nullableInteger = null; 2 nullableInteger = 1; 3 if (nullableInteger.HasValue) //if (nullableInteger != null) 4 { 5 int integer = nullableInteger.Value; //int integer = nullableInteger 6 }
在Java中,基本型別(int、bool)變數永遠都不能為null,我們需要使用對應的封裝類來實現這一目的:
1 Integer nullableInteger = null; 2 nullableInteger = new Integer(1);
C#中的類、介面屬於引用型別,引用型別變數本身就可以賦值null。
(譯者注:在C語言中,普通變數和指標變數有區別,普通變數記憶體中儲存的是變數本身代表的數值,而指標變數記憶體中儲存的是一個記憶體地址,該地址可以“不存在”(不指向任何記憶體)。道理跟這裡一致。)
C#中允許將一個類標記為partial,也就是說,我們可以在多個原始檔中同時定義一個類。編譯時,這些不同原始檔中的程式碼可以自動組合起來形成一個整體。這非常有利於我們儲存那些自動生成的程式碼,因為自動生成的程式碼一般不需要再修改,所以完全可以放在一個單獨的原始檔中:
1 //in file MyClass.Generated.cs 2 public partial class MyClass 3 { 4 public void OneMethod() 5 { 6 7 } 8 } 9 10 //in file MyClass.cs 11 public partial class MyClass 12 { 13 public void AnotherMethod() 14 { 15 16 } 17 }
(譯者注:部分類的出現,可以說主要是為了方便“視覺化開發”,因為在現代軟體開發過程中,IDE通常會根據設計器中的操作為我們生成固定程式碼,這些程式碼一般不需要我們再人工調整,完全可以單獨放在一個原始檔中。)
在Java中,我們可以建立一個實現了某些介面、或者繼承某個類的匿名類,只要該匿名類中實現了基類(介面)所有沒被實現的方法:
1 this.addEventListener(new ListenerInterface 2 { 3 public void onEvent(Object source, Event arg) 4 { 5 6 } 7 });
C#中的匿名類並沒有顯式定義方法,而僅僅只包含只讀屬性。如果兩個匿名類中的屬性型別相同,並且順序一樣,那麼就可以認為這兩個匿名類是相同的型別。
1 var i1 = new { A = 10, B = "" }; 2 var i2 = new { A = 1000, B = "string" }; 3 //these two classes have the same type 4 i1 = i2;
為了支援匿名類,C#引進了var關鍵字。
在.NET(C#)中,有如下型別成員:
- 構造方法(靜態或者例項)
- 析構方法
- 方法(靜態或例項)
- 欄位(靜態或例項)
- 屬性(靜態或例項)
- 事件(靜態或例項)
- 重寫操作符或型別轉換(下一篇部落格有介紹)
Java中的型別成員僅包含:
- 構造方法(靜態或例項)
- 構造程式碼塊
- 析構方法
- 方法(靜態或例項)
- 欄位(靜態或例項)
Java和C#中的靜態構造方法比較相似,但是語法上有細微差別,Java的靜態構造方法這樣:
1 public class MyClass 2 { 3 static 4 { 5 //do something the first time the class is used 6 } 7 }
而C#中的靜態構造方法這樣寫:
1 public class MyClass 2 { 3 static MyClass() 4 { 5 //do something the first time the class is used 6 } 7 }
Java中支援另外一種封裝體:構造程式碼塊。數量上沒有限制,這些程式碼會自動合併到該類的構造方法中:
1 public class MyClass 2 { 3 { 4 System.out.println("First constructor block, called before constructor"); 5 } 6 public MyClass() 7 { 8 System.out.println("MyClass()"); 9 } 10 { 11 System.out.println("Second constructor block, called before constructor but after first constructor block"); 12 } 13 }
在C#中,析構方法(或者說析構器)是Finalize方法的一種簡寫方式。當GC準備回收一個堆中物件的記憶體之前時,會先呼叫物件的析構方法。Java中有一個確定的方法叫finalize,它的功能與C#中的析構方法類似。
在C#中,我們可以按照C++中那種語法去定義析構方法:
1 public class MyClass 2 { 3 ~MyClass() 4 { 5 //object is being freed 6 } 7 }
不像C#,Java中允許我們通過一個類例項去訪問類中的靜態成員,比如:
1 public class MyClass 2 { 3 public static void doSomething() 4 { 5 } 6 } 7 8 MyClass c = new MyClass(); 9 c.doSomething();
屬性在C#中非常有用處,它允許我們使用一種清晰的語法去訪問欄位:
1 public class MyClass 2 { 3 public int MyProperty { get; set; } 4 } 5 6 MyClass c = new MyClass(); 7 c.MyProperty++;
(譯者注:原作者在這裡舉的例子,只是簡單地說明C#中屬性的用法,並沒有充分體現出屬性的重要作用。)
我們可以定義一個自動屬性(比如上面例子),還可以顯式定義私有欄位:
1 public class MyClass 2 { 3 private int myField; 4 public int MyProperty 5 { 6 get 7 { 8 return this.myField; 9 } 10 set 11 { 12 this.myField = value; 13 } 14 } 15 }
在Java中,只能通過方法:
1 public class MyClass 2 { 3 private int myProperty; 4 public void setMyProperty(int value) { this.myProperty = value; } 5 public int getMyProperty() { return this.myProperty; } 6 } 7 8 MyClass c = new MyClass(); 9 c.setMyProperty(c.getMyProperty() + 1);
C#中,我們還可以為類、結構體以及介面定義索引器,比如:
1 public class MyCollection 2 { 3 private Object [] list = new Object[100]; 4 public Object this[int index] 5 { 6 get 7 { 8 return this.list[index]; 9 } 10 set 11 { 12 this.list[index] = value; 13 } 14 } 15 }
索引不止限制於整型,還可以是其他任何型別。
最後,屬性還可以有不同的訪問級別:
1 public int InternalProperty 2 { 3 get; 4 private set; 5 } 6 7 public string GetOnlyProperty 8 { 9 get 10 { 11 return this.InternalProperty.ToString(); 12 } 13 }
C#中一般使用事件去實現“觀察者模式”,事件允許我們註冊一個方法,當事件激發時,該方法會被呼叫。
1 public class MyClass 2 { 3 public event EventHandler MyEvent; 4 public void ClearEventHandlers() 5 { 6 //check for registered event handlers 7 if (this.MyEvent != null) 8 { 9 //raise event 10 this.MyEvent(this, EventArgs.Empty); 11 //clear event handlers 12 this.MyEvent = null; 13 } 14 } 15 } 16 17 MyClass a = new MyClass(); 18 //register event handler 19 c.MyEvent += OnMyEvent; 20 //unregister event handler 21 c.MyEvent -= OnMyEvent;
跟屬性一樣,C#中也允許我們自己顯式實現訪問器(add/remove),這樣我們可以更靈活控制事件的註冊和登出:
1 public class MyClass 2 { 3 private EventHandler myEvent; 4 public event EventHandler MyEvent 5 { 6 add 7 { 8 this.myEvent += value; 9 } 10 remove 11 { 12 this.myEvent -= value; 13 } 14 } 15 } 16 17
一個類中所有的欄位都會初始化為對應型別的預設值(比如int初始化0,bool初始化為false等)。C#中的屬性同樣可以按照這種方式自動初始化。這方面兩種語言都是一樣的(當然Java中沒有屬性)。
C#型別成員中有以下訪問級別:
- private:型別內部可訪問
- internal:同一程式集中可訪問
- protected:子類可訪問(包括自己)
- protected internal:同一程式集或者子類可訪問(譯者注:這裡取兩者並集)
- public:所有人均可訪問。
Java中型別成員的訪問級別為:
- package:同一包中可訪問
- protected:子類可訪問(包括自己)
- private:自己可訪問
- public:所有人可訪問。
在Java中,除非被宣告成了final,否則所有成員預設均是虛擬的(但是沒有virtual關鍵字標記)。
在C#中,如果要定義一個虛擬成員,我們必須使用virtual關鍵字:
1 public class MyBaseClass 2 { 3 public virtual void MyMethod() 4 { 5 6 } 7 } 8 public class MyDerivedClass : MyBaseClass 9 { 10 public override void MyMethod() 11 { 12 13 } 14 }
如果派生類中有一個與基類重名的成員(但不是重寫基類成員),這時候我們需要使用new關鍵字標記該成員(這樣的話派生類成員會覆蓋基類成員):
1 public class MyBaseClass 2 { 3 public void MyMethod() 4 { 5 6 } 7 } 8 9 public class MyDerivedClass : MyBaseClass 10 { 11 public new void MyMethod() 12 { 13 //no relation with MyBaseClass.MyMethod 14 } 15 }
在C#和Java中,我們都可以定義一個密封成員(sealed/final),密封成員在派生類中不能被重寫。
C#中的語法為:
1 public class MyClass 2 { 3 public sealed void DoSomething() 4 { 5 6 } 7 }
Java中的語法為:
1 public class MyClass 2 { 3 public final void doSomething() 4 { 5 6 } 7 }
兩種語言中,抽象類中都可以存在抽象方法,但這不是必須的。也就是說,一個抽象類中可以沒有任何抽象成員。在C#中,除了抽象方法外,還可以有抽象屬性和抽象事件。
方法也可以是泛型的,不管它是否存在於一個泛型類中。泛型方法可以自動識別它的型別引數:
1 public class MyClass 2 { 3 public static int Compare<T>(T v1, T v2) 4 { 5 if (v1 == v2) 6 { 7 return 0; 8 } 9 return -1; 10 } 11 } 12 //no need to specify the int parameter type 13 int areEqual = MyClass.Compare(1, 2);
Java和C#中都有隻讀欄位,但是C#中使用readonly來標記:
1 public static class Singleton 2 { 3 //a C# readonly field 4 public static readonly Singleton Instance = new Singleton(); 5 }
Java中使用final來標記:
1 public class Singleton 2 { 3 //a Java final field 4 public static final Singleton INSTANCE = new Singleton(); 5 }
C#中也另外一種只讀欄位:常量。一個常量總是靜態的,並且會被初始化一個基本型別值,或者列舉值:
1 public static class Maths 2 { 3 //a C# constant field 4 public const double PI = 3.1415; 5 }
readonly和const宣告的變數有區別:const變數只能在宣告時初始化,並且初始化表示式必須是可計算的,編譯之後不能再改變,它的值永遠是確定一樣的;而readonly變數既可以在宣告時初始化還可以在構造方法中初始化,所以每次執行,readonly變數的值可能不一樣(雖然之後也不能改變)。
寫這篇部落格時,我的好友Roberto Cortez對內容進行了核查,謝謝他!