第4章 继承

1. 继承的类型

  • C#类可以派生自另一个类和任意多个接口。
  • 结构总派生自System.ValueType,故不支持实现继承,但支持接口继承,即可以派生自任意多个接口。
  • 类总派生自System.Object或用户选择的另一个类,还可以派生自任意多个接口。
  • 如果类和接口都用于派生,则类总是必须放在接口的前面。

2.实现继承

(1) 虚方法:

  • 基类方法或属性声明命名为virtual,任何派生类中使用override显示声明来重写该方法或属性。
  • 成员字段和静态函数不能声明为virtual,因为虚方法只对类中的实例函数成员有意义。

(2) 多态性:

  • 方法中接收基类作为参数,则任何派生自基类的类型都可以传递给该方法。
  • 方法中使用参数的方法,如不是虚拟参数或没有重写派生类的方法,则执行基类方法;否则执行派生类方法。

(3) 隐藏方法:(尽量不使用)

  • 若签名相同的方法,在基类和派生类中都进行了声明,但该方法没有分别声明为virtual和override,派生类方法就会隐藏基类方法。
  • 使用new关键字隐藏方法,避免出现编译警告,用于处理版本冲突。

(4) 调用方法的基类版本:使用base关键字,在派生类中调用基类方法。

(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修饰符

  • 密封类表示不允许创建该类的子类。
  • 密封方法表示不能重写该方法。
  • 常用密封方法来终止继承:方法可以是基类的重写方法,但其继承类中不能扩展该方法。
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》

posted @ 2018-08-23 14:53  JJ聆听  阅读(166)  评论(0编辑  收藏  举报