第4章 繼承

1. 繼承的類型編程

  • C#類能夠派生自另外一個類和任意多個接口。
  • 結構總派生自System.ValueType,故不支持實現繼承,但支持接口繼承,便可以派生自任意多個接口。
  • 類總派生自System.Object或用戶選擇的另外一個類,還能夠派生自任意多個接口。
  • 若是類和接口都用於派生,則類老是必須放在接口的前面。

2.實現繼承安全

(1) 虛方法:ide

  • 基類方法或屬性聲明命名爲virtual,任何派生類中使用override顯示聲明來重寫該方法或屬性。
  • 成員字段和靜態函數不能聲明爲virtual,由於虛方法只對類中的實例函數成員有意義。

(2) 多態性:函數

  • 方法中接收基類做爲參數,則任何派生自基類的類型均可以傳遞給該方法。
  • 方法中使用參數的方法,如不是虛擬參數或沒有重寫派生類的方法,則執行基類方法;不然執行派生類方法。

(3) 隱藏方法:(儘可能不使用)spa

  • 若簽名相同的方法,在基類和派生類中都進行了聲明,但該方法沒有分別聲明爲virtual和override,派生類方法就會隱藏基類方法。
  • 使用new關鍵字隱藏方法,避免出現編譯警告,用於處理版本衝突。

(4) 調用方法的基類版本:使用base關鍵字,在派生類中調用基類方法。code

(5) 抽象類和抽象方法:對象

  • 類和方法可使用abstract關鍵字聲明爲抽象類和抽象方法。
  • 類中包括抽象方法,則該類也必須聲明爲抽象類。
  • 抽象類不能實例化;抽象方法不能直接實現,必須在非抽象的派生類中重寫(關鍵字override);派生類中須要實現全部抽象成員。
  • 雖然抽象類不能實例化,但能夠聲明一個抽象類變量,由實例化的派生類將其分配給基類。
// 抽象基類 Shape
public abstract class Shape
{
      public abstract void Resize(int width, int height); //抽象方法
}
 
// 派生類 Ellipse
public class Ellipse : Shape
{
      public override void Resize(int width, int height)
      {
            Size.Width = width;
            Size.Height = height;
      }
}
 
//實例化
Shape s1 = new Ellipse();

 (6) 密封類和密封方法:sealed修飾符blog

  • 密封類表示不容許建立該類的子類。
  • 密封方法表示不能重寫該方法。
  • 經常使用密封方法來終止繼承:方法能夠是基類的重寫方法,但其繼承類中不能擴展該方法。
class MyClass: MyBaseClass
{
     public sealed override void FinalMethod()
     {
          // implementation
     }
}

class DerivedClass: MyClass
{
     public override void FinalMethod()   //wrong!Will give compilation error
     {
     }
}

 

 (7) 派生類的構造函數:繼承

  • 對於派生類,需保證經過層次結構進行構造。
  • 構造函數老是按照層次結構的順序調用:先調用System.Object類的構造函數 -> 再按照層次結構由上向下進行 -> 直到到達編譯器要實例化的類爲止。
// 使用自動屬性初始化器初始化屬性
// 編譯器會建立一個默認的構造函數
public class Sharp
{
        public Position Position { get; } = new Position();
        public Size Size { get; } = new Size();
}

// 派生類Ellipse
public class Ellipse : Sharp
{
       public Ellipse()
              : base()
       {
       }
}

// 實例化Ellipse類,構造函數調用順序爲
// Object 構造函數 -> Sharp 構造函數 -> Ellipse 構造函數
  •  使用構造函數初始化器調用帶參數的基類構造函數的賦值方式
// 基類構造函數內進行賦值,非默認初始化
public class Shape
{
        public Shape(int width, int height, int x, int y)
        {
            Size = new Size { Width = width, Height = height };
            Position = new Position { X = x, Y = y };
        }
}

//  派生類
public class Ellipse : Shape
{
        // 派生類中建立構造函數,用構造函數初始化器初始化基類的構造函數
        public Ellipse(int width, int height, int x, int y)
            : base(width, height, x, y)
        {

        }
        // 但願容許使用默認的構造函數建立Ellipse對象
        public Ellipse()
            : base(width: 0, height: 0, x: 0, y: 0)
        {

        }
}

 3. 接口:interface索引

  • 接口只包括方法、屬性、索引器和事件的聲明。
  • 不容許提供接口中任何成員的實現方式。
  • 比較接口和抽象類:

(1) 定義和實現接口

  • 接口的命名採用Hungarian表示法,即在名稱前加一個字母,表示所定義對象的類型。接口類型字母爲I。
    public interface IBankAccount
    {
        void PayIn(decimal amount);
        bool Withdraw(decimal amount);
        decimal Balance { get; }
    }
    // 派生類 SaverAccount
    public class SaverAccount : IBankAccount
    {
        private decimal _balance;

        public void PayIn(decimal amount) => _balance += amount;

        public bool Withdraw(decimal amount)
        {
            if (_balance >= amount)
            {
                _balance -= amount;
                return true;
            }
            WriteLine("Withdrawal attempt failed.");
            return false;
        }

        public decimal Balance => _balance;

        public override string ToString() =>
            $"Venus Bank Saver: Balance = {_balance,6:C}";
    }


    // 派生類  GoldAccount
    public class GoldAccount : IBankAccount
    {
         //etc
    }

 

  • 接口僅表示其成員的存在性,派生的類負責肯定這些成員是虛擬仍是抽象的(但只有在類自己是抽象的,這些函數才能是抽象的)。
  • 接口引用安全能夠按成類引用——但接口引用強大之處在於,它能夠引用任何實現該接口的類。
IBankAccount[] accounts = new IBankAccount[2];
account[0] = new SaverAccount();
account[1] = new GoldAccount();

 (2) 派生的接口

  • 接口能夠彼此派生,實現派生接口的類必須實現相關接口的全部方法。
    public interface ITransferBankAccount : IBankAccount
    {
        bool TransferTo(IBankAccount destination, decimal amount);
    }

 

   public class CurrentAccount : ITransferBankAccount
    {
        private decimal _balance;

        public void PayIn(decimal amount) => _balance += amount;

        public bool Withdraw(decimal amount)
        {
            if (_balance >= amount)
            {
                _balance -= amount;
                return true;
            }
            WriteLine("Withdrawal attempt failed.");
            return false;
        }

        public decimal Balance => _balance;

        public bool TransferTo(IBankAccount destination, decimal amount) { bool result = Withdraw(amount); if (result) { destination.PayIn(amount); } return result; } public override string ToString() =>
          $"Jupiter Bank Current Account: Balance = {_balance,6:C}";
    }

 

  • 在實現並調用派生接口的方法時,沒必要知道該對象類型,只須要知道該對象實現了接口便可。

4. is 和 as 運算符: 與繼承有關

  • as運算符:返回對象的引用,且不拋出InvalidCastException異常;如不是所須要類型,返回null。
public void WorkWithManyDifferentObject(object o)
{
    IBankAccount account = o as IBankAccount;
    if (account != null)
    {
          //work with the account
    }
}
  •  is運算符:根據對象是否使用指定的類型,返回true或false。
public void WorkWithManyDifferentObject(object o)
{
    if (o is IBankAccount)
    {
          IBankAccount account = (IBankAccount)o;
          //work with the account
    }
}

 

注:文中代碼均來自《C#高級編程(第10版)C# 6 & .NET Core 1.0》

相關文章
相關標籤/搜索