大家好,這將是一個系列文章,都是有關於學習C#的,今天我就給大家帶來這個些列的第一章:同名方法之間的關系。
開講之前我得先簡單介紹下C#這門語言,C#是微軟於2000年6月推出的一門純粹的面向對象,是一門安全的,穩定的,簡單而優雅的由C,C++衍生出來的一門運行於.Net Framework之上的語言,其他優點還包括類型安全、組件技術、自動內存管理、跨平台異常處理、版本控制、代碼安全管理等等,其總設計師是微軟從美國Borland公司挖來的丹麥人:安德斯海爾斯伯格。
下面開始本章的內容(內容有點多,但是寫的很詳細,很值得一看)。
同名方法之間有幾種關系呢?答案是四種。
1:方法重載,方法重載是從方法體之間的參數(和方法返回值無關)來決定,對於類方法(靜態方法static method)和成員方法都適用。
2:方法重寫,方法重寫是建立在虛函數(virtual或abstract)和繼承之上,重寫函數在派生類和基類的原型是一模一樣的。
3:方法覆蓋,方法覆蓋也是建立在繼承之上,覆蓋函數在派生類和基類的原型是一模一樣的。和重寫不同的點是方法覆蓋它不是虛函數。
4:方法實現,這個比較特別,是專門針對接口而言的。
首先來來介紹一下方法重載吧:
先看下面例子:
public class SomeClass
{
// 方法i
public void Method(int a)
{
Console.WriteLine("這個是方法,參數是System.Int32類型");
}
// 方法ii
public void Method(float a)
{
Console.WriteLine("這個方法,參數是System.Single類型");
}
}
下面是調用方:
class Program
{
static void Main(string[] args)
{
SomeClass a = new SomeClass();
// 這個調用的是方法i 代碼段[1]
a.Method(3.0f);
// 這個調用的方法ii 代碼段[2]
a.Method(3);
// ※注:方法重載是面向對象中多態的一個體現,屬於靜態連篇,
// 也就是說重載的話,編譯器是找到你要調用的具體是哪個函數的
// 例如上面代碼段[1]在編譯的時候編譯器會自動幫你找到方法i並調用它
// 這種性質在方法重寫不存在,後面的例子會給大家介紹方法重寫
}
}
那麼有人就會問了,如果我使用泛型或者params參數或者ref及out能進行方法重載嗎?答案是能!請看下面幾個例子:
class Program
{
static void Main(string[] args)
{
SomeClass a = new SomeClass();
// 這個調用的是方法i 代碼段[1]
a.Method(3.0f);
// 這個調用的方法ii 代碼段[2]
a.Method(3);
// ※注:方法重載是面向對象中多態的一個體現,屬於靜態連篇,
// 也就是說重載的話,編譯器是找到你要調用的具體是哪個函數的
// 例如上面代碼段[1]在編譯的時候編譯器會自動幫你找到方法i並調用它
// 這種性質在方法重寫不存在,後面的例子會給大家介紹方法重寫
/*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓這個區間之間是新加內容↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
// 這個是調用的方法iii
a.Method(5, 1);
// 這個是調用的方法iv
a.Method(4);
// 這個調用的方法v
var s = 0;
a.Method(ref s);
/*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/
}
}
public class SomeClass
{
// 方法i
public void Method(int a)
{
Console.WriteLine("這個方法,參數是System.Int32類型");
}
// 方法ii
public void Method(float a)
{
Console.WriteLine("這個方法,參數是System.Single類型");
}
/*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓這個區間之間是新加內容↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
// 方法iii
public void Method(int a, params int[] array)
{
Console.WriteLine("這個方法,參數是System.Int32和一個可選參數");
}
// 方法iv
public void Method(T param)
{
Console.WriteLine("這個方法,參數是一個泛型類型,其類型為:" + typeof(T).FullName);
}
// 方法v
public void Method(ref int a)
{
Console.WriteLine("這個方法,參數是按引用傳值的System.Int32類型");
}
/*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/
}
那麼其實上面代碼是存在問題的,上面問題呢?看下面代碼:
class Program
{
static void Main(string[] args)
{
SomeClass a = new SomeClass();
// 這個調用的是方法i 代碼段[1]
a.Method(3.0f);
// 這個調用的方法ii 代碼段[2]
a.Method(3);
// ※注:方法重載是面向對象中多態的一個體現,屬於靜態連篇,
// 也就是說重載的話,編譯器是找到你要調用的具體是哪個函數的
// 例如上面代碼段[1]在編譯的時候編譯器會自動幫你找到方法①並調用它
// 這種性質在方法重寫不存在,後面的例子會給大家介紹方法重寫
// 這個是調用的方法iii
a.Method(5, 1);
// 這個是調用的方法iv
a.Method(4);
// 這個調用的方法v
var s = 0;
a.Method(ref s);
/*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓這個區間之間是新加內容↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
// 請看下面這個方法,其實它是符合3個方法原型的
// 它符合方法i,因為方法i:public void Method(int a)的原型參數就是一個int類型
// 其實它也符合方法iii,因為下面這個相當於在方法iii: public void Method(int a, params int[] array)中的可選參數不填
// 它也符合方法iv,因為它符合方法iv:public void Method(T param)這個原型
// 那麼問題來了,它到底調用的是方法i,方法iii,還是方法iv呢?
// 答案是:方法i
// 因為當重載沖突的時候,是最先考慮參數原型最簡單的,也就是方法i
// 其次才是考慮可選參數方法,也就是方法iii
// 最後才是考慮泛型方法,也就是方法iv
a.Method(6);
// 如果想顯示調用方法iii,你得這麼寫:
a.Method(6, new int[0]);
// 如果你想顯示調用方法iv,你得這麼寫
a.Method(6);
// 好了,方法重載就先介紹到這裡了,大家先慢慢體驗吧。
/*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/
}
}
下面來介紹本文的第二部分內容,方法重寫。其實同名方法的這4個關系都是屬於面向對象編程中多態的一部分,方法重載和方法覆蓋都是屬於靜態聯篇的一部分,也就是說編譯器是找到你具體要掉的方法的,那麼方法重寫和實現接口就完全不一樣了,這兩者是屬於動態聯篇中的一部分的,編譯器是不知道你這個方法調用的具體是哪個的,只有等到程序運行的時候才能找到具體調用的是哪個函數。
重寫的例子,看下面把:
class Program
{
static void Main(string[] args)
{
BaseClass baseClass = new MyClass();
// 這調用的是方法iii
baseClass.AbstractMethod();
// 注意,這兒調用的是方法iv而不是方法ii
baseClass.VirtualMethod();
}
}
public abstract class BaseClass
{
// 方法i,這個是一個抽象函數,是一個特殊的虛函數,它沒有實現體
// 使用的關鍵字是abstract(必須)
public abstract void AbstractMethod();
// 方法ii,是一個虛函數,有實現體
// 使用的關鍵字是virtual(必須)
public virtual void VirtualMethod()
{
Console.WriteLine("來自BaseClass的方法");
}
}
public class MyClass : BaseClass
{
// 方法iii
// 重寫一個方法,必須使用關鍵字(override)
public override void AbstractMethod()
{
Console.WriteLine("重寫了基類的AbstractMethod方法");
}
// 方法iv
// 重寫一個方法,必須使用關鍵字(override)
public override void VirtualMethod()
{
Console.WriteLine("重寫了基類的VirtualMethod方法");
// 這兒保留並繼續調用了基類的VirtualMethod方法,這個是一個良好的編程習慣
// 當能某些情況下這個基類的方法是不需要調用的
base.VirtualMethod();
}
}
下面是程序運行結果:

下面介紹一下方法覆蓋,並且也會順便講一下方法覆蓋和方法重寫兩者的區別。
class Program
{
static void Main(string[] args)
{
/*-------------------------------------------------第一部分----------------------------------------------*/
// 注意我定義c是BaseClass類型的,但是new 的實體確實MyClass類型的
// 代碼段[1]
BaseClass c = new MyClass();
// 因為這兒是一個虛方法,所以這兒的結果是調用方法iii
c.Method1();
// 注意,因為它不是一虛個方法,所以它調用的不是MyClass的方法iv
// 而是BaseClass的方法i,這個調用機制和虛函數不一樣
// 這種特殊的調用關系只在用基類引用接派生類實體中會出現,也就是和代碼段[1]的變量聲明方式有關
c.Method2();
/*-----------------------------------------------------------------------------------------------------------*/
/*-------------------------------------------------第二部分----------------------------------------------*/
// 我用MyClass類型的c2 new了一個MyClass類型的實體
MyClass c2 = new MyClass();
// 這兒結果是還是調用方法iii和上面第一部分一樣
// 虛函數的調用機制和變量定義類型無關
c2.Method1();
// 非虛函數的調用機制和變量定義類型有關
// 所以這兒調用的是MyClass的方法iv
c2.Method2();
/*-----------------------------------------------------------------------------------------------------------*/
}
}
public class BaseClass
{
// 方法i
public virtual void Method1()
{
Console.WriteLine("來自BaseClass的方法Method1");
}
// 方法ii
public void Method2()
{
Console.WriteLine("來自BaseClass的方法Method2");
}
}
public class MyClass : BaseClass
{
// 方法iii
public override void Method1()
{
Console.WriteLine("重寫了來自BaseClass類的方法Method1");
base.Method1();
}
// 方法iv
// 注意這兒必須使用關鍵字new,否者會出現一個編譯警告
public new void Method2()
{
Console.WriteLine("覆蓋了基類的Method2方法");
// 同樣這兒保留並繼續調用了基類的Method2方法
// 當能某些情況下這個基類的方法是不需要調用的
base.Method2();
}
}
運行結果為:

最後來介紹方法實現,這個比較簡單:
public interface IMyInterface
{
// 方法i
void Method();
// 方法ii
void Method(int x);
}
class MyClass : IMyInterface
{
// 這兒實現了方法i,並把它設置為了虛方法
// 當然,去掉virtual關鍵字,把它設置為一個普通的方法也是完全可以的
public virtual void Method()
{
Console.WriteLine("隱式實現了方法:IMyInterface.Method");
}
void IMyInterface.Method(int x)
{
Console.WriteLine("顯示實現了方法:IMyInterface.Method,有關接口的內容,將會在後續文章中介紹");
}
}
好了,現在已經23點半,我也得洗洗睡了,大家晚安。我會在後續的文章中介紹有關C#的其他特點。