天天看點

The Similarities and Differences Between C# and Java -- Part 1(譯)

原文位址

目錄

  • 介紹(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對内容進行了核查,謝謝他!