類繼承
可以使用一個已經存在的類作為新類的基礎,這個已經存在的類稱為基類,新類稱為派生類,派生類成員組成為:本身聲明中的成員和基類的成員。派生類不能刪除它所繼承的任何成員。
要聲明一個派生類,需要在類名后加入基類規格說明。基類規格說明是由冒號和基類名稱組成。
訪問繼承的成員
繼承的成員可以被訪問,就像它們是派生類自己聲明的一樣。
namespace InheritanceExample
{
class SomeClass
{
public string Field1 = "base class field";
public void Method1(string value)
{
Console.WriteLine("Base class-- Method1: {0}", value);
}
}
class OtherClass : SomeClass
{
public string Field2 = "derived class field";
public void Method2(string value)
{
Console.WriteLine("Derive class -- Method2:{0}", value);
}
}
class Program
{
static void Main()
{
OtherClass oc = new OtherClass();
oc.Method1(oc.Field1);
oc.Method1(oc.Field2);
oc.Method2(oc.Field1);
oc.Method2(oc.Field2);
}
}
}

所有類都派生自object類
除了特殊的類object,所有的類都是派生類,即使它們沒有基類規格說明。類object是唯一的非派生類,因為它是繼承層次結構的基礎。
需要注意的是:一個類聲明的基類規格說明中只能有一個單獨的類,這稱為單繼承。雖然類只能直接繼承一個基類,但繼承的層次沒有限制。
屏蔽基類的成員
雖然派生類不能刪除它繼承的任何成員,但可以用與基類成員名稱相同的成員來屏蔽基類成員。這是繼承的基本功能之一。在派生類中屏蔽基類成員的一些要點如下:
- 要屏蔽一個繼承的數據成員,需要聲明一個新的相同類型的成員,並使用相同的名稱。
- 要屏蔽一個繼承的函數成員,需要聲明新的帶有相同簽名的函數成員,簽名即由名稱和參數列表組成,不包含返回類型。
- 要讓編譯器知道在故意屏蔽繼承的成員,使用
new修飾符。 - 也可以屏蔽靜態成員。
如果派生類必須完全的訪問被隱藏的繼承類成員,可以使用基類訪問表達式來訪問,所謂基類表達式就是由關鍵字new后面跟着一個點和數據成員名字組成。
namespace InheritanceExample
{
class SomeClass
{
public string Field1 = "base class field";
public void Method1(string value)
{
Console.WriteLine("Base class-- Method1: {0}", value);
}
}
class OtherClass : SomeClass
{
new public string Field1 = "derived class field";//屏蔽基類字段成員
new public string Method1(string value)//不要求返回值類型一致
{
Console.WriteLine("Derive class -- Method1:{0}", value);
Console.WriteLine("using base--Field1,{0}", base.Field1);//類內使用base來訪問被隱藏的繼承成員
base.Method1( Field1);
return value;
}
}
class Program
{
static void Main()
{
OtherClass oc = new OtherClass();
oc.Method1(oc.Field1);
}
}
}

基類訪問
如果派生類必須完全地訪問被隱藏的繼承成員,可以使用基類訪問表達式訪問隱藏的繼承成員。基類訪問表達式由關鍵字base后跟一個點和成員的名稱組成。
namespace InheritanceExample
{
class SomeClass
{
public string Field1 = "base class field";
public void Method1(string value)
{
Console.WriteLine("Base class-- Method1: {0}", value);
}
}
class OtherClass : SomeClass
{
new public string Field1 = "derived class field";
new public string Method1(string value)//不要求返回值類型一致
{
Console.WriteLine("Derive class -- Method1:{0}", value);
Console.WriteLine("using base--Field1,{0}", base.Field1);//類內使用base來訪問被隱藏的繼承成員
base.Method1(Field1);
return value;
}
public void PrintField1()
{
Console.WriteLine(Field1);
Console.WriteLine(base.Field1);
base.Method1("called by PrintField1()");//用base 調用基類方法
}
}
class Program
{
static void Main()
{
OtherClass oc = new OtherClass();
oc.PrintField1();
}
}
}
可以看到可以在派生類內使用base來調用基類的成員(字段與方法)。
使用基類的引用
派生類的實例由基類的實例加上派生類新增的成員組成。派生類的引用指向整個類對象,包括基類部分。
如果有一個派生類對象的引用,就可以獲取該對象基類部分的引用(使用類型轉換運算符把該引用轉換為基類類型)。
namespace InheritanceExample
{
class MyBaseClass
{
public void Print()
{
Console.WriteLine("This is the base class.");
}
}
class MyDerivedClass : MyBaseClass
{
new public void Print()
{
Console.WriteLine("This is the derived class.");
}
}
}
class Program
{
static void Main()
{
MyDerivedClass derived = new MyDerivedClass();
MyBaseClass mybc = (MyBaseClass)derived; //轉換為基類,該對象僅能“看到”基類的方法。
derived.Print();
mybc.Print();
}
}
}

可以看到派生類屏蔽了基類的Print方法,故派生類實例對象調用Print方法,調用的是派生類的方法,而基類引用對象調用print方法,調用的是基類的Print方法。
如果對派生類增加新的方法Print,則基類引用不能調用該方法,再次印證了“基類引用只能‘看到’基類的成員。

namespace InheritanceExample
{
class MyBaseClass
{
public void Print()
{
Console.WriteLine("This is the base class.");
}
public int Field = 20;
}
class MyDerivedClass : MyBaseClass
{
new public void Print()
{
Console.WriteLine("This is the derived class.");
}
public void Print1()
{
Console.WriteLine("This is derive class Method Print1");
}
}
class Program
{
static void Main()
{
MyDerivedClass derived = new MyDerivedClass();
MyBaseClass mybc = (MyBaseClass)derived; //轉換為基類,該對象僅能“看到”基類的方法。
derived.Print();
mybc.Print();
derived.Print1();
//mybc.Print1();
Console.WriteLine("mybc.Field:{0}", mybc.Field);
}
}
}

虛方法與覆寫方法
當使用基類引用訪問派生類對象時,得到的是基類的成員,也只能訪問和調用基類成員。虛方法可以使得基類引用訪問”升至“派生類內。
可以使基類引用調用派生類的方法,需要滿足下面的條件:
- 派生類的方法和基類的方法有相同的簽名和返回類型,並且具有相同的可訪問性。
- 基類的方法使用
virtual標注 - 派生類的方法使用
override標注
namespace InheritanceExample
{
class MyBaseClass
{
virtual public void Print()
{
Console.WriteLine("This is the base class");
}
}
class MyDerivedClass : MyBaseClass
{
public override void Print()
{
Console.WriteLine("This is called in MyDerivedClass");
base.Print();
Console.WriteLine("This is the derived class");
}
}
class Program
{
static void Main()
{
MyDerivedClass derived = new MyDerivedClass();
MyBaseClass mybc = (MyBaseClass)derived;
derived.Print();
mybc.Print();
}
}
}
需要注意的是:不能覆寫static方法或者非虛方法,
覆寫標記為override的方法
覆寫方法可以在繼承的任何層次出現。
- 當使用對象基類引用調用一個覆寫方法時,方法的調用被沿派生層次上溯執行,一直到標記為override的方法的最高派生版本。
namespace InheritanceExample
{
class MyBaseClass
{
virtual public void Print()
{
Console.WriteLine("This is the base class");
}
}
class MyDerivedClass : MyBaseClass
{
public override void Print()
{
Console.WriteLine("This is the derived class");
}
}
class SecondDerived : MyDerivedClass
{
public override void Print()
{
Console.WriteLine("This is the second derived class");
}
}
class Program
{
static void Main()
{
SecondDerived derived = new SecondDerived();
MyBaseClass mybc = (MyBaseClass)derived;
derived.Print();
mybc.Print();
}
}
}

可以看到基類的引用mybc在調用Print方法時,直接上溯到了最高派生類的Print方法,因為最高派生類的Print有override標記。
- 如果在更高的派生級別有該方法的其他聲明,但沒有被標記為override,那么它們不會被調用。
namespace InheritanceExample
{
class MyBaseClass
{
virtual public void Print()
{
Console.WriteLine("This is the base class");
}
}
class MyDerivedClass : MyBaseClass
{
public override void Print()
{
Console.WriteLine("This is the derived class");
}
}
class SecondDerived : MyDerivedClass
{
new public void Print()
{
Console.WriteLine("This is the second derived class");
}
}
class Program
{
static void Main()
{
SecondDerived derived = new SecondDerived();
MyBaseClass mybc = (MyBaseClass)derived;
derived.Print();
mybc.Print();
}
}
}
取消最高派生類的Print方法的override標記,加以new標記進行屏蔽,發現基類的引用調用Print方法,只能調用中間層次的派生類(MyDerived)的Print方法。
覆蓋其他成員類型
在屬性以及索引器上的覆蓋與方法的覆蓋一樣。
namespace ClassInheritance
{
class MyBaseClass
{
public int _myInt = 5;
virtual public int MyProperty
{
get { return _myInt; }
}
}
class MyDerivedClass : MyBaseClass
{
public int _myInt = 10;
public override int MyProperty
{
get { return _myInt; }
}
}
class Program
{
static void Main()
{
MyDerivedClass derived = new MyDerivedClass();
MyBaseClass mybc = (MyBaseClass)derived;
Console.WriteLine(derived.MyProperty);
Console.WriteLine(mybc.MyProperty);
}
}
}

虛方法與覆蓋只適用於函數成員,沒有字段的”虛方法“與”覆寫方法“

構造函數的執行
要創建對象的基類部分,需要隱式調用基類的某個構造函數作為創建實例過程的一部分,繼承層次鏈中的每個類在執行它自己的構造函數之前執行它的基類構造函數。
構造函數初始化語句
默認情況下,在構造對象時,將調用基類的無參數構造函數。但構造函數可以重載,所以基類可能有一個以上的構造函數。如果希望派生類使用一個指定的基類構造函數而不是無參數構造函數,必須在構造函數初始化語句中指定它。
有兩種形式的構造函數初始化語句。
- 第一種是使用base並指明使用哪一個基類構造函數。
- 第二種是使用this並指明應使用當前類哪一個構造函數。
第一種比如:
public MyDerivedClass(int x, string s):base(s,x) //構造函數初始化語句。
{
...
}
如上面代碼片段,構造函數初始化指明了要使用兩個參數的基類構造函數,並且第一個參數是一個string,第二個參數是一個int。
在基類參數列表中的參數必須在類型和順序方面與已定的基類構造函數的參數列表相匹配。
第二種比如:
public MyClass(int x):this(x,"Using default string") //構造函數初始化語句
{
...
}
這種形式的構造函數初始化語句可以讓構造過程使用當前類的其他構造函數,比如上面代碼片段,MyClass類包含一個參數的構造函數,但這個單參數函數構造函數使用了同一個類中具有兩個參數的構造函數,為第二個參數提供了一個默認值。
這種語法很有用的一種情況是:一個類有好幾個構造函數,並且它們都需要在對象構造的過程開始時執行一些公共的代碼。對於這種情況,可以把公共的代碼提取出來作為一個構造函數,被其他所有的構造函數初始化語句使用。可能我們會認為,還可以聲明另外一個方法來進行這些公共的初始化,讓所有的構造函數來調用這個方法,但這個方法不是一個好的辦法,因為首先,編譯器知道方法是構造函數后會進行一些優化。其次有的時候一些事情必須在構造函數中進行,在其他地方則不行。比如readonly字段只能在構造函數中初始化。
回到公共構造函數,如果這個構造函數可以初始化類中所有需要初始化的東西,並且可以獨立作為一個有效的函數,那么完全可以把它設置為public的構造函數,但是如果它不能完全初始化一個對象,怎么辦?此時,必須禁止從類的外部調用構造函數,因為那樣的話,它只會初始化對象的一部分。要避免這個問題,可以把構造函數聲明為private,而不是public,然后讓其他構造函數使用它,比如:
namespace Constructor
{
class MyClass
{
readonly int firstVar;
readonly double secondVar;
public string UserName;
public int UserID;
private MyClass()
{
firstVar = 20;
secondVar = 30.5;
Console.WriteLine("Calling constructor with no parameters");
}
public MyClass(string firstName) : this()
{
UserName = firstName;
UserID = -1;
}
public MyClass(int idNum) : this()
{
UserName = "Anonymous";
UserID = idNum;
}
}
class Program
{
static void Main()
{
MyClass mc1 = new MyClass("JohnYang");
MyClass mc2 = new MyClass(0);
Console.WriteLine("mc1.Id is {0}", mc1.UserID);
Console.WriteLine("mc2.UserName is {0}", mc2.UserName);
}
}
}

類訪問修飾符
類可以被系統中其他類看到並訪問.
類的可訪問性有兩個級別:public和internal.默認是internal,不允許是其他類型.
- 標記為public的類可以被系統內任何程序集中的代碼訪問.要使一個類對其他程序集可見,使用
public訪問修飾符,如下:
public class Myclass{
...
}
- 標記為
internal的類只能被它自己所在的程序集內的類看到,這是默認的可訪問級別,所以除非在類的聲明中顯式地指定修飾符pulic,程序集外部的代碼不能訪問該類.
比如:創建類庫SuperLib.cs,並寫下如下代碼:
其中,SquareWidget類不聲明為public.
//SuperLib.cs
using System;
namespace SuperLib
{
class SquareWidget
{
public double SideLength = 0;
internal int Test = 10;
public int Test1 = 11;
public double Area
{
get { return SideLength * SideLength; }
}
}
}
然后在測試代碼test.cs中引用上述類庫,然后寫入以下測試代碼:
using System;
using SuperLib;
namespace Constructor
{
class WidgetsProgram
{
static void Main()
{
SquareWidget sq = new SquareWidget();
sq.SideLength = 5;
Console.WriteLine($"{sq.Area}");
Console.WriteLine($"{sq.Test1}");
Console.WriteLine($"{sq.Test}");
Console.ReadLine();
}
}
}
然后,將類庫代碼中對類SquareWidget添加public聲明:
using System;
namespace SuperLib
{
public class SquareWidget
{
public double SideLength = 0;
internal int Test = 10;
public int Test1 = 11;
public double Area
{
get { return SideLength * SideLength; }
}
}
}
可以發現可以在測試代碼中順利的引用訪問類庫中的SquareWidget類.
補充:Visual studio 對C#類庫生成dll及對其引用
1.新建一個項目,選擇"類庫"‘用於創建C#類庫dll項目’.
2.將Class1.cs改名自己要創建的文件名,並填入代碼。
3.生成解決方案,(ctrl+shift+B);在工程文件的bin\debug目錄里看到dll,文件擴展名是dll。
4.新建一個C#控制台程序;
5.點擊 “項目” 然后 “添加引用”,從"瀏覽(B)"路徑找到剛剛生成的Dll文件並添加.
6.寫入測試代碼,在開頭加上using dll項目名語句
程序集間的繼承
C#允許從一個在不同的程序集內定義的基類來派生類。
要從不同程序集中定義基類的派生類,必須滿足:基類必須被聲明為public,這樣才能從它所在的程序集外部訪問它。
仍然利用上述SuperLib.cs,在另一個程序的源文件中引入該類庫,然后對SquaredWidget進行繼承:
// assembly.cs
using System;
using SuperLib;
namespace Constructor
{
class SquareWidgetInheritance : SquareWidget //繼承SuperLib中的SquareWidget類
{
}
class WidgetsProgram
{
static void Main()
{
SquareWidgetInheritance sq = new SquareWidgetInheritance(); //創建SquareWidgetInheritance實例
sq.SideLength = 5;
Console.WriteLine($"{sq.Area}");
Console.WriteLine($"{sq.Test1}");
//Console.WriteLine($"{sq.Test}");
Console.ReadLine();
}
}
}

成員訪問修飾符
對於類的可訪問性,只有兩種修飾符:internal,public。而類的可訪問性有5種:public, private, internal, protected, protected internal。
對於成員訪問性的細節學習之前,有一些通用性的內容需要闡述:
- 所有顯式聲明在類聲明種的成員都是互相可見的,無論它們的訪問性如何。
- 繼承的成員不在類的聲明種顯式聲明
- 必須對每個成員指定成員訪問級別。如果不指定某個成員的訪問級別,它的隱式訪問級別為private。
- 成員不能比它的類有更高的可訪問性。也就是說,如果一個類的可訪問性限於它所在的程序集,那么類的成員個體也不能從程序集的外部看到,無論它們的訪問修飾符是什么,Public也不例外。
訪問成員的區域
下面的類種聲明了5種不同訪問級別的成員:
public class MyClass
{
public int Member1;
privatte int Member2;
protected int Member3;
internal int Member4;
protected internal int Member5;
}
另一個類B能否訪問這些成員取決於以下兩個特征:
- 類B是否派生自MyClass
- 類B是否和MyClass在同一程序集
這兩個特征划分出4個集合,與MyClass類相比,其他類可以是下面任意一種。

公有成員的可訪問性
public訪問級別是限制最少的,所有的類,包括程序集內部的類和外部的類都可以自由地訪問成員。

私有成員的可訪問性
私有成員的級別是限制最嚴格的。
- private 類成員只能被它自己的類的成員訪問。它不能被其他的類訪問,包括繼承它的類。
- 然而,Private成員能被嵌套在它的類中的類成員訪問。

受保護的可訪問性
protetcted訪問級別如同private訪問級別,除了一點,它允許派生自該類的類訪問該成員。需要注意的是,即使程序外部繼承該類的類也可以訪問該成員。

內部成員的可訪問性
標記為internal的成員對程序集內的所有類可見,但對程序集外的類不可見。

受保護內部成員的可訪問性
標記為protected internal的成員對所有繼承該類的類以及所有程序集內部的類可見。注意這是protected 和internal的並集,不是交集。

成員訪問修飾符小結


抽象成員
抽象成員只能在抽象類中聲明,抽象成員有以下的特征:
- 必須是函數成員,也就是說字段和常量不能是抽象成員;
- 必須用abstract修飾符標記
- 不能有實現代碼塊,抽象成員的代碼用分號表示。
- 一共有四種類型的成員可以被聲明為抽象的:(1)方法(2)屬性(3)事件(4)索引
- 盡管抽象成員必須在派生類中用相應的成員覆寫,但不能把virtual修飾符附加到abstract修飾符
- 類似虛成員,抽象成員的實現必須指定override修飾符

抽象類
抽象類只能被用作其他類的基類,不能創建抽象類的實例,抽象類使用abstract修飾符聲明。抽象類的成員可以包含抽象成員,或普通的非抽象成員。
抽象類可以自己派生自另一個抽象類。任何派生自抽象類的類必須使用override關鍵字實現該類所有的抽象成員,除非派生類也是抽象類.
namespace AbstractExample
{
abstract class MyBase
{
public int SideLength = 10;
const int TriangleSideCount = 3;
abstract public void PrintStuff(string s);
abstract public int MyInt { get; set; }
public int PerimeterLength()
{
return TriangleSideCount * SideLength;
}
}
class MyClass: MyBase
{
public override void PrintStuff(string s)
{
Console.WriteLine(s);
}
//public override int MyInt { get; set; } //沒有實現MyInt屬性
}
class Program
{
static void Main()
{
MyClass mc = new MyClass();
mc.PrintStuff("This is a string...");
}
}
}
由於沒有實現MyInt屬性,結果拋出錯誤。

namespace AbstractExample
{
abstract class MyBase
{
public int SideLength = 10;
const int TriangleSideCount = 3;
abstract public void PrintStuff(string s);
abstract public int MyInt { get; set; }
public int PerimeterLength()
{
return TriangleSideCount * SideLength;
}
}
class MyClass: MyBase
{
public override void PrintStuff(string s)
{
Console.WriteLine(s);
}
public override int MyInt { get; set; }
}
class Program
{
static void Main()
{
MyClass mc = new MyClass();
mc.PrintStuff("This is a string...");
Console.WriteLine($"Perimeter length:{mc.PerimeterLength()}");
}
}
}

密封類
密封類與抽象類正好相反,抽象類不能有實例,只能被繼承,而密封類不能被繼承,只能被實例。用sealed修飾符標注。
靜態類
靜態類中所有成員都是靜態的。靜態類用於存放不受實例數據影響的數據和函數,常見用途是創建一個包含一組數學方法和值的數學庫。
關於靜態類需要注意的是:
- 類本身必須被標記為static
- 類的所有成員必須是靜態的
- 類可以有一個靜態構造函數,但不能有實例構造函數,不能創建該類的實例
- 靜態類是隱式密封的,也就是 說靜態類不可以被繼承
namespace StaticClassExample
{
static public class MyMath
{
public static float PI = 3.14f;
public static bool IsOdd(int x)
{
return x % 2 == 1;
}
public static int Times2(int x)
{
return 2 * x;
}
}
class Program
{
static void Main()
{
int val = 3;
Console.WriteLine($"{val} is odd is {MyMath.IsOdd(val)}");
Console.WriteLine($"{val}*2={MyMath.Times2(val)}");
}
}
}

擴展方法
擴展方法允許編寫的方法和聲明它的類之外的類關聯。
擴展方法必須聲明在靜態類中,本身必須被聲明為static,必須包括關鍵字this和它所擴展的類的名稱,作為第一個參數的類型。
namespace StaticClassExample
{
sealed class MyData
{
private double D1, D2, D3;
public MyData(double d1,double d2,double d3)
{
D1 = d1;D2 = d2;D3 = d3;
}
public double Sum()
{
return D1 + D2 + D3;
}
}
//由於上述類是sealed 類,只能被實例化,不能被繼承,派生,那么為增強該類的功能,如果我們拿不到它的源碼,無法進行
//直接添加方法,那么可以嘗試新建立一個靜態類,然后以MyData類的實例為參數,增加新功能的函數成員,然后用
//靜態類方法調用即可,但現在有更為較好的方法來解決,那就是擴展方法,它可以讓實例調用我們增加的方法,而不是用
//靜態類的類方法來調用。
static class ExtendClass
{
public static double Average(this MyData md)
{
return md.Sum() / 3;
}
}
class Program
{
static void Main()
{
MyData md = new MyData(4, 5, 6);
Console.WriteLine($"Sum:{md.Sum()}");
Console.WriteLine($"Average:{md.Average()}");//可以使用擴展方法,使得實例可以直接調用我們新創建的函數。
}
{
}
}
}

