原文位址
目錄
- 介紹(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 }
View Code
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 }
View Code
委托
在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 }
View Code
在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 }
View Code
在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 }
View Code
索引不止限制于整型,還可以是其他任何類型。
最後,屬性還可以有不同的通路級别:
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;
View Code
跟屬性一樣,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
View Code
字段和屬性的自動初始化
一個類中所有的字段都會初始化為對應類型的預設值(比如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對内容進行了核查,謝謝他!